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 行目を「フォーム送信値」とみなし $_POSTparse_str で展開
  • $_POST['csrf_token']$_SESSION['csrf_token']hash_equals() で比較

このドリルの入力例 (tests/input.txt):

csrf_token=EXPECTED_TOKEN_ABC123

期待される出力:

OK

別パターン (攻撃想定): csrf_token=AAAANG

採点

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";
}
▶ 3v4l で実行

なぜ 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";
}