LoginSignup
4
2

More than 3 years have passed since last update.

react-railsを利用したRailsアプリケーションにてCSRFの対策を行う

Last updated at Posted at 2020-05-05

はじめに

最近久しぶりに Rails で Web アプリケーションを開発しました。その中で React でフォームを作ることになったため、CSRF に関する対策について調べました。そのとき調べた内容を記載します。

なお、React の利用は SPA などではなく、react-rails を利用してページごとに React Component を読み込ませています。

※ この記事は自分の個人ブログからの転載です。

CSRF について

CSRF (Cross Site Request Forgeries) とは、悪意のあるユーザーが、任意の Web アプリケーションを利用しているユーザーの認証情報を用いて、任意の Web アプリケーション上の機密情報を盗む、または意図しないコードの実行をしようとする試みのことです。

具体的な攻撃方法について、Rails ガイドに記載されている例を元に説明します。

<img
  src="http://www.webapp.com/project/1/destroy"
  width="0"
  heigth="0"
  border="0"
/>

上記のような img tag が含まれたページをブラウザが表示するとき、ブラウザは http://www.webapp.com/project/1/destroy に対して GET リクエストを実行します。

もし攻撃を受けた側のユーザーが www.webapp.com のサービスを利用しており、 web.webapp.comの認証情報がセッションや Cookies に残っていた場合、その認証情報を利用して GET http://www.webapp.com/project/1/destroy の API を実行することができます。

JavaScript を利用すれば、POST リクエストも行うことができます。次のように作られたリンクをクリックすると、POST http://www.example.com/account/destroy が実行されてしまいます。

<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
>

Rails における基本的な CSRF 対策について

Rails における CSRF の対策は、基本的には authenticity_token というパラメータを form 毎に埋め込み、POST リクエストの中でその token を検証するというものです。次のような形で、form に authenticity_token というパラメータを埋め込みます。そして、これと同じトークンをセッションにも保存します。

<form action="/login" accept-charset="UTF-8" method="post">
  <input type="hidden" name="authenticity_token" value="xxxxx==" />>
  <input type="email" name="sessions[email]" id="sessions_email" />
  ...
  <input type="submit" name="commit" value="OK" data-disable-with="OK" />
</form>

token の検証方法について、rails のドキュメントでは下記のように記載されています。

Returns true or false if a request is verified. Checks:

・Is it a GET or HEAD request? GETs should be safe and idempotent
・Does the form_authenticity_token match the given token value from the params?
・Does the X-CSRF-Token header match the form_authenticity_token?

実際の Rails のコードと合わせると、下記のような条件を満たしたとき token の検証は問題ないと判断されます。

  • request が get/ head である
  • CSRF の保護が有効になっており、かつ下記の条件を満たしている
    • request の origin が nil である。もしくは、同一 origin からのリクエストである
    • 下記パラメータのいずれかが、セッション内に格納されている authenticity_token と一致している
    • authenticity_token パラメータ
    • request.x_csrf_token

CSRF の保護は test 環境以外ではデフォルトで有効になっています。そのため、通常の Web アプリケーションの利用においては、GET リクエストでリソースの更新等をしていない限り、別段意識せずとも Rails 側が対応してくれることになります。

action/method毎にトークンの設定をする

ただし、CSP(Content Security Policy) を利用しているサービスにおいては追加で対応が必要となります。下記のような HTML が渡されたとき、後者の /innocuous にリクエストをする form 要素は無視され、前者の /user/change_password の方が優先されると報告されています。これにより、ユーザーのパスワードを変更するなどの攻撃が可能となります。

<form method="post" action="/user/change_password">
  <!-- xss -->
  <form method="post" action="/innocuous">
    <input type="hidden" name="authenticity_token" value="thetoken" />
  </form>
</form>

この問題は、この Pull Request において言及され、対策が取り込まれました。対策内容はaction/method ごとに authenticity_token を作成するというものです。これにより、上述の form hijacking により生成された POST リクエストは無効となります。

今回の対処方法

今回 CSRF 対策をする上で行ったことは下記2点です。

  • config/application.rb にて per_form_csrf_tokensを true とする (参考)
  • React Component の引数に action, method を指定した authenticity_token を渡す

authenticity_tokenの渡し方については下記のようになります。

# app/views/sessions/new.html.erb
<%= react_component("LoginForm", {
  loginUrl: admin_password_reset_url,
  token: form_authenticity_token(form_options: { action: session_path, method: "post" }),
}) %>

最後に

今回 SPAではないReactを利用したRailsアプリケーションで、CSRFの対策を行う方法についてまとめました。
この方法の問題点や、もっと良い実装方法がありましたら、コメント等をもらえると嬉しいです!

4
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2