topic: session-cookie (session / cookie / login / CSRF) / ch05 — CSRF とトークン / 演習 02
📝 ドリル 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() は 一定時間で比較 する設計。
テストケース
標準入力
csrf_token=EXPECTED_TOKEN_ABC123
期待される出力
OK
📄 starter.php(雛形)
このコードから書き始めてください。
<?php
// 採点用スタブ: CLI で session_start() を安定動作させるための前処理
error_reporting(E_ERROR);
ini_set('session.use_cookies', '0');
ini_set('session.use_only_cookies', '0');
$tmpDir = sys_get_temp_dir() . '/dojo_l13_' . getmypid();
@mkdir($tmpDir, 0700, true);
session_save_path($tmpDir);
session_id('TESTSESSIONID');
@session_start();
// 採点用: stdin の 1 行目をフォーム送信として $_POST に展開
parse_str(trim(fgets(STDIN) ?: ''), $_POST);
// TODO: $_SESSION['csrf_token'] に固定値 'EXPECTED_TOKEN_ABC123' をセットする
// TODO: $_POST['csrf_token'] と $_SESSION['csrf_token'] を hash_equals() で比較する
// TODO: 一致なら "OK"、不一致なら "NG" を 1 行出力する
✅ 解答例を見る(自分で解いてから)
<?php
// 採点用スタブ: CLI で session_start() を安定動作させるための前処理
error_reporting(E_ERROR);
ini_set('session.use_cookies', '0');
ini_set('session.use_only_cookies', '0');
$tmpDir = sys_get_temp_dir() . '/dojo_l13_' . getmypid();
@mkdir($tmpDir, 0700, true);
session_save_path($tmpDir);
session_id('TESTSESSIONID');
@session_start();
// 採点用: stdin の 1 行目をフォーム送信として $_POST に展開
parse_str(trim(fgets(STDIN) ?: ''), $_POST);
// サーバー側の正解 (本物の Web では random_bytes で生成して保存しておく)
$_SESSION['csrf_token'] = 'EXPECTED_TOKEN_ABC123';
$sent = $_POST['csrf_token'] ?? '';
$saved = $_SESSION['csrf_token'] ?? '';
if (hash_equals($saved, $sent)) {
echo "OK\n";
} else {
echo "NG\n";
}