Rails のバージョン:5.2
Rails のログイン認証画面に Postman で Post してログイン認証をやってみた。
すると、ActionController::InvalidAuthenticityToken
が出た。
Rails の CSRF 対策によるもの。
CSRF とは
A さんがプロジェクト管理サービスにログイン
↓
ログアウトせずに、そのまま掲示板サイトを閲覧
↓
とある書き込みをブラウザが読み込む(<img src="http://pj-service.com/project/1/destroy>
)
↓
ブラウザから上記 URL に対してリクエストが走る(このとき、まだ有効な Cookie が送信されるため認証後の画面でもリクエストが通ってしまう)
↓
A さんが知らないうちにプロジェクトが削除されている。
ブラウザが異なるドメインからのリクエストでも、そのドメインで利用できる Cookie がある場合送信するというブラウザの仕様を突いたものみたいだ。
CSRF 対策とは
GET 以外のリクエストにセキュリティトークンを追加することで、Web アプリケーションを CSRF から守ることができます。
下記をアプリケーションコントローラに設定することで CSRF 対策が有効になる。
protect_from_forgery with: :exception
ちなみに Rails5.2 以降ではデフォルトで有効になっているみたいで定義はされていなかった。
Rails5.2 以降では ActionController::Base 内で有効になっているため、デフォルトの設定でよければ自分で記述する必要はない
具体的には下記のことをやってみるみたいだ。
rails は、get 以外の動詞のリンクに、authenticity_token というパラメータを自動的に付け加えます。get 以外の動詞の各アクションで params[:authenticity_token]と session[:csrf_id]を比較して、同値であれば OK としているようです。*1 同値でなければ ActionController::InvalidAuthenticityToken という例外がでます。
CSRF の対応について、rails 使いが知っておくべきこと - おもしろ web サービス開発日記
CSRF 対策が有効のままでどう認証を突破するか
本題です。
Postman で Rails の認証を突破する方法ですが、ググってもそもそも CSRF 対策を無効にしようみたいな記事ばかりだったので今回自分でいろいろ試してみました。
その1:authenticity_token と ID と Password を Postman から送信してみる
CSRF トークンは HTML に埋め込まれているので、ログイン画面の CSRF トークンを取得。
ブラウザのコンソールで下記を実行すると取得できます。
HTML 要素なので目視で探してもいいです。
document.getElementsByName('csrf-token')[0].content
次にブラウザのコンソールを開いたまま画面から実際にログインしてみます。
ネットワークタブから該当のアクセスを探して、Headers > Form Data を見ます。
authenticity_token
user[login]
user[password]
が送信されています。
同じように上記のパラメータを Postman からログイン画面に Post してあげればよさそうですね。
ログアウトして、authenticity_token は新しいものを取得しましょう。
これで送信してみますがダメでした。
その2:画面でログインして、ログイン後のセッションを使う
ブラウザのコンソールを開いたまま画面から実際にログインします。
ネットワークタブからログイン後のページのアクセスを探して、Headers > Request Headers > Cookie を見ます。
_src_session
のキーと値をコピーします。
Postman の Cookies に追加します。
この状態でログイン認証が必要なページに GET すると、なんと成功します。
ログイン認証後のセッションがあれば問題なくログインできるようですね。
その3:ブラウザのセッション情報を Postman と同期する
その2の方法でもよいですが、毎回面倒です。
ブラウザのセッション情報を Postman と同期する方法がありました。
Postman で Cookie が必要な API を実行する - ユニファ開発者ブログ
この方法なら、画面でログインするだけで Postman に認証後のセッション情報が同期されているので、先ほどの面倒な手順なくリクエストを投げれば成功します。
逆に画面でログアウトすれば認証が必要なリクエストの場合は失敗します。
まとめ
セッションさえ偽造できればログイン後の画面にアクセスできる。
ブラウザは毎回送信できる Cookie があれば送信している。
Postman とブラウザのセッション情報を同期すれば認証後の画面へのリクエストが思いのままに投げられる。
本来は API を投げる際はトークン認証とかなのですがそういった実装がなくとりあえずログイン認証後の画面の URL にリクエスト投げたい場合の TIPS でした。