PHP の CSRF 対策(トークンの活用)を完全解説!初心者向けガイド
新人
「CSRF って何ですか?」
先輩
「CSRF は『Cross-Site Request Forgery(クロスサイトリクエストフォージェリ)』の略で、日本語では『クロスサイトリクエスト偽造』と言うよ。」
新人
「どんな攻撃なんですか?」
先輩
「簡単に言うと、悪意のあるウェブサイトが、ユーザーの知らない間に別のウェブサイトにリクエストを送ってしまう攻撃だよ。」
新人
「えっ!?それってどういう仕組みなんですか?」
先輩
「じゃあ、具体的に CSRF の仕組みを説明するね!」
1. CSRF とは?
CSRF(クロスサイトリクエストフォージェリ)は、悪意のある第三者が、ユーザーの知らない間に別のウェブサイトに対して不正なリクエストを送る攻撃のことです。
たとえば、ログイン中の銀行サイトで、ユーザーが送金操作をしないにもかかわらず、攻撃者が仕掛けた偽のページにアクセスするだけで勝手に送金されてしまうことがあります。
CSRF の特徴
- 攻撃者がユーザーの意図しないリクエストを送信する
- ユーザーが意図せず操作を行ってしまう
- 被害者がログインしている状態で攻撃が成立しやすい
2. CSRF 攻撃の仕組み
CSRF 攻撃は、以下のような流れで行われます。
攻撃の流れ
- ユーザーが銀行サイトにログインする
- 攻撃者が悪意のあるサイトを作成し、ユーザーを誘導する
- 悪意のあるサイトがユーザーのブラウザを利用して銀行サイトにリクエストを送信
- 銀行サイトは、ログイン済みのユーザーからのリクエストと判断し、処理を実行
具体的な攻撃例
例えば、攻撃者が以下のような HTML を作成し、ユーザーをこのページにアクセスさせたとします。
<form action="https://bank.example.com/transfer" method="post">
<input type="hidden" name="to_account" value="999999">
<input type="hidden" name="amount" value="100000">
<input type="submit" value="クリックでプレゼント">
</form>
このフォームがユーザーのブラウザで自動送信されるように細工されていると、ログイン済みの銀行サイトに対して、意図しない送金リクエストが送られてしまいます。
CSRF の危険性
この攻撃によって、次のような被害が発生する可能性があります。
- 勝手に送金されてしまう
- パスワードが変更されてしまう
- アカウントが削除される
このような攻撃を防ぐために、CSRF 対策をしっかりと行う必要があります。次回は、具体的な対策方法として「トークンを活用した CSRF 対策」について解説します。
3. CSRF 対策の基本(トークンの仕組みとは?)
CSRF 攻撃を防ぐためには、「CSRF トークン」という仕組みを利用します。
CSRF トークンとは、一意なランダム文字列を発行し、それをフォームに埋め込むことで、リクエストが正規のものであるかを確認する仕組みです。
CSRF トークンの仕組み
- サーバー側でランダムな CSRF トークンを生成し、セッションに保存する。
- フォームの hidden フィールドに CSRF トークンを埋め込む。
- フォームが送信された際、送信されたトークンとセッションのトークンを比較し、一致すればリクエストを受け付ける。
なぜ CSRF トークンが有効なのか?
攻撃者が勝手にリクエストを送ることはできても、サーバーに保存されたトークンを知ることはできません。そのため、トークンが一致しないリクエストはブロックされる仕組みになっています。
4. CSRF トークンを使ったフォームの保護(基本的な実装方法)
実際に CSRF トークンをフォームに埋め込み、安全にリクエストを処理する方法を見ていきましょう。
フォームの作成
フォームに CSRF トークンを埋め込むため、まずは PHP でトークンを生成し、セッションに保存します。
<?php
session_start();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
?>
次に、フォームの hidden フィールドに CSRF トークンを埋め込みます。
<form action="process.php" method="post">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token'], ENT_QUOTES, 'UTF-8'); ?>">
<label for="name">名前:</label>
<input type="text" id="name" name="name">
<button type="submit">送信</button>
</form>
このようにすることで、ユーザーがフォームを送信するたびに、サーバーが発行した CSRF トークンを一緒に送信するようになります。
5. CSRF トークンの生成と検証(PHP の具体的なコード例)
送信された CSRF トークンをサーバーで検証し、一致しない場合はリクエストを拒否する処理を追加しましょう。
トークンの検証処理
送信された CSRF トークンと、セッションに保存されたトークンを比較します。
<?php
session_start();
if ($_SERVER["REQUEST_METHOD"] == "POST") {
if (!isset($_POST["csrf_token"]) || $_POST["csrf_token"] !== $_SESSION["csrf_token"]) {
die("不正なリクエストです!");
}
echo "リクエストが正常に処理されました!";
}
?>
CSRF トークンのリセット
CSRF トークンは一度使ったら無効にすることで、セキュリティをさらに強化できます。リクエストが成功したら、新しいトークンを発行しましょう。
<?php
if ($_SERVER["REQUEST_METHOD"] == "POST") {
if (!isset($_POST["csrf_token"]) || $_POST["csrf_token"] !== $_SESSION["csrf_token"]) {
die("不正なリクエストです!");
}
echo "リクエストが正常に処理されました!";
// 新しいトークンを発行
$_SESSION["csrf_token"] = bin2hex(random_bytes(32));
}
?>
CSRF トークンの有効期限を設定
トークンに有効期限を設定することで、セキュリティをさらに強化できます。
<?php
session_start();
if (!isset($_SESSION["csrf_token"]) || time() - $_SESSION["csrf_token_time"] > 300) {
$_SESSION["csrf_token"] = bin2hex(random_bytes(32));
$_SESSION["csrf_token_time"] = time();
}
?>
このコードでは、CSRF トークンの有効期限を 5 分(300 秒)に設定しています。
次回は、CSRF トークンの有効期限や、他の CSRF 対策方法について詳しく解説します。
6. CSRF トークンの有効期限とセッション管理(より安全な実装)
CSRF トークンは一度発行したら永続的に使用できるわけではなく、一定時間で無効化するのが推奨されています。ここでは、有効期限を設けてより安全な実装を行う方法を紹介します。
トークンの有効期限を設定する理由
- セッションが長時間続いた場合でも、古いトークンを使った攻撃を防ぐため
- 1回のフォーム送信ごとに新しいトークンを発行し、再利用を防ぐため
トークンの有効期限を設定する方法
PHP で CSRF トークンの有効期限を 10 分(600 秒)に設定する例を示します。
<?php
session_start();
// トークンの有効期限(秒)
define("CSRF_TOKEN_EXPIRY", 600);
if (!isset($_SESSION["csrf_token"]) || time() - $_SESSION["csrf_token_time"] > CSRF_TOKEN_EXPIRY) {
$_SESSION["csrf_token"] = bin2hex(random_bytes(32));
$_SESSION["csrf_token_time"] = time();
}
// トークンをフォームに埋め込む
?>
フォームに CSRF トークンを埋め込む
<form action="process.php" method="post">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token'], ENT_QUOTES, 'UTF-8'); ?>">
<label for="name">名前:</label>
<input type="text" id="name" name="name">
<button type="submit">送信</button>
</form>
トークンの検証時に有効期限をチェック
<?php
session_start();
if ($_SERVER["REQUEST_METHOD"] == "POST") {
if (!isset($_POST["csrf_token"]) || !isset($_SESSION["csrf_token"]) || $_POST["csrf_token"] !== $_SESSION["csrf_token"]) {
die("不正なリクエストです!");
}
// 有効期限のチェック
if (time() - $_SESSION["csrf_token_time"] > CSRF_TOKEN_EXPIRY) {
die("トークンの有効期限が切れています。再度フォームを送信してください。");
}
echo "リクエストが正常に処理されました!";
// 新しいトークンを発行
$_SESSION["csrf_token"] = bin2hex(random_bytes(32));
$_SESSION["csrf_token_time"] = time();
}
?>
このコードでは、トークンの有効期限を超えている場合、フォームの再送信を促すようにしています。
7. 他の CSRF 対策方法(Referer チェックや SameSite 属性の活用)
CSRF トークン以外にも、CSRF 攻撃を防ぐための手法があります。その中でも「Referer チェック」と「SameSite 属性」を活用した対策を紹介します。
Referer チェック
Referer チェックとは、リクエストの送信元 URL を確認し、正規のサイトからのリクエストかどうかを判定する方法です。
<?php
if ($_SERVER["REQUEST_METHOD"] == "POST") {
if (!isset($_SERVER["HTTP_REFERER"]) || strpos($_SERVER["HTTP_REFERER"], "https://example.com") !== 0) {
die("不正なリクエストです!");
}
}
?>
この方法では、リクエストの Referer ヘッダーが正しいサイトからのものであることを確認します。ただし、ブラウザやネットワーク環境によっては Referer ヘッダーが送信されない場合があるため、単独の対策としては不十分です。
SameSite 属性の活用
SameSite 属性を設定することで、異なるサイトからのリクエスト時に Cookie を送信しないようにできます。これにより、CSRF 攻撃のリスクを軽減できます。
<?php
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'domain' => '',
'secure' => true,
'httponly' => true,
'samesite' => 'Strict'
]);
session_start();
?>
この設定では、SameSite 属性を「Strict」にすることで、他のサイトからのリクエスト時に Cookie が送信されなくなります。
8. PHP の CSRF 対策を学ぶためのおすすめの方法
CSRF 対策をより深く理解し、実践できるようになるための方法を紹介します。
1. PHP の公式ドキュメントを読む
PHP の公式サイトには、セキュリティに関する情報が掲載されています。最新の対策方法を学ぶために、定期的に確認しましょう。
2. OWASP のセキュリティガイドを参照する
OWASP(Open Web Application Security Project)は、ウェブアプリケーションのセキュリティに関するベストプラクティスを提供しています。
3. CSRF 脆弱性のあるサンプルアプリを試してみる
実際に CSRF 攻撃の仕組みを理解するために、意図的に脆弱なアプリケーションを使ってテストしてみるのも有効です。
例えば、以下のような手順で試せます。
- CSRF トークンなしでフォームを作成する
- 別の HTML ページから勝手にリクエストを送る
- CSRF 対策を適用し、リクエストがブロックされることを確認する
4. セキュリティテストツールを活用する
CSRF を含むウェブアプリケーションの脆弱性をチェックできるツールもあります。
- OWASP ZAP(Zed Attack Proxy)
- Burp Suite
これらのツールを活用し、自分のアプリケーションが安全かどうか確認してみましょう。
これで PHP の CSRF 対策に関する解説は終了です。CSRF は対策を怠ると大きなリスクになるため、しっかりと理解し、実装していきましょう!