#358 Brakeman
- Download:
- source codeProject Files in Zip (129 KB)
- mp4Full Size H.264 Video (25.5 MB)
- m4vSmaller H.264 Video (13.2 MB)
- webmFull Size VP8 Video (12.3 MB)
- ogvFull Size Theora Video (34.2 MB)
下の画面は、いくつかのセキュリティ関連の問題を持ったアプリケーションのスクリーンショットです。それらの問題を探し出すのは簡単ではありません。今回のエピソードでは、よくあるセキュリティ上の問題に対して警告を発してくれるBrakeman gemを使ってみます。
BrakemanはRailsアプリケーションのRubyコードをスキャンして、セキュリティに関連するよくある問題を探します。完全というわけではないですが、やっかいな脆弱性を見つけ出してくれるので、我々のアプリケーションで使用して何を見つけることができるかを見てみることにしましょう。
Brakemanを使う
Brakemanを使うためには、まずいつもの方法でインストールを行ないます。
$ gem install brakeman
rbenvを使っている場合は、gemのインストールの後にrbenv rehash
を実行してbrakemanコマンドを利用できるようにします。gemがインストールできたら、Railsアプリケーションのappディレクトリでbrakemanコマンドを実行します。これによってソースコードを分析し、レポートを出力します。
$ brakeman [Notice] Detected Rails 3 application Loading scanner... [Notice] Using Ruby 1.9.3. Please make sure this matches the one used to run your Rails application. Processing application in /Users/eifion/store # Rest of output omitted.
ここでBrakemanがいくつかの問題を発見しました。それらを詳細に見る前にレポートのHTMLバージョンを生成します。-o
オプションを使用してファイル名の末尾を.html
と指定すると、HTMLのレポートが生成されます。
$ brakeman -o brakeman.html
このファイルの最初にはBrakemanが実行したチェックの一覧があります。それに続いて結果の概要が表示されます。
もっとも興味深いのは、レポートの最後の部分です。そこには発見された問題のリストがあります。
最初の列は信頼度で、Brakemanがセキュリティ上の問題を見つけたことがどれくらい確実かを示しています。それに加えて、場所、問題の種類、詳細のメッセージが表示されています。HTML版のレポートを見ているので、メッセージをクリックすると問題の周辺のソースコードを確認することができます。
問題を修正する
問題のリストを上から一つずつ修正していきます。まずリストの2番目から始めます。メッセージは以下の通りです。
All versions of Rails before 3.0.14, 3.1.6, and 3.2.6 contain SQL Injection Vulnerabilities: CVE-2012-2694 and CVE-2012-2695; Upgrade to 3.2.6, 3.1.6, 3.0.14
これはアプリケーションが使用しているRailsのバージョンに固有の問題です。今回のアプリケーションはRails 3.2.5を使用していますが、重要なセキュリティ上の修正が3.2.6で導入されました。これはかなり最近の変更なので、これによってBrakemanが最新のセキュリティ関連の修正をすでにサポートしていることがわかります。この警告を出ないようにするには、gemfile内のバージョン番号を更新します。
gem 'rails', '3.2.6'
変更したらbundle update rails
を実行してRailsの新バージョンをインストールします。ここでbrakemanを再度実行してレポートを再生成すると、この問題は消えました。次の問題はUsersController
のcreate
アクションにあります。
Unprotected mass assignment near line 7: User.new(params[:user])
モデルの警告のセクションに関連の問題があります。
Mass assignment is not restricted using attr_accessible
レポートの内容がよくわからないという場合は、Brakemanのドキュメントを参照してください。そこには多くの項目がありますが、その中に1セクションまるごとを割いた警告タイプに関する説明があり、mass assignmentを始めとするBrakemanが報告するセキュリティ問題のいくつかのタイプについて詳しく解説されています。ここに、この脆弱性の内容とその解決方法が説明されています。Rails 3.1以降のバージョンであれば、設定ファイルに1行追加するだけでこの問題を簡単に修正することができます。
config.active_record.whitelist_attributes = true
新しいRailsアプリケーションではこの設定がデフォルトで有効になっていますが、古いバージョンからアップグレードした場合は忘れがちなので、Brakemanからの通知が役に立ちます。Brakemanから警告があったUser
モデルのように、すべてのモデルに忘れずにattr_accessible
を追加します。
class User < ActiveRecord::Base has_secure_password attr_accessible :name, :password, :password_confirmation validates_format_of :name, with: /^\w+$/ end
ここでレポートを再生成するとこれらの警告は消えています。次に取り組む問題はProductsController
の中にあります。
Possible SQL injection near line 3: Product.order("name #{params[:direction]}")
SQLインジェクションの問題は、常に真剣に受け止めるべきです。ProductsController
のindex
アクションを見ると、書き方に問題があるコードがありBrakemanから警告が出ています。
def index @products = Product.order("name #{params[:direction]}") end
SQLのorder
句に引数を直接挿入していますが、これは間違った方法です。これを修正するには引数の値をチェックして、asc
かdesc
のいずれかにしか設定されないようにして、その値をSQLの中で使用します。Brakemanが、paramsの値に危険性があるが、我々が設定した文字列の値は大丈夫であることを通知してくれます。
def index direction = params[:direction] == "desc" ? "desc" : "asc" @products = Product.order("name #{direction}") end
レポートを再生成するとこの問題は消えています。最後のセキュリティ警告はこれです。
Possible unprotected redirect near line 12: redirect_to((session.delete(:return_to) or root_url))
Brakemanはこのエラーが問題である確度は弱いと言っているので、もしかしたらこれは問題ではないかも知れませんが、常に念のために調査するようにするのがいいでしょう。Redirectの警告についてのドキュメントにこのタイプの脆弱性についての詳細情報があります。ユーザから渡されたパラメータに基づいてリダイレクトを行なうと、フィッシング攻撃の標的になってしまいます。我々のサイトにそっくりな悪意のあるサイトが作られて、そのサイトの持ち主がこの脆弱性を利用して我々のサイトから彼らのサイトにリダイレクトさせることが可能です。誰かが我々のアプリケーションにログインした後に悪意のあるサイトにリダイレクトされると、例えばパスワードの再入力を求められる可能性があります。我々のサイトをこの脆弱性から守るためには、リダイレクト先をアプリケーション内に限定するよう:only_path
オプションを使用します。アプリケーションのSessionsController
で、newアクションのパラメータから設定されたreturn_to
というセッション変数の値に基いてリダイレクトを行います。
class SessionsController < ApplicationController def new session[:return_to] = params[:return_to] if params[:return_to] end def create user = User.find_by_name(params[:name]) if user && user.authenticate(params[:password]) session[:user_id] = user.id redirect_url = session.delete(:return_to) || root_url redirect_to redirect_url else flash.now.alert = "Name or password is invalid" render "new" end end def destroy session[:user_id] = nil redirect_to root_url end end
これを保護するためにリダイレクトでonly_path
オプションを使用します。
def create user = User.find_by_name(params[:name]) if user && user.authenticate(params[:password]) session[:user_id] = user.id redirect_url = session.delete(:return_to) || root_url redirect_to redirect_url, :only_path => true else flash.now.alert = "Name or password is invalid" render "new" end end
ここでレポートを再生成すると、この警告は消えています。
最後の警告はUser
モデルの関連です。
Insufficient validation for 'name' using /^\w+$/. Use \A and \z as anchors near line 4
ここでは、正規表現の中で不十分な検証が使われていると表示されています。キャレット記号(^)とドル記号($)をアンカーとして使用していますが、\A
と\z
を使用するべきです。これは、ユーザ名の形式をチェックするためにUserモデルでvalidates_format_of
を使用しているからです。ユーザは、ユーザ名の欄に改行を入れることで、この検証をすり抜けることが可能です。というのもこの正規表現は行の境界だけをチェックするだけで、テキストの全体は見ていないからです。
class User < ActiveRecord::Base has_secure_password attr_accessible :name, :password, :password_confirmation validates_format_of :name, with: /^\w+$/ end
これを修正するために正規表現を変更します。
validates_format_of :name, with: /\A\w+\z/
レポートを再生成すると警告はすべてなくなりました。これで我々のアプリケーションは完全に安全になったのでしょうか? 残念ながら違います。Brakemanが検知できないこともあるので、新しいセキュリティの問題には常に注意を払うべきです。Brakemanはすばらしいツールですが、アプリケーションに問題がないことを保証するものではなく、潜在的な問題を見つけるための一つの方法として使用するべきです。
ここで紹介できなかったBrakemanの機能は他にもたくさんあります。READMEでオプションの部分を見ると、レポートの生成方法をカスタマイズする方法がたくさんあることがわかります。
BrakemanをRakeタスクとして実行したい場合は--rake
オプションを使用します。これによってアプリケーションにRakeファイルが生成されます。
namespace :brakeman do desc "Run Brakeman" task :run, :output_file do |t, args| require 'brakeman' Brakeman.run :app_path => ".", :output_file => args[:output_file], :print_report => true end end
生成されたこのタスクは、RubyからBrakemanを実行する場合のいい見本になります。多くのオプションを指定してレポートを本格的にカスタマイズすることで、アプリケーションに対応させることができます。ただしこのコマンドを実行するには、gemfileにbrakeman
gemを追加する必要があります。
gem 'brakeman', group: :development
Brakemanを実行するもう一つの方法はGuardを使う方法で、エピソード264で紹介しています。Guard Brakeman gemを使えば、ファイルを修正するたびに自動的にBrakemanが実行されるように設定することが可能です。