RailsのCSRF保護について

f:id:ryoutaku_jo:20190822233652p:plain

【結論】

CSRFとは、WEBアプリケーションの投稿機能などを利用して悪意のあるコードを仕込むことで、認証済みのユーザーに意図しないコマンドを実行させる攻撃手法

・対策は、GETとPOSTを適切に使い分け(RESTful)GET以外のリクエストにセキュリティトークンを追加すること

Railsでは、一意のトークンを生成し、フォームの送信時に照合することで攻撃を防ぐ仕組みがデフォルトで備わっている

【目次】

【本題】

CSRFについて

CSRFクロスサイトリクエストフォージェリ)とは、WEBアプリケーションの脆弱性を突いた攻撃手法です。

攻撃の方法としては、まずWEBアプリケーションに備わっている投稿機能などを用いて、悪意のあるコードをWEBページ上に仕込みます。

そして、第三者がコードの仕込まれたページを開くことで、意図しないリクエストが送信されます。

これにより、ユーザーのアカウント情報が無くとも、情報を抜き取ったり、金銭的な被害を与えることができます。

対策

対策は、GETとPOSTを適切に使い分け(RESTful)GET以外のリクエストにセキュリティトークンを追加することです。

GETとPOSTを適切に使い分け

HTTPメソッドのGETは、リソースの状態を変化させない特性を持っています。

リソースの取得はPOSTでも行えますが、POSTにはGETの様な安全性はありません。

その為、リソースの取得(データ読み込み、検索など)はGETを用います

GETであれば、意図せぬリクエストが送られたとしても、データが書きかえられたりする心配はありません。

GET以外のリクエストにセキュリティトークンを追加する

そして、GET以外のリクエストについては、セキュリティトークンを追加して対策をします。

具体的な流れとしては、以下の通りです。

  1. WEBページ(HTML)一意のトークンを埋め込む
  2. 同じトークンはセッションcookieにも保存する
  3. リクエストを送信するとき、HTMLのトークンも一緒に送信する
  4. 送られてきたトークンと、セッションのトークンを比較する
  5. 両方が一致すれば、リクエストを処理する
  6. 一致しなければ、リクエストをブロックする

これにより、意図しないリクエストによる攻撃を防ぐことができます。

RailsCSRF保護の仕組み

Railsには、このCSRF保護の仕組みがデフォルトで実装されています。

CSRF保護を有効にするには、以下のコードをapplication_controller.rbに記述します。

protect_from_forgery with: :exception

Rails5.1以前ではrails newの時点で既に記述されていますが、Rails5.2以降ではこのコードが無くとも設定が有効になっています。

HTMLにトークンを埋め込むコードは、以下の通りです。

<%= csrf_meta_tags %>

こちらもapplication.html.erbに標準で記述されています。

後は、form_withlink_to等のビューヘルパーを利用すれば、リクエスト送信時に勝手にトークンも送ってくれます。

これにより、トークンの生成→埋め込み/格納→送信→照合を全て行ってくれます。

Railsのフォーム以外の通信(Ajaxなど)でCSRF保護を利用する

なお、AjaxでPOST送信したい場合など、form_withlink_to等を使わない場合は、自分でトークンを送る必要があります。

JavaScriptでHTMLに埋め込まれたトークンを取得するには、以下の様にコードを実装すれば良いです。

const csrf_token = document.getElementsByName( 'csrf-token' ).item(0).content;

トークンは X-CSRF-Token というヘッダ名で受け取れます。

    $.ajaxPrefilter( (options, originalOptions, jqXHR) => {
        if (!options.crossDomain) {
          if (token) {
               return jqXHR.setRequestHeader('X-CSRF-Token', csrf_token);
           }
        }

なお、以下の様にパラメータで渡そうとすると、「+」がスペースに置換されてしまうので、一部でエラーが発生してしまうので注意が必要です。

$.ajax({
  data: "authenticity_token": csrf_token,
  type: "POST",
  url: "http://www.example.com/",
    // ...

参考情報

Rails セキュリティガイド - Rails ガイド

RailsのCSRF保護を詳しく調べてみた(翻訳)

CSRFの対応について、rails使いが知っておくべきこと - おもしろwebサービス開発日記

外部からPOSTできない?RailsのCSRF対策をまとめてみた - Qiita

Rails アップグレードガイド - Rails ガイド

《今日の学習進捗(3年以内に10000時間に向けて)》

CTOのレビューを受けて、CSRF保護をRailsがデフォルトで用意している機能に寄せる修正を行っている。
一旦一通り動作するところもまで実装が完了したが、Rails5.1以降、コードの記述方法や仕様が変更になった箇所があることから、一部動作に不安が残る。
また、csrf_tokenがテスト環境だと取得できない問題なども発生したので、もう少し調査をしてから再度レビューを出したい。

今回のCSRF保護もそうだが、Railsが標準で備えている機能に対する理解が乏しいと改めて感じた。
OSSRailsガイドなどを定期的に読み進めて、Railsで出来る範囲を把握して、車輪の再発明を減らしていきたい。

学習開始からの期間 :258日
今日までの合計時間:2442h
一日あたりの平均学習時間:9.5h
今日までに到達すべき目標時間:2356h
目標との解離:86h
「10,000時間」まで、

残り・・・「7558時間!」