PHPでのCSRF対策についてアウトプットとして記事をまとめる。
1.初めに
CSRFとはWebアプリケーションに存在する脆弱性、もしくはその脆弱性を利用した攻撃方法のこと。
2.具体例
ワンタイムトークンを利用した対策を紹介する。
3. ワンタイムトークンとは
簡単に1度きりの合言葉を生成し、その合言葉が一致した場合のみログインを認め、それ以外は不正リクエストとして拒否する。
3.1 実装手順
- トークンによるリクエストチェック
- ユーザ操作によるリクエストの送信時にトークン(合言葉)をランダム生成する
- リクエストと共に、トークンをセッションに格納かつPOST送信する
- 受け取ったトークンとセッションに格納されているトークンを照合する
- 一致していれば正規リクエストとして正常処理し、
- 不一致なら不正リクエストとして拒否する
3.2 実装
<?php
//セッションの利用を開始
session_start();
//ワンタイムトークン生成
$token = openssl_random_pseudo_bytes(16);
$csrf_token = bin2hex($token);
// トークンをセッションに保存
$_SESSION['csrf_token'] = $csrf_token;
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ログイン</title>
</head>
<body>
<form action="login.php" method="post">
<div>
<label>
ユーザーID:
<input type="text" name="user_id">
</label>
</div>
<div>
<label>
パスワード:
<input type="password" name="pass">
</label>
</div>
<input type="hidden" name="csrf_token" value="<?php echo $csrf_token; ?>" />
<input type="submit" value="ログイン">
</form>
</body>
</html>
session_start();
$token = filter_input(INPUT_POST, 'csrf_token');
//トークンがない、もしくは一致しない場合、処理を中止
if (!isset($_SESSION['csrf_token']) || $token !== $_SESSION['csrf_token']) {
exit('不正なリクエスト');
}
unset($_SESSION['csrf_token']);
解説
解説すると、まず、ログインフォームのあるform.phpでトークンをセッションで保存するため、session_start()
をします。
次に、ワンタイムトークンを生成します。phpではランダムな暗号を生成する関数が用意されています。
用意されている関数参考までに
random_bytes()
openssl_random_pseudo_bytes()
公式によると、random_bytes()
を使用することを推奨しています。
ただ、どちらでも使用できます。
今回はopenssl_random_pseudo_bytes()
を使います。
引数には希望するバイト長を入れてください。
そして、ランダムな暗号を16進数に変換します。
bin2hex($token);
その値をセッションに保存します。
作成したワンタイムトークンを送信するために、<form>
中の<input>
で送信します。
この時に、inputの形式をhiddenにして送信します。
トークンを送信するinputタグはform内であればどこでも大丈夫です。
デベロッパーツールで確認すると、inputタグのvalue属性の値にトークンがあります。
送られたトークンはlogin.phpで処理されます。
ここからはlogin.phpの処理です。
まず、セッションを利用するのでsession_start()
を文頭に記述します。
その後送られてきたトークンがPOSTで送られてきたのがをチェックします。
トークンがセットされているか、セッションで保存したトークンと送られてきたトークンの値が合っているかを確認し、一致しない場合はエラーメッセージとともに、以降の処理を終了します。
一致する場合はunset($_SESSION['csrf_token'])
でセッションの割り当てを解除します。
セッションは使用を終えた時点で破棄するのが良いとされていますが、破棄をしなくてもエラーが起きるわけではない。
そして、その後の処理に移ります。
参考
こちらの記事とドキュメントを参考にしました。
最後に
初めて記事を書いたため、間違っている場合や見づらい場合が指摘していただけると幸いです。
これからもアウトプットとして書いていきますので、ご参考になれば幸いです。