概要

クロスサイトリクエストフォージェリ(以下、CSRF(シーサーフ)と呼称します)は、Webサイトの利用者が意図せずWebサイト上の処理を実行させられてしまう脆弱性です。

攻撃者は、攻撃者が用意したWebサイトに何らかの方法(メール、SNSでURLを送信するなど)で利用者を誘導します。例えば、CSRFの脆弱性があるWebサイトの利用者が、攻撃者によって用意されたサイトを訪れると、攻撃者が指定した処理を実行するリクエストが、利用者のブラウザから意図せず正規のWebサイトに向けて送信されます。このとき、攻撃者が指定した処理は利用者からの要求として実行されます。

CSRFは別名として、「XSRF」、「リクエスト強要」、「Session Riding(セッションライディング)」と呼称されることもあります。

関連ゼイジャッキー

CSRF

クロスサイトリクエストフォージェリ
どこからともなく風に乗って現れてサーファーの板をのっとり、狙った場所でサーフ板の持ち主になりすましていたずらをする。
成りすましたユーザの登録情報を変更したり、ユーザの権限で物品購入や送金など不正な操作をしたりする。
クロスサイトスクリプティングと名前が似ているが全くの別種である。

ゼイジャッキーとは?

 

 

脆弱性の仕組み

多くのWebサイトでは、無数のリクエストの中から利用者ごとの状態を識別するために、Webサイトが生成した値(以下、セッションID※1と呼びます)を利用者のブラウザに記憶させます。

例えば、利用者はセッションIDを保持した状態でWebサイトにアクセスするため、Webサイトは利用者ごとのログイン状態を識別できます。

※1 セッションについて、詳細は以前のブログで解説しています。

img01

このようなセッションID(図中のSESSION_ID)のやり取りによって、ログインに必要な情報を任意の処理ごとに入力しなくても、Webサイトはしばらくの間ログイン状態を維持できます。
CSRF攻撃は、Webサイト利用者がWebサイト上で実行できる何らかの重要な処理を標的とします。何らかの重要な処理とは、例えば次のようなものがあげられます。

  • SNSにおける投稿処理・投稿の削除処理
  • ECサイトにおける物品購入処理
  • 決済サービスの送金処理
  • アカウント情報変更処理 etc.

またCSRFの脆弱性が悪用されると、例えば次のような被害が発生します。

  • Webサイト利用者が攻撃者によって意図せず不適切な投稿をしてしまう
  • Webサイト利用者のアカウント情報が攻撃者によって意図せず変更されてしまい、アカウントが乗っ取られてしまう
  • Webサイト利用者が攻撃者によって意図しない宛先に送金してしまう etc.

