CSRFとは
- Webアプリケーションの「重要な処理(パスワードやメールアドレスの変更、決済、口座振込など)」は、ユーザが意図したリクエストか確認が必要。
- 確認が抜けていると、罠サイトを閲覧しただけでユーザのブラウザから勝手に「重要な処理」が実行させられてしまう。
- リクエスト強要とも。
以下のようなHTMLがあったとします。
<!-- // 正規のサイト -->
<form action="http://hoge.com/password_change.php" method="post">
<input type="text" name="password">
<input type="submit" value="変更する">
</form>
それに対して以下のようなHTMLを用意します。
<!-- // 罠サイト -->
<body onload="document.forms[0].submit()">
<form action="http://hoge.com/password_change.php" method="post">
<input type="hidden" name="password" value="cracked">
</form>
</body>
password_change.php
で確認の処理が抜けていると、パスワードが勝手に変更させられてしまう。
CSRFの対策
inputのhiddenにトークンを埋め込みます。
<?php
session_start();
if (empty($_SESSION['token'])) {
$token = bin2hex(openssl_random_pseudo_bytes(24));
$_SESSION['token'] = $token;
} else {
$token = $_SESSION['token'];
}
?>
<form action="http://hoge.com/password_change.php" method="post">
<input type="hidden" name="token" value="<?php echo htmlspecialchars($token, ENT_COMPAT, 'UTF-8'); ?>">
<input type="text" name="password">
<input type="submit" value="変更する">
</form>
パスワードの変更受付処理でトークンを検証します。
session_start();
$token = filter_input(INPUT_POST, 'token');
if (empty($_SESSION['token']) || $token !== $_SESSION['token']) {
die('エラー');
}
- 他にも「パスワードの再入力を求める」や「リファラをチェックする」といった対策がある。
- トークンの埋め込みが最も一般的。
CSRF(Web APIの場合)
こんなAPIがあったとします。
session_start();
if (empty($_SESSION['uid'])) {
header('HTTP/1.1 403 Forbidden');
die('ログインしてください');
}
$req = json_decode(file_get_contents('php://input'));
// DBにpasswordをセットする疑似コード
$this->db->set('password', ['uid' => $_SESSION['uid'], 'password' => $req->password]);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['result' => 'OK']);
それに対して以下のようなJavaScriptを実行させます。
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://api.hoge.com/passward");
xhr.withCredentials = true;
xhr.send('{"password": "cracked"}');
APIで確認の処理が漏れていると、パスワードが勝手に変更させられてしまいます。
CSRFの対策(Web APIの場合)
ログイン(uidを取得)処理でトークンも生成。
if ($_SESSION['token']) {
$token = bin2hex(openssl_random_pseudo_bytes(24));
$_SESSION['token'] = $token;
}
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['uid' => $_SESSION['uid'], 'token' => $_SESSION['token']]);
APIでトークンを検証する。
session_start();
if (empty($_SESSION['uid'])) {
header('HTTP/1.1 403 Forbidden');
die('ログインしてください');
}
$token = $_SERVER['HTTP_X_CSRF_TOKEN'];
if (empty($token) || $token !== $_SESSION['token']) {
header('HTTP/1.1 403 Forbidden');
die('エラー');
}
...
まとめ
- CSRFは攻撃者が罠サイトを作成し、ユーザーに罠サイトを閲覧させることで発動する。
- HTMLフォームの場合もWeb APIの場合もトークンを生成し検証する方法が最も安全。