ブラインドSQLi:沈黙するデータベースから「秘密」を吐き出させる技術
現場でインシデントレスポンスを担当していると、よく耳にするセリフがある。「うちのアプリはエラーメッセージを表示しない設定にしているから、SQLインジェクションは起きないはずだ」と。
残念だが、それは大きな誤解だ。データベースが沈黙しているとき、攻撃者は別の言語で語りかけてくる。それが「ブラインドSQLインジェクション(Blind SQLi)」だ。画面にエラーを出さずとも、システムの「反応速度」や「挙動の差異」を観測することで、攻撃者はデータベースの全容を少しずつ、しかし確実に盗み出す。
今回は、この厄介な攻撃の正体と、それを根絶するための実戦的な防御策を解説する。
—
1. なぜ「ブラインド」なのか? 攻撃のメカニズム
ブラインドSQLiには、大きく分けて二つの手法がある。
① 真偽ベース (Boolean-based)
レスポンスの「内容」の変化を見る手法だ。例えば、id=1' AND 1=1-- と id=1' AND 1=2-- を投げ、ページが表示されるか否かで判定する。これだけで「そのSQLは実行可能か?」という命題の真偽が取れる。あとは二分探索を使えば、テーブル名やパスワードのハッシュ値を一文字ずつ抽出できてしまう。
② 時間ベース (Time-based)
レスポンスの「速度」を見る手法だ。エラー画面も真偽の差異も出ない場合、攻撃者は SLEEP() 関数を注入する。
id=1' AND (SELECT IF(SUBSTRING(password,1,1)='a', SLEEP(5), 0) FROM users WHERE id=1)--
もしレスポンスが5秒遅延すれば、パスワードの1文字目は「a」だと断定できる。この泥臭い作業を自動化ツールが数分で完了させるのだ。
—
2. 現場で使える「コピペ厳禁」の防御コード
「パラメーター化クエリ(プリペアドステートメント)」を使えば、SQLiの99%は防げる。しかし、なぜか「クエリの組み立て」を自作するエンジニアが後を絶たない。ここでは、PHPとPythonでの「安全な実装」を提示する。
PHP (PDOによるプリペアドステートメント)
文字列連結でクエリを作るのは今すぐやめよう。
PDO::ERRMODE_EXCEPTION, // エラーを例外として投げる
PDO::ATTR_EMULATE_PREPARES => false, // ネイティブなプリペアドステートメントを強制
]);
$user_id = $_GET[‘id’];
// 安全な実装:プレースホルダを使用
$stmt = $pdo->prepare(“SELECT username FROM users WHERE id = :id”);
$stmt->execute([‘id’ => $user_id]);
$user = $stmt->fetch();
// 結果の表示…
?>
Python (SQLAlchemy/psycopg2)
Python環境でも同様。SQLの構成をDBドライバに委ねるのが鉄則だ。
PostgreSQLでの例
import psycopg2
def get_user_data(user_id):
conn = psycopg2.connect(“dbname=test user=postgres”)
cur = conn.cursor()
# 悪意ある文字列が入力されても、単なる「データ」として扱われる
query = “SELECT username FROM users WHERE id = %s”
cur.execute(query, (user_id,))
return cur.fetchone()
—
3. インフラ層での防御:WAFと設定による多層防御
アプリケーションコードの修正が即座にできない場合、あるいは「防御の厚み」を増したい場合、インフラ層での対策が有効だ。
Nginx/WAFでのフィルタリング
ブラインドSQLi特有のキーワード(SLEEP, BENCHMARK, UNION SELECT 等)を検知するルールをWAF(AWS WAF等)に設定しておくべきだ。
AWS WAF (Managed Ruleの概念設定)
- SQL Injection Rule:
sql_injection_match_setを有効化し、リクエストパラメータを検査する。 - Rate Limiting: 時間ベースのSQLiは大量のリクエストを伴うことが多いため、同一IPからのリクエストレートを制限する(Rate-based rule)。
—
4. 最後に:エンジニアとして持つべき感覚
ブラインドSQLiを防ぐために最も重要なのは、「自分の書いたコードは、入力値によってどう変貌するか?」を常に疑う姿勢だ。
- ホワイトリスト検証: IDなら整数か?名前なら許可された文字種か? 入力値チェックは「性善説」で書くな。
- 権限の最小化: アプリが接続するDBユーザーには、必要最小限の権限(
SELECT,INSERTのみ等)しか与えるな。DROP TABLEを許すような権限でアプリケーションを動かすのは、鍵をつけたまま玄関を開けているのと同じだ。
エラーが出ないから安全、ではない。沈黙こそが最大の脅威だと思え。この意識を持つだけで、君たちが作るシステムの堅牢性は劇的に向上するはずだ。
明日からの開発で、一度自分の書いたSQLクエリを見直してみてほしい。文字列連結の + や . が見えたら、それが修正の第一歩だ。
コメント