topic: session-cookie (session / cookie / login / CSRF) / ch05
ch05 — CSRF とトークン
学習目標
- CSRF (Cross-Site Request Forgery) が何を狙う攻撃かを 1 行で言える
- ランダムなトークンを生成し
$_SESSION['csrf_token']に保存できる - 受け取ったフォームのトークンを
hash_equals()で安全に比較できる
所要時間
スライド 6 分 + ドリル 2 問 = 30 分
ドリル
| no | 内容 |
|---|---|
| 01 | トークンを生成して $_SESSION に保存 → 16 進文字数を出力 |
| 02 | stdin からトークン文字列を受け取り、$_SESSION 内の正解と一致したら OK、違ったら NG |
本物の Web で確認したい場合
cd topics/13-session-cookie/ch05-csrf/drill/01-token-generate/
php -S localhost:8000 answer.php
ブラウザでアクセスし、開発者ツールで PHPSESSID Cookie を見て、サーバー側で Session にトークンが保存されていることを確認 (実体はサーバー側ファイル)。
演習問題の詳細
この章の演習問題の内容を読めます。実際に手元で解くには教材リポジトリを clone してください。
ドリル 01 — CSRF トークンを生成して Session に保存
問題
CSRF 防御の出発点 — ランダムなトークンを生成して $_SESSION['csrf_token'] に保存 する処理を書いてください。
random_bytes(32)でランダム 32 バイトを取得bin2hex()で 16 進文字列 (64 文字) に変換$_SESSION['csrf_token']に保存- 保存したトークンの 長さ を
"length: 64"の形式で 1 行出力する (採点のためにトークン本体ではなく長さを出す)
期待される出力:
length: 64
採点
php scripts/grade.php topics/13-session-cookie/ch05-csrf/drill/01-token-generate/
ヒント
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
echo "length: " . strlen($_SESSION['csrf_token']) . "\n";本物の Web で確認したい場合
cd topics/13-session-cookie/ch05-csrf/drill/01-token-generate/
php -S localhost:8000 answer.php
ブラウザのソース表示で hidden input にトークンが入っていることを確認 (本ドリルでは form を出力しないが、本物の Web では <input type="hidden" name="csrf_token" value="..."> を出す)。
ドリル 02 — CSRF トークンを検証
問題
サーバー側が保持しているトークンと、フォーム送信で受け取ったトークンを hash_equals() で比較し、一致すれば OK、違えば NG を 1 行出力してください。
- サーバー側の正解トークンは固定値
EXPECTED_TOKEN_ABC123(採点用に決め打ち) - 起動時に
$_SESSION['csrf_token'] = 'EXPECTED_TOKEN_ABC123'をセットしておくこと - 標準入力 1 行目を「フォーム送信値」とみなし
$_POSTにparse_strで展開 $_POST['csrf_token']と$_SESSION['csrf_token']をhash_equals()で比較
このドリルの入力例 (tests/input.txt):
csrf_token=EXPECTED_TOKEN_ABC123
期待される出力:
OK
別パターン (攻撃想定): csrf_token=AAAA → NG
採点
php scripts/grade.php topics/13-session-cookie/ch05-csrf/drill/02-token-verify/
ヒント
$_SESSION['csrf_token'] = 'EXPECTED_TOKEN_ABC123';
parse_str(trim(fgets(STDIN) ?: ''), $_POST);
$sent = $_POST['csrf_token'] ?? '';
$saved = $_SESSION['csrf_token'] ?? '';
if (hash_equals($saved, $sent)) {
echo "OK\n";
} else {
echo "NG\n";
}なぜ hash_equals か
== や === は比較に掛かる時間が値で変わる (タイミング攻撃の足掛かり)。hash_equals() は 一定時間で比較 する設計。
演習問題(2問)
サイト内で問題文・雛形・解答例を確認できます。実際に手元で解くには教材リポジトリを clone してください。