ここでは、決済サービスの送金処理にCSRFの脆弱性があった場合を例にとって考えます。 説明のため、次のようなWebサイトが存在すると仮定します。

  • 送金処理を実装した正規のWebサイト(https://bank.example/)
  • 攻撃者が用意した悪意のあるWebサイト(https://attacker.example/)

そのうえで、攻撃者が次のようなフォームを実装した悪意のあるWebサイトを用意し、正規のWebサイトの利用者がクリックするように誘導します ※2

※2 攻撃者の用意したWebサイトの実装次第では、JavaScriptを組み合わせることで、ボタンクリックを待たずにリクエストを発生させることも可能です。

<!-- attacker.example -->
<form method="POST" action="https://bank.example/transfer">
 <input type="hidden" name="receiver" value="attacker">
 <input type="hidden" name="amount" value="100000">
 <button type=submit>クリックすると幸せになります</button>
</form>

クリックすると幸せになれる、シンプルなボタンです。このHTMLがレンダリングされると、ブラウザでは次のように表示されます。

img02

攻撃者が用意したサイトの見え方

ここで正規のWebサイトの利用者が、正規のWebサイトにCookie(セッションIDなど)を持った状態でこのボタンをクリックすると、次のようなリクエストが発生します。便宜上、ヘッダを一部省略しています。

POST /transfer HTTP/1.1
Host: bank.example
Content-Type: application/x-www-form-urlencoded
Cookie: SESSION_ID=abcd
 
receiver=attacker&amount=100000

リクエストは正規のWebサイトの利用者が所持しているbank.example上のCookieを含んで送信されます。SESSION_IDがログイン済みのCookieである場合、送金処理はWebサイト利用者による正当なリクエストとして処理されてしまいます。(SESSION_IDに設定されている属性によっては、SESSION_IDは送信されません。詳細は対策:CookieのSameSite属性を適切に設定するに記載しています。)

img03

このように、攻撃者によって指定された処理を、Webサイトの利用者が意図せずに実行させられてしまう脆弱性が、CSRFの脆弱性です。

対策

CSRFの脆弱性の根本的な原因は、「Webサーバが受信したリクエストがどこから来たものかわからない」ことにあります。例えば、脆弱性の仕組みで登場したようなattacker.exampleから送信されるリクエストを、bank.exampleがbank.example以外から送信されたリクエストであることを判別できれば、CSRFの攻撃は成立しないはずです。

この章では、CSRF攻撃の対策方法をいくつかご紹介します。

ここで紹介するCSRF攻撃の対策のうち、トークンを用いてリクエストの正当性を検証する方法は多くのWebアプリケーションフレームワークが採用している最も一般的な対策方法です。

また、CSRF攻撃には多層防御的な対策を推奨します。トークンを用いてリクエストの正当性を検証する対策を実装したうえでそれ以外の対策も可能な限り実装し、より堅牢な対策とすることが好ましいでしょう。

トークンを用いてリクエストの正当性を検証する

フォームを実装しているページに、攻撃者が推測できない値(以下、CSRFトークンと呼びます)※3を埋め込む対策方法です。

Webサーバで生成したCSRFトークンをWebサイト利用者にあらかじめ渡しておき、重要な処理を実行する際には、セッション管理用のCookieと併せてCSRFトークンを送信します。Webサーバは、受信したリクエストに含まれるCSRFトークンがWebサーバで生成したものと一致することを確認します。

CSRFトークンはWebサイトとその利用者しか知りえない値であるため、CSRFトークンが一致しないリクエストを「不正なリクエスト」として拒否できるようになります。

※3 ランダムな値は暗号論的疑似乱数生成器を使って生成してください。例えば、JavaにおいてはSecureRandomクラスが該当します。

img04

多くのWebアプリケーションフレームワークでは、CSRFトークンを⽤いてCSRF攻撃を対策する機能を備えています。したがって昨今のWebアプリケーション開発においては、これらの対策を自前で実装するケースは少ないでしょう。Webアプリケーションフレームワークが保有するCSRF攻撃を対策する機能を利用することが一般的であり、推奨されます。

Webアプリケーションフレームワークがこのような機能を備えていない場合、Webサイト上に存在するすべての重要な処理に、同等の機能を実装することを推奨します。

Originリクエストヘッダを検証する

WebサーバでOriginリクエストヘッダを検証し、許可したオリジン以外からのリクエストを拒否する対策方法です。

Originリクエストヘッダは、リクエストの発生したオリジン(スキーム、ホスト名、ポート番号)が記録されたリクエストヘッダです。オリジンをまたぐリクエストはもちろん、同一オリジン内のリクエストであってもOriginリクエストヘッダは付与されます 。※4

つまり、脆弱性の仕組みで登場したようなhttps://attacker.exampleからhttps://bank.exampleにリクエストするような場合は、次のようなヘッダをブラウザが自動的に付与します。

Origin: https://attacker.example

このようなブラウザの性質を利用して、アプリケーションが意図しないオリジンからのリクエストを拒否するように実装・またはミドルウェアを構成することで、CSRF攻撃の対策の1つとなります。

※4 同一オリジン内のGETまたはHEADリクエストなど、Originリクエストヘッダが送信されない一部の例外もあります。

img05

ただし、HTTP GETメソッドではOriginリクエストヘッダは付与されないため、GETメソッドを用いたエンドポイントを保護したい場合、この方法だけでは不十分です。

なお、この対策に限った話ではありませんが、データの読み取り以外の用途にGETメソッドを用いるべきではありません。

カスタムリクエストヘッダが付与されていることを検証する

正当なリクエストにはカスタムリクエストヘッダを付与し、Webサーバが受信したリクエストにカスタムリクエストヘッダが付与されていることを検証する対策方法です。HTML formによるリクエストではなく、JavaScriptを用いたリクエストを実装していることが前提の対策となります。
JavaScriptを用いてブラウザからWebサーバに向けてリクエストする場合、正当なリクエストには必ずカスタムヘッダを付与するように実装します。このとき、ヘッダの値は重要ではありません。※5

※5 ヘッダの値にCSRFトークンを含めWebサーバで検証する方式は、より安全な対策です。本記事では値を検証しない対策のみ紹介しています。

fetch('https://api.bank.example/transfer', {
  method: 'POST',
  headers: {
    'X-Custom-Header': '任意の値',
  },
  body: params,
});

実際に送信されるリクエストは、おおよそ次のようになります。

POST /transfer HTTP/1.1
Host: api.bank.example
Content-Type: application/x-www-form-urlencoded
X-Custom-Header: <任意の値>

<params>

上記の例において、Webサーバで受信したリクエストにX-Custom-Headerが付与されていることを確認します。付与されていない場合、不正なリクエストとして拒否するように実装します。

また、この方法はブラウザにおけるオリジンをまたいだリクエストの仕組み(オリジン間リソース共有: CORS)を利用しています。 ブラウザはオリジンをまたいだ単純リクエスト以外のリクエストを送信する前に、プリフライトリクエストと呼ばれるHTTP OPTIONSリクエストを自動的に送信し、本来のリクエストを送信してよいかサーバに確認します。 カスタムヘッダが付与されたすべてのリクエストは単純リクエストではないため、カスタムヘッダを強制させることは、プリフライトリクエストを強制させることを意味します。

ブラウザが送信したプリフライトリクエストに対して、Webサーバは次のようなレスポンスヘッダを返します。

Access-Control-Allow-Origin: https://bank.example
Access-Control-Allow-Headers: X-Custom-Header

プリフライトリクエストに失敗した(リクエスト発生元のオリジンがAccess-Control-Allow-Originで許可されていないなど)場合、ブラウザは本来送信しようとしていたリクエストを送信しません。

img06_2

こうすることで、Webサーバが期待するオリジン以外からのリクエストを拒否できるようになります。

カスタムリクエストヘッダおよびCORSの設定によるCSRF攻撃の対策は自前でも実装できますが、Webアプリケーションフレームワークが用意した機能を使用するか、公開されているライブラリを使用してミドルウェアを構成することが一般的です。

Fetch-Metadataを検証する

ブラウザが送信するすべてのHTTPSリクエストにはFetch Metadataリクエストヘッダが付与されています。このうち、Sec-Fetch-Siteリクエストヘッダを検証する方法です。
ここまでの例では割愛しましたが、脆弱性の仕組みで登場したhttps://bank.exampleのフォームリクエストには、次のようなヘッダが含まれています。

Sec-Fetch-Site: same-origin

Sec-Fetch-Siteリクエストヘッダは、リクエストの発生元が記録されたヘッダです。Sec-Fetch-Siteリクエストヘッダを確認することで、Webサーバは受信したリクエストがどこで発生したリクエストなのかを知ることができます。

つまり、https://bank.exampleからhttps://bank.exampleに発生するリクエスト以外をブロックする場合、Sec-Fetch-Siteリクエストヘッダの値がsame-originであることを確認すれば、CSRF攻撃の対策となります。

セッション管理用のCookieを利用者のブラウザに記録する際、SameSite属性をLaxまたはStrictに設定することで、CSRF攻撃の緩和策となります。

Set-Cookie: SESSION_ID=abcd; SameSite=Lax;
SameSite属性にLax、Strictが指定されたCookieは、サイトをまたぐリクエストにおいて次のようにCookieの自動送信が制限されます。
  • Lax: GET以外のサイトをまたぐリクエストにおいてCookieを自動で付与しない
  • Strict: サイトをまたぐあらゆるリクエストにおいてCookieを自動で付与しない

サイトの定義について詳しい説明は省略しますが、例えば、次のようなURLは登録可能な同一ドメインであるため、同じサイトとして扱われます。

  • https://bank.example/
  • https://api.bank.example/

ただし、サイトの定義はブラウザ間で解釈に違いがあり、MDNにも次のようにあいまいな記載があります。

これらは、同じサイトであったり、スキームが考慮されれば異なるサイトであったりします。

- http://example.com

- https://example.com

Site (サイト) - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN より

そのため、SameSite属性を付与するだけではなく、ここまでで紹介した対策と組み合わせて堅牢な対策とするのが好ましいでしょう。

また、記載した通りですがSameSite属性にLaxが指定されたCookieはHTTP GETリクエストで自動で送信されます。繰り返しになりますが、データの読み取り以外の用途にGETメソッドを用いるべきではありません。

被害例

CSRF攻撃が成立する仕組みとその対策を解説しましたが、この章ではCSRFの脆弱性に起因して発生した現実世界の事例をいくつかご紹介します。

ぼくはまちちゃん騒動

2005年4月中旬頃、SNSのひとつである「mixi」でログイン済みの利用者が同サイトの日記に投稿されていたURLをクリックすると、利用者の意図と関係なく、「ぼくはまちちゃん!」というタイトルの日記が自動的に投稿されるようになっていました。

横浜市小学校襲撃予告事件

襲撃予告が投稿された正規のWebサイト(横浜市の公式Webサイト)では、投稿された文章が正規のWebサイトの入力フォームを経由したデータかどうかを確認しておらず、攻撃者が用意した悪意のあるWebサイトにアクセスすると、ただちに襲撃予告が送信されるようになっていました。 これにより、当時19歳の男性が襲撃予告を投稿したとして誤認逮捕されました。

JVN iPediaに登録された本脆弱性の件数

「CSRFに関する脆弱性(CWE-352)として」直近10年間でJVN※6に登録された件数です。

年代 件数
2013 22件
2014 32件
2015 92件
2016 176件
2017 268件
2018 442件
2019 485件
2020 389件
2021 427件
2022 749件
2023 1087件
JVNiPedia

上記の表はMyJVN APIを利用してCWE識別子をもとに集計しています。

 ※6 JVNは、JPCERTコーディネーションセンターと独立行政法人情報処理推進機構(IPA)が共同で運営する、脆弱性対策情報ポータルサイトです。

まとめ

CSRF攻撃は、利用者のアカウントの乗っ取りや不正利用につながるほか、サービスの特性によってはWebサイト全体に影響を及ぼす可能性があります。
こういった被害を防ぐには、Webサイトの設計を見直すほか、脆弱性診断を実施して、意図しないWebサイトからのリクエストを受け入れないよう対策し、安全な状態か確認することを推奨します。