フロントエンドのネットワークセキュリティの必修科目 1 SOP、CSRF、およびCORS
/ 14 min read
本文では、3つのキーワードについて説明します:
- 同一オリジンポリシー(Same-origin policy、SOP)
- クロスサイトリクエストフォージェリ(Cross-site request forgery、CSRF)
- クロスオリジンリソース共有(Cross-Origin Resource Sharing、CORS)
同一オリジンポリシー(SOP)
同一オリジン
まず、同一オリジンとは何かを説明します:プロトコル、ドメイン、ポートがすべて同じであれば、同一オリジンです。
url | 同一オリジン |
---|---|
https://niconico.com | 基準 |
https://niconico.com/spirit | o |
https://sub.niconico.com/spirit | x |
http://niconico.com/spirit | x |
https://niconico.com:8080/spirit | x |
制限
クロスオリジンの問題に直面するのは、SOPのさまざまな制限のためです。しかし、具体的には何が制限されているのでしょうか?
もしSOPが「非同一オリジンのリソースの取得を制限する」と言っているのであれば、それは正しくありません。最も単純な例として、画像、CSS、JavaScriptファイルなどのリソースを参照する場合、クロスオリジンが許可されています。
もしSOPが「クロスオリジンのリクエストを禁止する」と言っているのであれば、それも正しくありません。本質的には、SOPはクロスオリジンのリクエストを禁止しているのではなく、リクエストの応答をインターセプトしています。
実際には、SOPには2つのケースがあります:
- iframeや画像などのリソースを通常どおり参照できますが、その内容に対する操作は制限されます。
- 直接的にはAjaxリクエストが制限されますが、正確にはAjaxの応答結果の操作が制限されます。これが後述するCSRFを引き起こす原因となります。
しかし、本質的にはこれらの2つは同じです:要するに、非同一オリジンのリソースに対して、ブラウザは「直接使用」できますが、プログラマーやユーザーはこれらのデータを操作することはできません。これにより、悪意のある行動を防止するため、現代のセキュリティブラウザはユーザーを保護しています。
以下は、実際のアプリケーションで遭遇する可能性のある3つの例です:
- Ajaxを使用して他のクロスオリジンAPIにリクエストする場合、これは最も一般的なケースであり、初心者のフロントエンド開発者にとっては悪夢です。
- iframeと親ページの間での通信(DOMや変数の取得など)は、あまり一般的ではなく、解決方法もわかりやすいです。
- クロスオリジンの画像(例えば
<img>
からの画像)を操作する場合、キャンバスで画像を操作する際にこの問題に遭遇することがあります。
もしSOPがなかったら:
- iframe内の機密情報が無制限に読み取られる可能性があります。
- より悪意のあるCSRFが行われる可能性があります。
- インターフェースが第三者に悪用される可能性があります。
クロスオリジンの回避
SOPはユーザーをより安全にする一方で、プログラマーには一定の手間をかけることになります。なぜなら、ビジネス上、クロスオリジンの要件がある場合があるからです。クロスオリジンを回避するための解決策は、制約があるため、またインターネット上にも関連する記事が多く存在するため、ここでは詳細な解決策を説明しませんが、いくつかのキーワードを示します:
Ajaxに対して
- JSONPの使用
- サーバー側でのCORSの設定
- サーバー側のリバースプロキシ
- WebSocketの使用
iframeに対して
- location.hashやwindow.nameを使用して情報をやり取りする
- postMessageの使用
クロスサイトリクエストフォージェリ(CSRF)
概要
CSRF(Cross-site request forgery)は、一般的な攻撃手法の一つです。これは、Aウェブサイトに正常にログインした後、クッキーにログイン情報が正常に保存されている状態で、他のBウェブサイトがAウェブサイトのAPIを呼び出して操作を行うことを指します。AのAPIはリクエスト時に自動的にクッキーを付けてしまいます。
先ほど述べたように、SOPではHTMLタグを使用してリソースをロードすることができ、またSOPはリクエストをブロックするのではなく、応答をインターセプトします。CSRFはこれら2つの利点をうまく利用しています。
GETリクエストに対しては、<img>
に直接配置することでクロスオリジンのAPIを気づかれずにリクエストすることができます。
POSTリクエストに対しては、多くの例ではフォームの送信が使用されます:
したがって、SOPはCSRF対策として使用できません。
SOPの制限を振り返ると、これらの2つの例はHTMLタグを直接使用してリクエストを送信していますが、ブラウザはこれを許可しています。これは、直接JSで結果を操作することができないためです。
SOPとAjax
Ajaxリクエストの場合、データを取得した後にJSで自由に操作できます。この場合、同一オリジンポリシーは応答をブロックしますが、リクエストは依然として送信されます。なぜなら、ブラウザが応答をインターセプトするからです。実際には、リクエストはすでにサーバーに送信され、結果が返されていますが、セキュリティポリシーのため、ブラウザはJSの操作を続けることを許可しません。そのため、おなじみのblocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
というエラーが表示されます。
したがって、再度強調しますが、同一オリジンポリシーはCSRF対策として使用できません。
ただし、CSRF対策の例外もあります。ブラウザはすべてのリクエストを成功させるわけではありません。上記の場合は単純なリクエストに限られます。関連する知識は、以下のCORSセクションで詳しく説明します。
CSRF対策
SOPはCSRFに乗っ取られましたが、本当に役に立たないのでしょうか?
そうではありません!SOPはクッキーの名前空間を制限していますので、リクエストは自動的にクッキーを送信しますが、攻撃者はクッキーの内容そのものを直接取得することはできません。
したがって、CSRFに対処するためのアイデアは次のとおりです:フロントエンドとバックエンドの分離によく使用される認証方法であるトークンベースの認証を使用し、トークンをクッキーに保存せず、リクエスト時にリクエストヘッダーに手動で追加します。
もう1つの方法は、クッキーの中身をリクエスト時にクエリ、ボディ、またはヘッダーで送信することです。リクエストがサーバーに到達すると、クッキーが送信された情報を照合せず、カスタムフィールドだけを確認します。正しい場合、それはクッキーを送信したドメインからのリクエストであることが確実ですが、CSRFはこれを達成できません。(この方法はフロントエンドとバックエンドの分離に使用され、バックエンドレンダリングではDOMに直接書き込むことができます)
以下はサンプルコードです:
クロスオリジンリソース共有(CORS)
クロスオリジンはブラウザの制限ですが、クロスオリジンリソース共有(Cross-origin resource sharing)もサーバーとブラウザの協調の結果です。
サーバーがCORS関連の設定を行っている場合、ブラウザに返されるレスポンスヘッダーにはAccess-Control-Allow-Origin
が追加されます。ブラウザはこのフィールドの値が現在のソースと一致する場合、クロスオリジン制限を解除します。
CORSには2つの種類のリクエストがあります。
シンプルリクエスト
- リクエストメソッドはGET、POST、またはHEADを使用します。
- Content-Typeはapplication/x-www-form-urlencoded、multipart/form-data、またはtext/plainに設定します。
上記の2つの条件を満たすものは、CORSのシンプルリクエストとなります。シンプルリクエストは直接サーバーに送信され、CSRFの原因となります。
プリフライトリクエスト
シンプルリクエストの要件を満たさないリクエストは、プリフライトリクエスト(Preflight Request)を送信する必要があります。ブラウザは、実際のリクエストを送信する前に、OPTIONメソッドのリクエストをサーバーに送信し、現在のオリジンがCORSのターゲットに適合しているかどうかを問い合わせます。検証が成功した場合にのみ、正式なリクエストが送信されます。
たとえば、application/jsonを使用してパラメータを送信するPOSTリクエストは、シンプルリクエストの要件を満たさないため、プリフライトでブロックされます。
また、PUTメソッドを使用したリクエストもプリフライトリクエストが送信されます。
上記で言及されているCSRFを防ぐことができる例外は、プリフライトリクエストを指します。クロスオリジンのプリフライトリクエストが成功しても、実際のリクエストは送信されず、これによりCSRFが成功しないようになります。
CORSとCookie
同じドメインではなく、クロスオリジンのCORSリクエストはデフォルトではCookieやHTTP認証情報を送信しません。フロントエンドとバックエンドの両方で、リクエスト時にCookieを送信するように設定する必要があります。
これがなぜaxiosでCORSリクエストを行う際にwithCredentials: true
を設定する必要があるかの理由です。
以下は、Node.jsのバックエンドフレームワークであるKoaのCORS設定です:
ちなみに、Access-Control-Allow-Credentials
をtrue
に設定する場合、Access-Control-Allow-Origin
は*
に設定することはできません。セキュリティ上の理由から、少々面倒ですね…
今後の予告
今のところはこれで終わりですが、ご質問があればコメントでお知らせください。将来的にはXSS、CSP、およびhttp/httpsに関連するトピックについても説明する予定です。
- 2020-02-13 更新 前端ネットワークセキュリティ必修 2 XSS と CSP
- 2020-06-14 本文更新、一部の説明をより正確にする