結論(POSTフォームのCSRF対策兆候)
POSTフォームのCSRF対策兆候とは、POSTで送信するフォームのHTMLに、csrf_token・_wpnonce・authenticity_tokenのようなCSRFトークンらしき項目が含まれているかという外形的な手がかりです。CSRFは、ログイン中の利用者に悪意あるページから勝手にリクエストを送らせ、意図しない登録・変更を実行させる攻撃で、予測不能なトークンをサーバー側で照合するのが代表的な対策です。外からはサーバー側の検証まで分からないため、トークンが見つからなくても×ではなく△(要確認)と判定します。
この記事のポイント
- CSRFは、ブラウザが別サイトからの送信にも送信先のCookieを付ける性質を悪用する攻撃です。
- 代表的な対策は、推測できないCSRFトークンをhidden項目で埋め込み、サーバー側で照合する方法です。
- Fetch Metadata・Origin検証・SameSite Cookieは多層防御として有効ですが、SameSite=Laxだけでは不十分な場合があります。
- セキュリティAIチェッカーは、POSTフォームがありトークンらしき文字列があれば○、なければ△(重み3)、POSTフォームがなければ対象外とします。
- ○でもサーバー側で検証していなければ意味がないため、実装の確認は別途必要です。
| 診断項目名 | POSTフォームのCSRF対策兆候 |
|---|---|
| 項目ID | forms.csrf-signal |
| 分類 | フォーム・パスワード入力 |
| 重大度・配点の重み | 重要(high) 重み 3 |
| 関連する標準・ガイドライン | OWASP ASVS 5.0.0 V3.5(3.5.1 L1:CORSプリフライトに頼らない場合、機微な機能へのリクエストがアプリ自身から発したものか検証する/3.5.3 L1:機微な機能には POST 等の非安全メソッドを使う)、ASVS 3.3.2(L2:SameSite Cookie)、OWASP WSTG v4.2 WSTG-SESS-05(CSRF のテスト)、CWE-352、IPA「安全なウェブサイトの作り方」1.6 CSRF |
POSTフォームのCSRF対策兆候とは
POSTフォームのCSRF対策兆候とは、POST で送信するフォームの HTML に、CSRF トークンらしき項目(csrf_token、_wpnonce、authenticity_token など)が含まれているかという、外から確認できる手がかりのことです。
<form method="post" action="/contact/send.php">
<input type="hidden" name="csrf_token" value="(推測できないランダムな値)">
</form>
CSRF(クロスサイト・リクエスト・フォージェリ)は、利用者がログインしたまま別の悪意あるページを開いたときに、そのページから自治体システムへ勝手にリクエストを送らせ、本人の意図しない登録・変更・送信を実行させる攻撃です。代表的な対策は、フォームに推測できない秘密のトークン(CSRFトークン)を hidden 項目として埋め込み、サーバー側で照合する方法です。
この診断項目は、トップページの HTML に POST フォームがある場合に、CSRF トークンらしき文字列が HTML 内に見えるかどうかを確認します。外から見えるのは「兆候」だけであり、実際にサーバー側で検証しているかまでは分からないため、見つからない場合も「× 要改善」ではなく「△ 要確認」にとどめています。
CSRF攻撃の仕組みと対策の考え方
攻撃の仕組み
ブラウザは、別サイトから送信されたフォームでも、送信先ドメインの Cookie を(SameSite 属性の設定次第で)付けて送ります。サーバーが「Cookie のセッションが有効なら処理する」だけの実装だと、攻撃者のページに置いた自動送信フォームで、ログイン中の職員や住民の権限による処理が実行されます。
主な対策(OWASP CSRF Prevention Cheat Sheet)
| 対策 | 内容と注意点 |
|---|---|
| シンクロナイザートークン方式 | サーバーがセッションごと(または要求ごと)に予測不能なトークンを生成し、hidden 項目やリクエストヘッダーで送らせて照合する。トークンをURL(GETパラメータ)に入れると履歴やログ経由で漏れるため避ける |
| 署名付きダブルサブミットCookie | セッションに結び付けたHMAC署名付きトークンを Cookie とリクエスト値の両方で送り照合する。セッションに結び付けない単純な署名では Cookie 注入に弱い |
| Fetch Metadata | Sec-Fetch-Site: cross-site の POST/PUT/PATCH/DELETE を既定で拒否する。ヘッダーを送らない古いブラウザ向けに、Origin 検証へのフォールバックが必須とされている |
| Origin / Referer の検証 | ブラウザが改変を許さないヘッダーで送信元を確認する。補完的な層として有効 |
| SameSite Cookie | 多層防御として有効。ただし Lax は非安全メソッドしか防がず、GET で状態変更する実装は守れない。また SameSite は「サイト(登録可能ドメイン)」単位なので、同じ登録可能ドメインの別サブドメインからの要求は防げない |
| ログインCSRF対策 | ログイン前のフォームにもトークンが必要で、ログイン成功時にはセッションIDを再生成する |
OWASP ASVS 5.0.0 の要件
ASVS 5.0.0 V3.5 は、3.5.1(L1)で「CORS プリフライトに頼らない場合、機微な機能へのリクエストがアプリ自身から発したものか検証する」こと、3.5.3(L1)で「機微な機能には POST 等の非安全メソッドを使う」ことを求めています。SameSite Cookie は 3.3.2(L2)の要件です。テスト手順としては OWASP WSTG v4.2 の WSTG-SESS-05(CSRF のテスト)があり、弱点としては CWE-352 に分類されます。
IPA の整理
IPA「安全なウェブサイトの作り方」(1.6 CSRF)は、根本的解決として次の3つを挙げています。
- POST でアクセスさせ、hidden パラメータに秘密情報を入れて照合する。
- 処理実行前にパスワード再入力を求める。
- Referer が正しいリンク元か確認する。
また、保険的対策として、重要操作時の登録メールアドレスへの通知を挙げています。
CSRF対策についてよくある誤解
- 「POST にすれば CSRF は防げる」:POST フォームも外部サイトから自動送信できます。POST はトークンを本文で安全に運ぶための前提にすぎません。
- 「HTTPS だから大丈夫」:CSRF は通信の暗号化とは無関係です。
- 「トークンが HTML にあるから安全」:サーバー側で検証していなければ意味がありません。この項目が「○ 問題なし」でも実装の確認は必要です。
Cookie 側の対策は「CookieのSameSite属性」の項目で別に評価しています。このページはフォームの HTML に現れる CSRF トークンの兆候に絞って解説します。
POSTフォームのCSRF対策兆候の診断基準(セキュリティAIチェッカー)
取得対象と方法
- 最終到達ページ(トップページ)の HTML 本文(最大2MB)を解析します。
<form>開始タグのうち、method="post"(大文字小文字・引用符の有無を問わない)を持つものがあるかを調べます。- HTML 全体に、代表的な CSRF トークン名の文字列が含まれるかを調べます。対象は次のとおりです。
| 対象の文字列 | 主な出力元 |
|---|---|
csrf、xsrf | Django の csrfmiddlewaretoken、Laravel 等の csrf-token メタ要素、Spring の _csrf を含む |
authenticity_token | Rails |
__RequestVerificationToken | ASP.NET |
_wpnonce | WordPress |
form_token | Drupal |
name="_token"/name="form[_token]" | Laravel・Symfony が出力する hidden 項目(name 属性の値が _token、または フォーム名[_token] の形) |
判定条件
| 判定 | 条件 | 得点への影響 |
|---|---|---|
| ○ 問題なし(pass) | POST フォームがあり、CSRF トークンらしき文字列を確認 | 重み3 × 1.00 |
| △ 要確認(warn) | POST フォームがあるが、トークンらしき文字列を確認できない | 重み3 × 0.45 |
| 対象外(na) | POST フォームがない | 採点から除外 |
この項目に「× 要改善(fail)」はありません。HTML の外側(Cookie、ヘッダー検証、サーバー処理)で対策している可能性を否定できないためです。
自治体サイトでの判定例
| 例 | 判定 |
|---|---|
| トップページのフォームがサイト内検索(GET)だけ | 対象外 |
<form method="post"> に <input type="hidden" name="csrf_token" value="..."> | ○ 問題なし |
WordPress のコメント・問い合わせフォームで _wpnonce を含む | ○ 問題なし |
Laravel の @csrf が出力した <input type="hidden" name="_token" value="..."> | ○ 問題なし |
| POST の問い合わせフォームがあり、トークンらしき項目がない | △ 要確認 |
自治体トップページのフォームは多くが GET の検索フォームのため、対象外が一般的です。問い合わせ・申請・予約のフォームページは個別に診断してください。
診断の限界
- 文字列の有無だけを見るため、無関係な JavaScript 内の「csrf」という文字でも「○ 問題なし」になります。
- 独自名のトークン(例:
ticket、hash、my_token)は検出できず「△ 要確認」になります。_tokenはname属性の値としてだけ認めるため、id="_token"や JavaScript の変数名だけでは兆候になりません。 <button formmethod="post">だけで POST にしているフォームは POST フォームとして数えません。- Fetch Metadata、Origin 検証、SameSite=Strict のみで防御しているサイトは、安全でも「△ 要確認」になります。
- トークンの予測不能性、セッションとの結び付き、検証の有無は外部から判定できません。
CSRF対策がないとどんなリスクがあるか
- なりすまし申請・設定変更:電子申請や施設予約で、ログイン中の住民の名義で予約・取消・登録情報変更が行われます。
- 管理画面の乗っ取り:CMS にログイン中の職員が悪意あるページを開くと、記事改ざん、管理者ユーザー追加、設定変更が行われ、公式サイトの改ざんに発展します。
- 問い合わせフォームの悪用:トークンも送信回数制限もないフォームは、外部から大量送信や第三者宛ての迷惑送信の踏み台にされやすくなります。
CSRF は CWE-352 として MITRE の「CWE Top 25」に継続して掲載されている代表的な弱点です。
CSRF対策の実装方法(Laravel・Django・Rails・ASP.NET・WordPress・PHP・Next.js)
1. フレームワーク標準の CSRF 対策を有効にする
| フレームワーク | 設定 |
|---|---|
| Laravel | web ミドルウェアで既定有効。Blade のフォームに @csrf を入れる |
| Django | CsrfViewMiddleware を有効にし、テンプレートに {% csrf_token %} |
| Rails | protect_from_forgery(既定有効)と form_with |
| ASP.NET Core | [ValidateAntiForgeryToken] とタグヘルパー |
| WordPress | wp_nonce_field() を出力し、受信側で check_admin_referer() / wp_verify_nonce() |
2. 素の PHP の例
<?php
session_start();
if (empty($_SESSION['csrf'])) {
$_SESSION['csrf'] = bin2hex(random_bytes(32));
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$sent = isset($_POST['csrf_token']) ? $_POST['csrf_token'] : '';
if (!hash_equals($_SESSION['csrf'], $sent)) {
http_response_code(403);
exit('不正なリクエストです。画面を再読み込みしてやり直してください。');
}
// 処理本体
}
?>
<form method="post" action="/contact/send.php">
<input type="hidden" name="csrf_token"
value="<?= htmlspecialchars($_SESSION['csrf'], ENT_QUOTES, 'UTF-8') ?>">
</form>
random_bytes と hash_equals は PHP 7 で利用できます。トークンはセッションに保存し、送信値と一致しない POST は処理せずに 403 を返します。
3. Fetch Metadata による多層防御(Next.js Route Handler の例)
export async function POST(request: Request) {
const site = request.headers.get("sec-fetch-site");
if (site && site !== "same-origin" && site !== "none") {
return new Response("Forbidden", { status: 403 });
}
// さらに Origin ヘッダーが自サイトか確認し、トークンも照合する
}
4. Cookie を SameSite 付きにする
# Apache 2.4.x(mod_headers)。PHP なら session.cookie_samesite = "Lax" を推奨
# Cookie の出し方で入るヘッダー表が異なる(CGI・mod_proxy_fcgi は always 側)ため両方に書く
Header edit Set-Cookie "^(?!.*;\s*(?i:SameSite)=)(.*)$" "$1; SameSite=Lax"
Header always edit Set-Cookie "^(?!.*;\s*(?i:SameSite)=)(.*)$" "$1; SameSite=Lax"
proxy_cookie_flags ~ samesite=lax; # nginx 1.19.3 以降。proxy_pass で中継する応答の Cookie が対象
IIS では URL Rewrite の outbound ルールや、アプリ側(ASP.NET の SameSiteMode.Lax)で設定します。
5. 設定後の確認方法
curl -s https://www.city.example.lg.jp/contact/ | grep -ioE '<input[^>]*type=["'\'']?hidden[^>]*>'
# トークンなしの送信が拒否されるか(自組織のテスト環境で実施)
curl -s -o /dev/null -w '%{http_code}\n' -X POST -d 'name=test' https://test.example.lg.jp/contact/send.php
本番環境への試験送信は業務影響があるため、テスト環境で行ってください。トークンなしの送信が 403 などで拒否されれば、サーバー側で検証している目安になります(拒否の理由が別の入力チェックでないかは、応答内容やログで確かめてください)。
サイトを壊さないための注意点
- 静的HTMLキャッシュ(CDN・CMSの静的書き出し)にトークンを焼き込むと、全員が同じトークンを共有したり期限切れで送信できなくなったりします。トークンを含むページはキャッシュ対象外にするか、JavaScript で取得する方式にします。
- 長時間入力するフォームはセッション切れでトークン不一致になりやすいため、分かりやすいエラーと再入力手順を用意します。
SameSite=Strictは外部サイトからのリンク遷移時にログイン状態が外れるため、電子申請などではLaxから始めます。- 外部決済などのクロスサイトPOSTを受けるエンドポイントは、Fetch Metadata 拒否の例外として個別に設計します。
よくある質問(FAQ)
CSRFとはどんな攻撃ですか?
利用者がログインしたまま別の悪意あるページを開いたときに、そのページから自治体システムへ勝手にリクエストを送らせ、本人の意図しない登録・変更・送信を実行させる攻撃です。サーバーがCookieのセッションが有効かどうかだけで処理していると成立します。
POSTにすればCSRFは防げますか?
防げません。POSTフォームも外部サイトから自動送信できます。POSTはトークンを本文で安全に運ぶための前提にすぎず、トークンの照合などの対策が別に必要です。
HTTPSにしていればCSRFは起きませんか?
起きます。CSRFは通信の暗号化とは無関係で、HTTPSのサイトでも対策が必要です。
SameSite=Laxを設定すればCSRFトークンは不要ですか?
多層防御として有効ですが、単独では不十分な場合があります。LaxはGETで状態を変更する実装を守れず、SameSiteは登録可能ドメイン単位のため、同じドメインの別サブドメインからの要求は防げません。
独自の名前のトークンを使っていますが△になりました。脆弱なのですか?
脆弱とは限りません。ticketやmy_tokenのような独自名のトークンや、Fetch Metadata・Origin検証・SameSite=Strictだけで防御しているサイトは、安全でも△になります。サーバー側で検証していることを実装で確認してください。
トップページの診断が「対象外」でした。問い合わせフォームはどう確認しますか?
自治体トップページのフォームは多くがGETのサイト内検索のため、対象外が一般的です。問い合わせ・申請・予約のフォームページのURLを個別に診断してください。
この項目を含む127項目を、URLを入れるだけで無料診断します。