このページでは、Blind SQL Injection(ブラインドSQLインジェクション)の仕組みと、その攻撃手法について解説します。 体験ページ blind-sql-i-vulnerable.php と合わせて学習を進めましょう。
通常のSQLインジェクションでは、エラーメッセージや直接的なデータ表示を通じてデータベースの情報を取得します。 しかし、Blind SQL Injectionでは、データベースからの直接的な出力が得られない状況で、 SQLクエリの真偽(True/False)に基づくアプリケーションの応答の違いを利用して、 データベースの情報を推測的に窃取する攻撃手法です。
例えば、特定の条件が真(True)の場合と偽(False)の場合で、ページの表示内容や応答速度がわずかに異なることを利用します。 これにより、一文字ずつ、あるいは一ビットずつ情報を特定していきます。
体験ページ blind-sql-i-vulnerable.php では、ユーザーIDの入力欄がSQLインジェクションに対して脆弱に作られています。
入力されたユーザーIDは、以下のようなSQLクエリに直接埋め込まれます。
SELECT userid FROM users WHERE userid = '[ユーザー入力]'
このクエリは、ユーザーが存在するかどうかのみを判定し、その結果に応じて「ユーザーは存在します。」または「ユーザーは存在しません。」と表示します。 データベースのエラーメッセージや、ユーザー情報そのものは表示されません。
体験ページのデータベース users テーブルには、userid の他に pin(4桁の暗証番号)フィールドが存在します。
この pin をBlind SQL Injectionで特定する例を考えます。
ここでは、より効率的な「2分探索」を用いた手法を紹介します。
PINは4桁の数字(0000~9999)で構成されています。 各桁の値を1つずつ総当たりで試す代わりに、2分探索を利用することで試行回数を大幅に減らすことができます。
user001 のPINの1文字目を特定する場合を考えます。
まず、1文字目が 5 より大きいかどうかを判定します。
入力例 (1文字目が5より大きいか?):
user001' AND SUBSTR((SELECT pin FROM users WHERE userid = 'user001'), 1, 1) > '5
この入力により、実行されるSQLクエリは以下のようになります。
SELECT userid FROM users WHERE userid = 'user001' AND SUBSTR((SELECT pin FROM users WHERE userid = 'user001'), 1, 1) > '5'
もし「ユーザーは存在します。」と表示されれば、1文字目は 6, 7, 8, 9 のいずれかです。
もし「ユーザーは存在しません。」と表示されれば、1文字目は 0, 1, 2, 3, 4, 5 のいずれかです。
このようにして、検索範囲を半分に絞り込むことができます。
この操作を繰り返すことで、少ない試行回数で正確な1文字目を特定できます。
例えば、1文字目が 0 から 9 の場合、最大4回の試行で特定可能です。
> '5'> '7' または > '2'> '8' または > '6' または > '3' または > '1'この手法をPINの各桁(2文字目、3文字目、4文字目)に適用することで、効率的に4桁のPIN全体を特定することが可能です。
Blind SQL Injectionを含むあらゆるSQLインジェクション攻撃への最も効果的な対策は、 プリペアドステートメント(Prepared Statement)を使用することです。 プリペアドステートメントは、SQLクエリの構造とユーザー入力を明確に分離し、 入力値をデータとしてのみ扱い、SQLの一部として解釈されることを防ぎます。
// プリペアドステートメントの例
$stmt = $pdo->prepare("SELECT userid FROM users WHERE userid = :userid");
$stmt->bindParam(':userid', $userid);
$stmt->execute();
$userExists = $stmt->fetch(PDO::FETCH_ASSOC);
注意: ユーザーからの入力値を直接SQLクエリに連結するような実装は、 SQLインジェクションの脆弱性を生み出すため、絶対に行わないでください。