ログイン中のユーザーが意図しない操作を実行させられる「CSRF(クロスサイトリクエストフォージェリ)」は、Webアプリケーションにおいて見落としがちな脆弱性のひとつです。特にセッション管理やAPI連携が関わる場面では、意識的な対策が欠かせません。本記事では、CSRFの基本的な仕組みから、現場でよく使われる対策方法、そして実装時に気を付けるべきポイントまでを解説します。Webエンジニアとして安全なアプリケーションを構築するために、改めてCSRFの基礎と実践知識を押さえておきましょう。
CSRFとは何か?
CSRF(クロスサイトリクエストフォージェリ)は、Webアプリケーションに対する代表的な攻撃手法の一つであり、「信頼されたユーザーの操作を不正に悪用する」という点で非常に厄介な存在です。ユーザーがログイン中であることを利用して、意図しないリクエストをサーバーに送信させ、勝手に処理を実行させてしまいます。
Webアプリケーションが抱える“信頼”の落とし穴
Webアプリケーションでは、多くの場合「ログイン済みのユーザー=信頼できるユーザー」とみなしてリクエストを処理します。この「信頼」はセッションやCookieといった仕組みによって維持されており、ログイン後の操作において、再度パスワードを求めるような手間を減らすために活用されます。
しかし、この「信頼」の仕組みは裏を返せば、ユーザーが意図していなくても、正規のリクエストとして処理されてしまう可能性があるということです。これこそが、CSRFの最大の盲点です。
ユーザーの「意図しない操作」を成立させる攻撃とは
CSRF攻撃では、攻撃者が用意した罠ページやメールリンクをユーザーが開くことで、そのブラウザが勝手に対象のWebアプリケーションへリクエストを送る構造になっています。たとえば、以下のようなフォームが罠ページに含まれていた場合を考えてみましょう。
<form action="<https://example-bank.com/transfer>" method="POST">
<input type="hidden" name="amount" value="100000">
<input type="hidden" name="toAccount" value="attacker-account">
<input type="submit" value="送金">
</form>
<script>
document.forms[0].submit();
</script>
このようなページにアクセスしたユーザーがログイン状態であれば、自動送信されたPOSTリクエストが正規の送金処理として扱われる可能性があります。攻撃者は自らのページに悪意あるHTMLやJavaScriptを埋め込み、セッションの「信頼性」を逆手に取って攻撃を仕掛けます。
✅ ポイント:
- ユーザーの意図とは無関係に「信頼されたセッション」が利用される。
- 攻撃はユーザーの一瞬の操作(クリックやリンクアクセス)をトリガーとする。
- フロント側の見た目では、まったく何も起こっていないように見えることが多い。
攻撃フローを図で理解する:CSRFの典型的なシナリオ
CSRF攻撃が成立するまでの流れは、一見複雑に見えますが、実は非常にシンプルです。ここでは、ユーザーが銀行サイトにログインしている状態を例に、シーケンス図を用いて攻撃の典型的な流れを整理していきます。
シーケンス図で読み解く攻撃ステップ
CSRFの基本的な攻撃フローは以下のようになります。
- ユーザーが銀行サイト(例:example-bank.com)にログイン
ブラウザにはセッション情報が保存され、以降のリクエストには自動でCookieが付与される。
- 攻撃者が用意した罠ページ(malicious.com)を開く
メール、SNS、広告などで誘導される。
- 罠ページがhiddenフィールド付きの送金フォームを自動送信
POST
リクエストが銀行サイトに送られる。 - 銀行サイトはセッション付きの正規ユーザーからのリクエストと判断し処理を実行
結果として、ユーザーの口座から攻撃者の口座へ送金が行われる。
この一連の流れは、ユーザーが意識しない間に完結します。特に注意すべきは、「リクエスト元が第三者サイトでも、セッション付きのリクエストであれば処理されてしまう」という点です。
銀行を例にした実践的な流れ
実際の銀行システムを想定すると、以下のような情報が攻撃対象となります。
- エンドポイント:
https://example-bank.com/transfer
- パラメータ:
amount
,toAccount
- 認証:ログインセッション(Cookie)
攻撃者はこれらの情報を元にフォームを偽造し、以下のようなHTMLを生成します。
<form action="<https://example-bank.com/transfer>" method="POST">
<input type="hidden" name="amount" value="50000">
<input type="hidden" name="toAccount" value="attacker123">
<input type="submit" value="送信">
</form>
<script>
document.forms[0].submit();
</script>
ユーザーがこのページを開いた瞬間、意図しない送金が実行される可能性があります。
攻撃者は何を知っていなければならないのか?
意外に思われるかもしれませんが、CSRF攻撃は「対象サイトの内部構造をある程度知っていること」が前提です。攻撃者が必要とする情報には次のようなものがあります。
- 送金APIのURLやリクエスト方式(GET or POST)
- 必要なパラメータ名とその形式
- ログインがセッションベースであること(Cookieに依存)
これらの情報は、Webブラウザの開発者ツールや、公開されているドキュメント、さらには過去のリバースエンジニアリングなどから取得できます。つまり「公開されているインターフェースは、攻撃者にも等しく公開されている」という認識が必要です。
✅ ポイント:
- CSRFは“セッションの信頼性”を悪用する攻撃。
- 攻撃の成立には「ユーザーがログイン済み」「攻撃者がリクエスト構造を把握済み」の2条件が必要。
- 攻撃者は対象アプリの表面的な動作から必要情報を推測できる。
「セッションが生きている状態」とは何か?
CSRF攻撃が成立するための前提条件のひとつに「ユーザーがログイン済みであること」があります。言い換えると、「セッションが生きている状態」でなければ攻撃は成立しません。ここでは、この「セッションの状態」がどのように攻撃と関係するのかを掘り下げて解説します。
ログイン済みかどうかが攻撃成立に直結する理由
多くのWebアプリケーションでは、ユーザーがログインに成功すると、サーバー側でセッションを生成し、対応するセッションIDがクッキーに保存されます。このセッションIDは、リクエストごとにブラウザから自動的に送信されるため、ユーザーは毎回ログインし直す必要がありません。
CSRF攻撃では、この自動送信されるセッションIDが“本人確認”の代わりに利用されてしまう点が最大の問題です。つまり、ユーザーがログイン状態である限り、第三者が仕掛けたリクエストでも「本人の操作」として処理されてしまうのです。
ブラウザがセッション情報を送ってしまう仕組み
ここでの重要なポイントは、「ユーザーが意図せず開いたページからのリクエストであっても、Cookieは送信される」というブラウザの仕様です。これは、同一オリジン(same-origin)であれば当然の挙動であり、利便性のために設計されています。
しかし、この仕様がCSRF攻撃の土壌にもなっています。たとえば以下のような状況がそれにあたります。
- ユーザーがすでに
https://example.com
にログイン済み - 攻撃者のページ
https://evil.com
が、フォームを使ってhttps://example.com
にPOSTリクエストを送る - ブラウザは自動的に、example.comのCookie(セッション情報)を送信
- サーバー側では正規のユーザーの操作と誤認して処理を実行
つまり、ブラウザはリクエストの“出所”ではなく、“送り先”に対してのみCookieを添付するため、攻撃者が発したリクエストにも正規ユーザーのセッション情報がくっついてしまうのです。
✅ ポイント:
- ログイン=セッション情報(Cookie)が有効であるということ。
- CSRF攻撃は「ユーザーのセッションを外部から悪用する」手法。
- ブラウザの自動送信によって、ユーザーの意思とは無関係に攻撃が成立する。
なぜ罠ページにアクセスさせられてしまうのか?
CSRF攻撃では、ユーザー自身が罠ページを開いてしまうことが発端となります。つまり、攻撃の第一歩は「ユーザーを騙して罠ページを表示させること」です。このセクションでは、攻撃者がどのようにしてユーザーを誘導するのか、そしてどんな場面が危険なのかを具体的に解説します。
ユーザーを誘導する4つの典型パターン
- メール(フィッシング)
- 攻撃者は、銀行やSNSを装ったメールを送り、「こちらでアカウント情報を確認してください」といったリンクを貼り付けます。
- メールを開いてリンクをクリックした瞬間に、背後で不正なリクエストが発生します。
- SNS(拡散型トラップ)
- TwitterやFacebookで魅力的な投稿をし、そのリンク先に罠ページを仕込みます。
- 「限定キャンペーン」「今すぐアクセス」など、ユーザーの興味を引く文言でクリックさせます。
- 広告(マルバタイジング)
- 正規の広告ネットワークに、不正なスクリプトを仕込んだバナー広告を出稿します。
- ユーザーが広告をクリック、あるいは表示されただけでCSRFリクエストが発生することもあります。
- XSS(クロスサイトスクリプティング)
- 別の脆弱性を利用して、正規サイト内に悪意のあるJavaScriptを埋め込みます。
- 正規サイト内でCSRFが実行されるため、ユーザーはまったく疑う余地がありません。
これらの手口はどれも“自然な操作”を装っており、セキュリティ意識の高いユーザーであっても、知らぬ間に罠にはまってしまう危険性があります。
メール、SNS、広告、XSS……攻撃はどこにでもある
特に問題なのは、「罠ページ=怪しいサイト」とは限らない点です。たとえば以下のようなケースが存在します。
- 広告ネットワークに正規登録された広告枠に攻撃用コードが仕込まれている
- 人気SNSで拡散されたリンクが攻撃ページにつながっている
- 企業のサポートフォーラムに貼られたリンクがCSRFを含んでいる
つまり、攻撃はインターネット上のどこにでも潜んでいる可能性があるのです。
✅ ポイント:
- 攻撃者は“ユーザーの行動”をトリガーにして攻撃を仕掛ける。
- メールやSNS、広告、XSSなどの手口は日常的に存在する。
- 「怪しいリンクを踏まなければ大丈夫」はもはや通用しない。
攻撃者はなぜ銀行の仕様を知っているのか?
CSRF攻撃が成功するためには、攻撃者が対象となるWebアプリケーションの仕様を把握している必要があります。実際、「攻撃者はどこからそんな情報を得るのか?」と疑問に思う方も多いでしょう。このセクションでは、攻撃者がどのようにして対象システムの内部仕様を知るのかを解説します。
Cookie名、POSTパラメータ、エンドポイントはすべて解析されている
Webアプリケーションの多くは、ユーザーのブラウザからアクセス可能な形で動作しています。つまり、攻撃者も「普通のユーザー」としてそのアプリケーションを観察できるということです。
攻撃者は、次のような手段で仕様を調査します。
- ブラウザの開発者ツールを使ってネットワーク通信を調査
- リクエストヘッダー、POSTパラメータ、エンドポイントURLなどを把握可能。
- HTMLソースやJavaScriptコードから内部仕様を読み取る
- フォームの構成や動的処理の流れ、使用ライブラリなども露見する。
- APIドキュメントやエラーメッセージから情報を抽出
- 公開されているAPI仕様書や、サーバーからのレスポンスを解析。
このように、“表に見えている情報”は攻撃者にとって宝の山です。特にセキュリティが軽視されたシステムでは、POSTパラメータが明示されていたり、CSRF対策がされていなかったりと、攻撃のきっかけとなるヒントが数多く存在します。
公開されたUIは攻撃者にも開かれている
UI(ユーザーインターフェース)は、正規ユーザーの利便性のために設計されていますが、その全てが攻撃者にも見えているという点は、開発者にとって盲点になりがちです。
たとえば、以下のような仕様は攻撃者にとって有益です。
- ボタンを押すと送金が実行される(パラメータやエンドポイントが明確)
- 入力フォームにパラメータ名がそのまま書かれている(例:
<input name="toAccount">
) - JavaScriptでAPIリクエストが組み立てられている(コードから仕様が丸見え)
このような情報を基に、攻撃者はCSRF攻撃用のリクエストを再現します。特別な侵入や解析技術を使わずとも、「誰でも見える範囲の情報」から攻撃が可能なのです。
✅ ポイント:
- 攻撃者は対象アプリに“ログインせずとも”多くの情報を取得できる。
- 開発者が意図しない情報も、UIやコードから漏れていることが多い。
- セキュリティの設計は「攻撃者視点」で考えることが重要。
現代でもCSRFは脅威となるのか?
セキュリティ対策が進化した現代のWebアプリケーションにおいて、CSRFは「すでに古い攻撃」と思われがちです。しかし実際には、依然としてCSRFは無視できないリスクとして存在しています。このセクションでは、なぜ現代でもCSRFが脅威とされるのか、そして盲点となりやすいケースについて説明します。
なぜ“減った”けれど“無視できない”のか
確かに、近年は多くのWebフレームワークやブラウザによってCSRF対策が標準化され、攻撃の成功率は過去よりも格段に低下しました。たとえば次のような仕組みが導入されています。
- ブラウザの
SameSite
属性によるCookieの送信制御 - フレームワーク側のCSRFトークン自動生成・検証
- CORSによるクロスドメイン制限の強化
とはいえ、完全に排除されたわけではありません。セキュリティ対策は開発者の実装次第であり、「うっかり対策を外してしまった」「旧来の仕様を引き継いでいる」といったケースでは、攻撃の糸口が依然として残っています。
現場でありがちな3つの盲点:社内システム/レガシー/API
- 社内システム
- 「外部からアクセスできないから大丈夫」と思われがちな社内向けWebアプリ。
- しかし、社員が業務中にうっかり罠ページを開くと、セッションが盗まれる可能性がある。
- VPN接続中であれば、社内システムにもアクセスできる状態となる。
- レガシーなシステム
- 過去に開発されたアプリケーションがCSRF対策を持たないまま稼働している。
- 特にPHPや古いJavaシステムでは、CSRFトークンの仕組みが未導入なケースが多い。
- API(特にREST API)
- フロントエンドと分離されたSPA(Single Page Application)で使われるREST APIは、しばしばCSRFトークンを使っていない。
- JSON APIはCSRF対象外と思われがちだが、認証がCookieベースである場合は同様に危険。
✅ ポイント:
- CSRFは「基本的なセキュリティが抜けている箇所」を突く攻撃。
- 最新技術を使っていても、対策がなければ成立する。
- 対策をしていたとしても「一部だけ漏れている」ことが最大の盲点となる。
エンジニアとしてCSRF対策をどう実装すべきか?
CSRFに対する防御は、単に「フレームワークがやってくれるから安心」と考えるだけでは不十分です。設計や構成によっては、フレームワーク任せにできない状況も多く存在します。このセクションでは、現場で実践すべきCSRF対策の代表的な方法と、それぞれの活用ポイントを解説します。
フレームワーク任せでは危険なケース
多くのWebフレームワーク(ASP.NET、Spring、Laravelなど)には、CSRF対策機能が標準で備わっています。たとえば、フォームに自動でCSRFトークンを埋め込む仕組みや、サーバー側でトークンを検証する機能です。
しかし、次のようなケースでは手動による追加対策が求められます。
- フロントエンドとバックエンドが分離されたSPA構成
- JavaScriptから直接APIを呼び出す(AJAX通信)
- 外部APIと連携するシナリオで、認証がCookieベース
こうした場合、CSRFトークンの送受信や、CORSポリシーの設定が正しく行われていないと、防御が破られてしまいます。
SameSite
属性の活用ポイント
SameSite
はCookieの属性の一つで、クロスサイトリクエストでのCookie送信を制限できます。以下の3つの設定があります。
SameSite=Strict
:完全にクロスサイトのリクエストではCookieを送らないSameSite=Lax
:GETリクエストのみCookieを送る(POSTは送らない)SameSite=None; Secure
:クロスサイトでもCookie送信可、ただしHTTPS必須
現在のブラウザはSameSite=Lax
をデフォルトとしているため、POSTベースのCSRF攻撃に対して一定の防御力がありますが、完全ではありません。可能であればStrict
、必要に応じてLax
やNone
を選択すべきです。
ASP.NET Coreの例:
services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.Strict;
});
CSRFトークンによる防御
トークンを生成し、フォームやヘッダーに埋め込む方法が最も一般的です。
- クライアント側でフォームにトークンを埋め込む
- サーバー側でリクエストヘッダーやボディ内のトークンを検証
ASP.NET MVCの例:
@using (Html.BeginForm()) {
@Html.AntiForgeryToken()
<!-- フォーム内容 -->
}
ASP.NET Coreの例(検証側):
[ValidateAntiForgeryToken]
public IActionResult SubmitForm(MyModel model)
{
// 正常な処理
}
Referer/Originチェックの導入
Referer
やOrigin
ヘッダーを確認することで、リクエストの出所を判定する方法です。ただし、これらのヘッダーはユーザー環境によってブロックされていることもあるため、補助的な対策として利用されます。
Referer
:直前のページURL(ただしHTTPS→HTTPなどでは消えることもある)Origin
:クロスドメイン時に付与されやすく、より信頼できる値
ASP.NET Coreの例(ミドルウェアでOrigin確認):
app.Use(async (context, next) =>
{
var origin = context.Request.Headers["Origin"].ToString();
if (!string.IsNullOrEmpty(origin) && !origin.StartsWith("<https://trusted-origin.com>"))
{
context.Response.StatusCode = 403;
await context.Response.WriteAsync("Forbidden");
return;
}
await next();
});
✅ ポイント:
- フレームワークを信頼しすぎず、構成ごとの対策が必要。
SameSite
、CSRFトークン、Refererチェックを組み合わせて多層防御。- API開発ではCORSやヘッダー制御にも注意。
まとめ:CSRFを“理解した気”で終わらせないために
CSRFは、「ユーザーがログインしている」ことを前提に成立する非常に巧妙な攻撃です。その成り立ちや仕組みを知ることで、対策の重要性が見えてきたのではないでしょうか。しかし、本当に重要なのは「理解したつもり」で終わらせず、実際の設計・実装にどう反映させるかです。
想定される攻撃シナリオを設計に反映できるか
セキュリティ設計において大切なのは、機能の要件と同じレベルで“攻撃者の視点”を組み込むことです。たとえば以下のような問いを開発初期に投げかけることで、CSRFリスクを事前に発見できます。
- この操作はユーザーの認証状態を前提としているか?
- セッションが有効な限り、自動で実行される処理があるか?
- 外部サイトからPOSTリクエストを受け付ける余地があるか?
これらを洗い出すことで、フォームやAPIにCSRFトークンを導入するか、SameSite
制御が適切か、Refererチェックを挟むべきかといった判断がしやすくなります。
「いつか来る攻撃」に備えた防御力の見直しを
CSRFは「ゼロデイ」のような目新しい脆弱性ではありませんが、その分、攻撃手法として確立され、模倣も容易です。過去の成功事例を参考に、攻撃者は“まだ対策されていないシステム”を狙います。
したがって、次のような継続的な見直しが求められます。
- 新たな開発では必ずCSRF対策を明示的に設計に含める
- レガシーシステムも定期的にセキュリティレビューを実施する
- 脆弱性スキャンや外部診断ツールでCSRFチェックを行う
また、エンジニア一人ひとりが「CSRFという攻撃が存在する」ことを念頭に置き、コードレビューやチーム内での共有にもこの視点を取り入れることが重要です。
✅ 最終チェックポイント:
- セッションを利用する処理はすべてCSRFリスクを検討
- トークン/Cookie設定/ヘッダー確認など多重防御を実施
- 「安心して放置」せず、定期的な再評価が必要
コメント