Blind SQL Injection - 解説

このページでは、Blind SQL Injection(ブラインドSQLインジェクション)の仕組みと、その攻撃手法について解説します。 体験ページ blind-sql-i-vulnerable.php と合わせて学習を進めましょう。

Blind SQL Injectionとは?

通常のSQLインジェクションでは、エラーメッセージや直接的なデータ表示を通じてデータベースの情報を取得します。 しかし、Blind SQL Injectionでは、データベースからの直接的な出力が得られない状況で、 SQLクエリの真偽(True/False)に基づくアプリケーションの応答の違いを利用して、 データベースの情報を推測的に窃取する攻撃手法です。

例えば、特定の条件が真(True)の場合と偽(False)の場合で、ページの表示内容や応答速度がわずかに異なることを利用します。 これにより、一文字ずつ、あるいは一ビットずつ情報を特定していきます。

体験ページの脆弱性

体験ページ blind-sql-i-vulnerable.php では、ユーザーIDの入力欄がSQLインジェクションに対して脆弱に作られています。 入力されたユーザーIDは、以下のようなSQLクエリに直接埋め込まれます。

SELECT userid FROM users WHERE userid = '[ユーザー入力]'

このクエリは、ユーザーが存在するかどうかのみを判定し、その結果に応じて「ユーザーは存在します。」または「ユーザーは存在しません。」と表示します。 データベースのエラーメッセージや、ユーザー情報そのものは表示されません。

攻撃手法の例(PINの特定 - 2分探索)

体験ページのデータベース users テーブルには、userid の他に pin(4桁の暗証番号)フィールドが存在します。 この pin をBlind SQL Injectionで特定する例を考えます。 ここでは、より効率的な「2分探索」を用いた手法を紹介します。

PINは4桁の数字(0000~9999)で構成されています。 各桁の値を1つずつ総当たりで試す代わりに、2分探索を利用することで試行回数を大幅に減らすことができます。

PINの1文字目を特定する例

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回の試行で特定可能です。

この手法を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インジェクションの脆弱性を生み出すため、絶対に行わないでください。