【実務・中級編】SQLインジェクションの基本メカニズムと構文解析の脆弱性 – アプリケーションセキュリティ & 安全な開発防御ガイド

SQLインジェクションという「古くて新しい悪夢」の正体

現場でインシデント対応をしていると、いまだに「なぜ今さらSQLインジェクションで抜かれるのか?」と頭を抱える現場に遭遇する。フレームワークを使っているから安心、という慢心こそが最大の脆弱性だ。

SQLインジェクションの本質は、「データとして送ったはずの文字列が、DBエンジンのパーサーによって『命令(コマンド)』として誤解釈されること」にある。これを理解していないと、いくら型変換をしても、エスケープ関数を挟んでも、いずれどこかで穴を開けることになる。

1. なぜ「パーサー」が騙されるのか

例えば、こんなクエリを考えてみてほしい。

— ユーザーIDで検索するクエリ
SELECT FROM users WHERE id = ‘$user_id’;

開発者が「$user_idには数字しか入らないはずだ」と信じ込んで実装した場合、攻撃者は1' OR '1'='1という文字列を投げる。すると、生成されるSQLはこうなる。

SELECT FROM users WHERE id = ‘1’ OR ‘1’=’1′;

DBの構文解析エンジンは、ここで「本来の検索条件」と「攻撃者が付け加えた真偽判定」を区別できなくなる。結果、テーブルの全行が返される。これがインジェクションの基本原理だ。

重要なのは、データベースごとに構文解析の癖が異なるということだ。MySQL、PostgreSQL、Oracle…それぞれが持つ「エスケープの流儀」や「コメントアウトの解釈(--なのか#なのか)」の違いを知らずに、安易なブラックリスト方式のエスケープを実装するのは、素手で地雷原を歩くようなものだ。

—

2. 「これだけやればいい」セキュアな実装

泥臭い現場で生き残るための結論は一つ。「SQLの組み立てをデータベースエンジンに任せるな(動的生成を排除しろ)」、これに尽きる。

PHP (PDO) での実装例

プリペアドステートメント(静的プレースホルダ)を使う。これは「SQLの構造」を先にDBに送信し、後から「データ」を流し込む仕組みだ。これなら、データがどんなに凶悪な文字列であっても、単なる「値」としてしか扱われない。

// 接続設定(エラーモードを例外にするのが鉄則)
$pdo = new PDO(‘mysql:host=localhost;dbname=test’, ‘user’, ‘pass’, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);

// プレースホルダ(?)を使う。絶対に文字列連結しないこと!
$stmt = $pdo->prepare(“SELECT FROM users WHERE id = ?”);

// 実行時に値をバインドする
$stmt->execute([$_GET[‘id’]]);
$user = $stmt->fetch();

Python (SQLAlchemy) での実装例

現代的なORMを使う場合も、生クエリ(text()など)を多用しないよう注意が必要だ。

from sqlalchemy import text

安全なパラメータ化クエリの実行
:user_id というプレースホルダを使用する
query = text(“SELECT FROM users WHERE id = :user_id”)
result = connection.execute(query, {“user_id”: user_input})

—

3. 多層防御の「最後の砦」

アプリケーション側の修正が完了したら、インフラ層でも守りを固める。WAF(Web Application Firewall)は「万が一のバグ」を拾うためのセーフティネットだ。

Nginx + ModSecurity (WAF) の設定イメージ

SQLインジェクションのシグネチャを検知する設定を投入しておく。

ModSecurityの基本的なSQLi検知ルール(抜粋)
‘OR 1=1’ や ‘UNION SELECT’ などの典型的なパターンをブロック
SecRule ARGS “(\%27|’|–|#|\|UNION|SELECT|INSERT|DELETE)” \
“id:1001,phase:2,deny,status:403,msg:’SQL Injection Attempt Detected'”

また、IAM/DBユーザーの権限分離も忘れてはならない。Webアプリが接続するDBユーザーには、DROP TABLEやGRANTといったDDL権限を一切与えないこと。万が一SQLインジェクションが成功しても、被害を「データの閲覧」に限定させることが、被害を最小化する鍵だ。

最後に、現場の君たちへ

セキュリティ対策に「銀の弾丸」はない。だが、「入力値はすべて悪意があるものと見なす」というゼロトラストの精神をコードに落とし込むことはできる。

「動くからいいや」で済ませたコードが、数年後に大惨事を引き起こす。脆弱なコードを放置しているのは、時限爆弾を抱えて寝ているのと同じだ。今日、自分の書いているクエリを見直してほしい。文字列連結でSQLを作っている場所はないか? その小さな確認の積み重ねだけが、僕たちエンジニアの誇りを守る唯一の手段だ。

コメント

タイトルとURLをコピーしました