前提
Cloud CIRCUS Meetup に登壇した際に話した内容を記事にしました.
Meetup のアーカイブは以下に上がっています.
本記事は, Ruby on Rails で作成したプロジェクトが, どのように CSRF から私たちを守ってくれているのかの全体像を なんとなく 掴むところをゴールとして構成されています.
そのため, Rails 自体のソースコードの詳しい解説や, その他のセキュリティ対策に関することはスコープ外とします.
登壇した際に使用したスライドは以下で公開しています.
CSRF とは
Cross Site Request Forgery の頭文字を取ったもので, 利用者が意図しないリクエストを攻撃対象の Web サーバに送ってしまう脆弱性及びその脆弱性を利用した攻撃方法のことを指します.
具体的な例としては, ユーザがなにかの EC サイトにログインしている状態で
攻撃者が用意した Web アプリケーションに訪問すると, EC サイトに保持されているセッション情報を使用して, 意図しない商品の購入などが実行されてしまうなどが挙げられます.
図解
本来のながれ
攻撃のながれ
Rails はどのように僕たちを守っているのか
Rails では, HTML - セッション Cookie に 一意のトークン ( csrf_token
) を埋め込むことで CSRF 対策をしています.
一意のトークンは, デフォルトで authenticity_token
というキー名で使用されます.
発行のながれ
app/views/layouts/application.html.erb
の head
内で HTML へ埋め込んでいます.
rails の csrf_meta_tags
メソッドは actionview/lib/action_view/helpers/csrf_helper.rb
内に定義されており, トークン発行処理は form_authenticity_token
に実装されています (登壇資料作成に当たってそれなりに潜りましたが詳細の解説は本記事では割愛します) .
form_authenticity_token
が呼び出されると, 以下のような流れでトークン発行を実行します.
- ランダムな文字列を Base64 形式で生成し, セッションに格納します.
- ランダムなバイト列でワンタイムパッドを生成.
- ワンタイムパッドとセッションに格納された文字列をデコードした値を XOR 操作することによって暗号化します.
- ワンタイムパッドを暗号化した文字列の先頭に付加します (先頭にワンタイムパッドを仕込むことで復号可能な文字列となります) .
- view に埋め込むため, 先程生成した文字列をエンコードします.
以上の手順で csrf_token の view への埋め込みと, セッション cookie への格納を実行しています.
照合のながれ
Rails5 以降はデフォルトでオンになっているため, このような記述はされていませんが, protect_from_forgery
メソッドを呼び出すことで リクエストごと1 に csrf_token
の照合を実行します.
protect_from_forgery
が呼び出されると, 以下のような流れでトークン照合を実行します.
- パラメータのトークン (
authenticity_token
) をデコードします. - トークン先頭のワンタイムパッドを使用して発行時と同じロジックでマスクを解除します.
- マスク解除した文字列とセッション cookie をデコードした値で照合します.
- 照合が通過すれば以降の処理が実行されます.
上記のような処理をリクエストごとに実行することで, CSRF からアプリケーションを守ってくれるような仕組みとなっています.
まとめ
今回は Rails の CSRF 対策の全体像について執筆しましたが, Rails 以外の Web フレームワークでも同様に様々な機能が実装されています.
「なぜこのような記述がされているのか?」というところを
突き詰めてみると面白いですし, 実際の開発でも役に立つことがあるかもしれません.
-
POST リクエストのみ検証するなどの条件があります. ↩