この広告は、90日以上更新していないブログに表示しています。
安全なウェブサイトの作り方を読んだので、理解した内容を自分なりにまとめておきます。資料
上記は3章構成になっていてそれぞれ長めの内容なので、ここでは3章の『失敗例』について、Ruby on Rails ではどうするかについてをまとめます。
参考資料内のSQL インジェクション例を見て、Ruby on Rails ではどのように対策できるかを確認しました。
例えば、下記ような$uid,$pass をユーザ入力とし、SQL 文を動的に生成する場合、ユーザ入力をエスケープ処理しないとデータベースへの不正利用が実現されてしまう。
-- 実行する SQL 文SELECT *FROM usrWHEREuid ='$uid'AND pass ='$pass'-- $uid に `'goruchan--` を入力された際の SQL 文。パスワードなしで全情報が取得される。SELECT *FROM usrWHEREuid = goruchan
Ruby on Rails の場合、DB の操作はActiveRecord を介して行う。
検索にはfind_by メソッド*1やwhere メソッド*2が使える。上記のSQL 文は下記のようにかける。
# 脆弱性のある書き方user =User.find_by("uid = '#{params[:uid]}' AND pass = '#{params[:pass]}'")# SQL インジェクションへの対策後## ユーザ入力をサニタイズするsearch_sql = sanitize_sql_array(["uid = ? AND pass = ?", params[:uid], params[:pass]])user =User.find_by(search_sql)## 位置指定ハンドラで記述user =User.find_by("uid = ? AND pass = ?", params[:uid], params[:pass])## キーワード引数にて記述user =User.find_by(uid: params[:uid],pass: params[:pass])
対策後の書き方はいずれも同等で、可読性等から選択する。個人的にはキーワード引数のやつが読みやすい気がする🤔
Ruby で外部コマンドを実行する方法は、いくつかある(OS コマンドインジェクション):
# system:コマンドを実行し、実行結果の真偽値を受けとるresult = system("ls -l")puts result#<= 成功なら true, 失敗なら false# exec:Ruby のプロセスを外部コマンドに置き換えて実行。コマンド実行結果は受け取らないexec("ls -l")#<= コマンド実行に成功する場合、Ruby プロセスから抜けるputs"この行は実行されない"# バッククォート:外部コマンドを実行し、実行結果を文字列として取得result =`ls -l`puts result#<= コマンド実行時の結果
Ruby で外部コマンドをエスケープするにはmodule Shellwords が使える。
require'shellwords'pattern ='file || ls'# エスケープせずにそのまま実行puts"grep#{pattern}"# => grep file || ls# コマンドライン中に影響ある文字をエスケープするputs"grep#{Shellwords.shellescape(pattern)}"# => grep file\|\|\ ls
上記はいずれもシェルを起動して実行している。シェルの機能を実行させないことで OS コマンドインジェクションの脆弱性を解消できる。
Open3 はRuby の標準ライブラリの一部で、外部プログラムを実行するためのモジュール。次のようにして実行できる:
require'open3'# popen3 メソッドを使用して外部コマンドを実行Open3.popen3('ls','-l')do |stdin, stdout, stderr|while line = stdout.gets puts lineendend# `capture3`メソッドを使用して外部コマンドを実行command ='ls -l'stdout, stderr, status =Open3.capture3(command)puts"Standard Output:#{stdout}"puts"Standard Error:#{stderr}"puts"Exit Status:#{status.exitstatus}"
パス名パラメータに関する脆弱性は『ディレクトリトラバーサル攻撃』につながる。下記は脆弱性を含むコードで、/etc/passwd で重要情報が見れるようになってしまう。絶対パスに対して対策しても../../../etc/passwd として相対パスで指定されると表示してしまう。
file_name = params['file_name']file_name ='nofile.png'unlessFile.exist?(file_name)File.open(file_name,'rb')do |file|IO.copy_stream(file,$stdout)end
ここへの対策は、パス名からファイル名だけを取り出して使用することが根本的解決になる。Ruby ではsingleton method File.basename が用意されているのでそれを利用すると次のコードになる:
- file_name = params['file_name']+ file_name = File.basename(params['file_name'])
あらゆるセッションはcookie を利用してセッション固有の ID を保存し、多くのセッションストアでは、サーバ上のセッションデータ(データベーステーブル等)を検索する時に、このセッション ID を使用する。
不適切なセッション管理の具体例として、推測可能なセッション ID を用いてしまうことがある。セッション ID 生成を自前でやる場合には、下記のような推測しやすい ID を生成せずに、乱数発生器等によって生成される安全な値を利用すること。
Rails では、アプリケーションにアクセスする毎にセッションオブジェクトを1つ提供する。ユーザがアプリケーションを既に利用中の場合は既存のセッションを読み込み、そうでない場合は新規にセッションを作成する。セッション ID はgenerate_sid 内のSecureRandom で安全な乱数発生器によって生成される。
また、Rails のデフォルトのセッションストアは CookieStore で、cookie データは改ざん防止のため暗号署名が追加され、cookie 自身も暗号化されている。
上記の通りのため、Rails をデフォルトで利用するのであれば、推測可能な ID 生成については心配不要と言えそう🧐
XSS は Web アプリケーションにスクリプトを埋め込むことが可能な脆弱性がある場合に、利用者のブラウザ上で不正なスクリプトが実行されてしまう攻撃のこと。
XSS は根絶が難しい脆弱性である。XSSにまとめた中のエスケープ処理については、次の見落としポイントがあるので注意する:
Ruby on Rails でエスケープ処理に関して脆弱性のあるコードを考える:
# app/controllers/posts_controller.rbclassPostsController <ApplicationControllerdefshow@post =Post.find(params[:id])endend
<!-- app/views/posts/show.html.erb --><h1><%= @post.title %></h1><p><%= @post.content %></p>
上記はユーザ入力をそのまま表示してしまっているので、ブラウザ上で悪意あるスクリプトを実行される可能性がある。show.html.erb に対して、以下ようなエスケープ処理が必要。
- <p><%= @post.content %></p>+ <p><%= sanitize(@post.content) %></p>
個人的には出力全てに対してエスケープ処理ができているかの確認は人手でやるのはなかなか骨が折れると感じてます。そこで、Security Scan Tools と呼ばれるXSS 検出や診断を行うツールを活用するといいそうです。
オープンソースの Web アプリケーションセキュリティスキャナで、以下の特徴がある。
Ruby on Railsアプリケーションのセキュリティ解析を行うオープンソースのツールで、以下の特徴がある:
CSRF は悪意ある Web ページが、他の Web サーバへのリクエストを偽造(フォージェリ)し、ユーザの意図しない処理を他の Web サーバで実行させる攻撃のこと。被害者が認証済みにであることを利用し、悪意あるリクエストを送信することで、Web サーバにてアクションを実行させる。
具体的なイメージとしては次の図が参考になりました。
上記のようにログイン状態にて③が実行されると、Web アプリケーション A をそのまま実行してしまうと攻撃が成立する。これは、攻撃される Web サーバにて受け取ったリクエストがユーザ本人かどうかを確認していないことが原因で発生する。
対策としては、一般的には 『CSRFトークンの使用』がある。CSRFトークンは Web サーバが生成し、cookie や フォームんどを通じてクライアントに提供される。
このトークンは悪意ある Web ページからのリクエストからは取得できないため、リクエストにこのトークンがあることで正当性を判断できる。
Ruby on Rails では、必須セキュリティトークンが用意されている。config.action_controller.default_protect_from_forgery をtrue に設定すると自動的に行われるようになる。
また、Rails で新規作成したアプリケーションには次のコードがデフォルトで含まれる。
protect_from_forgerywith::exception
この設定は、Rails で生成される全てのフォームとAjax リクエストにセキュリティトークンが自動的に含まれるようになり、セキュリティトークンがマッチしない場合例外がスローされる。
上記の通りなため、Rails を変な使い方をしない限りはCSRF に対する対策は織り込まれた状態と言えそう。
HTTP ヘッダインジェクションは、攻撃者が Web アプリケーションの HTTP ヘッダに不正情報を挿入することを試みる攻撃手法で、次の攻撃につながる可能性がある。
上記の原因は、ユーザからの入力データをそのまま HTTP ヘッダで使ってしまうことにあるため、ユーザからの入力データを適切に検証、エスケープする対策が有効。具体的には、ヘッダーインジェクションの攻撃経路は、ヘッダーへのCRLF文字インジェクションに基づいているため、CRLF 文字のエスケープ対策が必要。
Rails ではRails 2.1.1 以降ではredirect_to メソッドのLocation フィールドから、それらの文字がエスケープされるようになったとのこと。
何かしら、ユーザ入力を用いて通常以外のヘッダーフィールドを作成する場合には、CRLF 文字のエスケープを自身で必ず実施する必要がある。
メールヘッダ・インジェクションは、主にメール送信時に攻撃者が不正なメールヘッダを挿入することで発生する。根本的な解決策としては、ユーザ入力値をメールヘッダに出力せず、メール本文に出力すれば良い。
Ruby on Rails でメール送信を行うシーンを考える。まずは脆弱性のある書き方を考える。
classUserMailer <ApplicationMailerdefwelcome_email(user, subject)@user = user mail(to:@user.email,subject: subject)endend
上記は、mail メソッドを用いてメールを送信し、to ヘッダはハードコートされているが、subject ヘッダはユーザ入力をそのまま使用している。
攻撃者が改行文字を挿入することで、追加のメールヘッダを挿入できてしまう:
subject ="Welcome to My App\r\nBcc: attacker@example.com"UserMailer.welcome_email(user, subject).deliver_now# 生成されるメールヘッダ# To: user@example.com# Subject: Welcome to My App# Bcc: attacker@example.com
上記のようなことを避けるために、ヘッダ関連のものはユーザ入力に対応させない(ハードコートする。今回の例なら'Welcome to My App'としてsubject に埋め込む)、もしくは改行コードを適切に処理する必要がある。
引用をストックしました
引用するにはまずログインしてください
引用をストックできませんでした。再度お試しください
限定公開記事のため引用できません。