WAFのシグネチャで「SQLインジェクション」を止めようとするな。――それが、泥沼の戦いの始まりだ。
現場でインシデント対応をしていると、「WAF(Web Application Firewall)を入れればSQLi(SQLインジェクション)は完璧ですよね?」という甘美な問いかけをよく受ける。結論から言おう。WAFはあくまで「盾」であり、アプリケーションそのものが脆弱であれば、いつか必ず貫かれる。
今日は、WAFのシグネチャ設計の限界と、それでもなお「防波堤」として機能させるための技術的勘所を、実務の最前線から解説する。
—
1. なぜ「キーワードマッチング」だけでは防げないのか
多くのエンジニアが陥る罠は、UNION, SELECT, DROP, -- といったキーワードをブラックリスト形式で弾こうとすることだ。攻撃者はこれを見て笑う。
例えば、SELECTを検知するWAFに対して、攻撃者はこう投げる。
sElEcT (大文字小文字の揺らぎ)
SEL//ECT (コメント挿入による難読化)
%53%45%4c%45%43%54 (URLエンコード)
これらを全てシグネチャで拾おうとすれば、正規表現は肥大化し、計算量は爆発し、最終的には「正常なユーザーの入力まで遮断する(False Positive)」という、インフラエンジニアにとって最も忌まわしい事態を招く。
泥臭い現場の戦術:正規化(Normalization)
WAFで検知するなら、入力値を「正規化」してから評価するのが鉄則だ。
1. URLデコード: 二重エンコードも考慮する。
2. 大文字小文字の統一: strtolower()相当の処理。
3. コメント除去: // や -- を削除し、SQLとして実行される直前のクリーンな状態に近づける。
この「正規化」を通してからシグネチャを走らせることで、ようやくシグネチャの漏れを防げる。
—
2. 結論:WAFに頼らず「プリペアドステートメント」で殺す
WAFの設定をこねくり回す前に、アプリケーションコードを修正するのが最短で最強のセキュリティ対策だ。SQLiの根本原因は、「データと命令(クエリ)の境界が曖昧なこと」にある。
【Python】SQLAlchemyを使ったセキュアな実装
生のクエリを組み立てるのではなく、ORMやパラメータバインドを強制する。
悪い例:文字列結合(絶対にやってはいけない)
cursor.execute(f”SELECT FROM users WHERE id = {user_input}”)
良い例:プリペアドステートメントの利用
データベースドライバ側で「値」として安全にエスケープされる
query = “SELECT FROM users WHERE username = :username”
result = session.execute(text(query), {“username”: user_input})
【PHP】PDOを使ったセキュアな実装
PHPならPDO一択だ。prepare() と execute() の分離を徹底する。
// データベース接続設定
$pdo = new PDO(‘mysql:host=localhost;dbname=test’, ‘dbuser’, ‘password’);
// プレースホルダーを用いた安全なクエリ構築
$stmt = $pdo->prepare(‘SELECT FROM users WHERE email = :email’);
// 入力値は「値」としてのみ扱われるため、SQL命令は注入できない
$stmt->execute([‘email’ => $userInput]);
$user = $stmt->fetch();
—
3. WAF(ModSecurity等)での防御を考えるなら
どうしてもレガシーなシステムを守らなければならない時、あるいは多層防御の観点でWAFをチューニングする場合の指針を教える。
WAF設定の勘所
単なるキーワード検索ではなく、「構造の異常」を狙う。
例えば、OR 1=1のようなパターンではなく、数値 = 数値 のような比較演算子がURLパラメータに現れる頻度を監視する。
Nginx + ModSecurity の設定例(概念)
特定の危険なパターンを正規表現でマッチングさせる例
ただし、正規化を事前に実行している前提
SecRule ARGS “@rx (?i)(union|select|drop|insert|delete|update)” \
“id:10001,phase:2,deny,status:403,msg:’Potential SQLi Attack Detected'”
重要なアドバイス:
この設定をいきなり「ブロックモード」で入れてはいけない。まずは「検知のみ(LogOnly)」で運用し、1週間ログを見て、誤検知(False Positive)が発生しないことを確認してからブロックに切り替えること。さもなくば、あなたのサービスのトップ顧客が突然403エラーに追い出される羽目になる。
—
最後に:セキュリティは「諦め」の先にある
いいか、セキュリティ担当者が最も警戒すべきは「自分たちは完璧に守っている」という慢心だ。
1. アプリケーション: プリペアドステートメントを強制する(これが9割)。
2. DB権限: Webアプリが使うDBユーザーには、DROP TABLEを許す権限を与えない(最小権限の原則)。
3. WAF: アプリの改修が間に合わない場合の「一時的な絆創膏」として使い、必ずホワイトリスト(正常な挙動)を定義する。
コードを書くとき、常に「この変数に『' OR 1=1 --』という文字列が入ってきたらどうなる?」と自問自答してほしい。その疑い深さこそが、最強のエンジニアへの第一歩だ。
健闘を祈る。また次の現場で会おう。
コメント