この記事はSPA + WEB_API
で構築するWEBサービスのセキュリティの脆弱性を考える時に必ず出てくる、CSRF対策やCORSの考察をした記録です。
SPAでWEBサイトを構築する方、または既に持っていて脆弱性がないか調べている時にお役に立てる内容となります。
CSRFについてはこちらのサイトがとてもわかりやすいです。
https://qiita.com/okamoai/items/044c03680766f0609d41
大前提
ブラウザで非同期通信を行う際には、同一生成元ポリシー(Same Origin Policy)によってWebページを生成したドメイン以外へのHTTPリクエストができません。
SPA + WEB_APIのパターンなど異なるドメインのリソースにアクセスしたいという需要があります
→ 昔はJSONP使ってたがセキュリティだめ
→ より手軽に、より安全にクロスドメインアクセスを実現したいという要求に応えるために作られたのがCORSという手法
CORSの設定方法
- クロスドメインアクセスされるサーバー側(=API)でアクセスを制御するルールを設定
- ブラウザとサーバー側でHTTPヘッダを使ってアクセス制御に関する情報をやりとりしながらドメインをまたいだアクセスをする
- サーバー側で設定するアクセスを制御するルールは下記
- クロスドメインアクセスを許可するWebページのオリジンサーバーのドメイン(通常SPAドメインを指定)
- 使用を許可するHTTPメソッド(GET,POSTなど)
- 使用を許可するHTTPヘッダ
実際の設定例
- AWS S3
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>https://sample-domain.cdn.com</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
- Rails API
rack-cors使用
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins Rails.application.config.x.cors_allowed_origins
resource 'sample-domain.cdn.com',
headers: :any,
methods: %i[get post put patch delete options head]
end
end
CORSのチェックはどういう時に行われる?
-
非同期かつクロスオリジンリクエストの場合のみ行われる、
プリフライトリクエスト
でCORSチェックを行う。http methodはOPTIONS
-
必ずプリフライトリクエストを伴うようになっていればよいということ。(非同期でのoriginヘッダーの偽装はできない)
-
プリフライトリクエストが起こらない単純リクエストというのもある。
- https://developer.mozilla.org/ja/docs/Web/HTTP/CORS 参照:「アクセス制御の例 」
-
SPA + APIのパターンでは
Content-Type: application/json
にすることが多い。→ 原則としてプリフライトリクエストを伴う。 -
API側でリクエストの受付をapplication/jsonのみにしておくと確実である。
CORSのチェックが行われない場合はどういうとき?
-
同期リクエスト(curlなどでAPI叩いたり、ブラウザの標準リクエスト)
-
この場合CORSチェックを伴わないAPIリクエストが可能になってしまう == CSRF攻撃が可能
CSRF対策
同期API直リクエストや非同期の単純リクエストの際に起こりうるCSRF、その対策はどうすれば良いか?
それぞれのリクエストの特徴がある!
非同期リクエスト
- カスタムヘッダを追加できる
- プリフライトリクエストを伴うことが可能
同期リクエスト
- カスタムヘッダの追加が不可能(標準ヘッダの変更しかできない)
対策方法
CSRF対策はいくつかの方法があります。
- CSRFトークンを使用した方法
- プリフライトリクエストとカスタムヘッダを使用
- Cookieの
SameSite
オプションを使用
3については下記サイトをご参照ください。ここでは主に2の方法を説明します。
https://qiita.com/okamoai/items/044c03680766f0609d41
2. プリフライトリクエストとカスタムヘッダを使用
- 同期リクエストはそもそもできなくしたい
- → APIサーバーでカスタムヘッダの検証をさせ、非同期リクエストではカスタムヘッダを付与してリクエストする。同期リクエストはカスタムヘッダの追加ができないので全て防ぐことが可能。
- → カスタムヘッダを下記のようにすることで 1. CSRFトークン も合わせて可能
# APIで生成し保存されているユーザー固有のランダム文字列
request.headers: { x-csrf-token: 'ランダム文字列', ... }
- 非同期リクエストへの対応
- → APIでapplication/jsonのみ受付け可能にする = プリフライトリクエストが必ず行われる
- → APIでCORS設定を行う(許可したオリジンアクセスのみリクエストできる)
あとがき
WEB API + SPA パターンは先行してWEBを作成、後追いでアプリを作る場合などにもよく使われます。
最近ではかなり案件数も増えている構成なので、これを気にちゃんと勉強できてよかったです。
ここまで読んでくださいまして、ありがとうございました!
誤りなどあれば、問題になることもあると思いますのでご指摘いただけると幸いです!