WAFは「魔法の盾」ではない。インジェクション攻撃を突破する「難読化」の深淵と、現場がとるべき真の防衛術
「WAF(Web Application Firewall)を導入しているから、SQLインジェクションは大丈夫」
もし君が今のプロジェクトでそうタカを括っているなら、今すぐその認識を改めたほうがいい。数々のインシデント現場に立ち会ってきた経験から言わせてもらうと、WAFはあくまで「既知のパターンを弾く水門」に過ぎない。 攻撃者は日々、その水門の隙間を縫う「難読化」という名のパズルを楽しんでいるんだ。
今回は、WAFの検知限界を突破する攻撃者の視点と、それを無効化するための「コードレベルでの確実な防衛戦略」を叩き込んでいく。
—
1. なぜWAFは「難読化」に敗北するのか
WAFのシグネチャ(検知ルール)は、基本的に「正規表現」で動いている。例えば、UNION SELECTという文字列を検知するように組まれていれば、それは弾かれる。だが、攻撃者はそんな単純なパターンをそのまま投げるようなヘマはしない。
攻撃者が使う「検知回避」のテクニック例
攻撃者は、データベースエンジンが解釈できるが、WAFが「怪しい文字列」と認識できない形式に変換する。
- コメント挿入:
UNI//ON SEL//ECT - エンコーディング: URLエンコードの二重化や、Unicodeの特殊文字変換
- SQL方言の悪用:
/!50000UNION/(MySQLのバージョンコメント機能を悪用)
これらを組み合わせられると、WAFのルールベース検知は「ただの無害なリクエスト」として通過させてしまう。WAFを突破された後、バックエンドのデータベースに脆弱なクエリが存在すれば、そこがゲームオーバーの地点だ。
—
2. 【実務的対策】インジェクションを根絶する実装
WAFをすり抜けてきたとしても、アプリケーション側で「そもそもデータとしてしか扱えない」状態にしていれば、SQLインジェクションは物理的に成立しない。
悪い実装(地雷)
// ユーザー入力をそのまま連結。WAFが突破されたら即終了の危険コード
$sql = “SELECT FROM users WHERE username = ‘” . $_GET[‘user’] . “‘”;
$db->query($sql);
良い実装(プリペアドステートメント)
PHP (PDO) を使った、現代のスタンダードな実装だ。
// プリペアドステートメント(準備されたステートメント)を使用
$stmt = $pdo->prepare(‘SELECT FROM users WHERE username = :username’);
// 入力値を「データ」としてのみバインドする
// これにより、攻撃者がSQL構文を注入しようとしても、単なる文字列として処理される
$stmt->execute([‘username’ => $_GET[‘user’]]);
$user = $stmt->fetch();
ポイント: 変数をSQL文の中に混ぜるな。バインド変数(プレースホルダ)を使え。これだけで、WAFの検知を回避されたとしても、攻撃側のペイロードは無害化される。
—
3. OSコマンドインジェクション対策:ブラックリストより「ホワイトリスト」
OSコマンドインジェクションも同様だ。WAFで | や ; を遮断していても、シェルを呼び出す実装自体が脆弱なら、別の経路から突破される。
Pythonでの安全な実装例
os.system や subprocess.call(shell=True) は厳禁だ。
import subprocess
悪い例: ユーザー入力をそのままシェルに渡す
subprocess.call(f”ls {user_input}”, shell=True)
良い例: 引数をリストで渡し、シェルを経由させない
これにより、ユーザー入力に悪意あるコマンドが含まれていても、
単なる「ファイル名」としてしか扱われない
def safe_list_files(filename):
# shell=False (デフォルト) を維持し、引数を分離する
result = subprocess.run([“ls”, filename], capture_output=True, text=True)
return result.stdout
—
4. 多層防御の「最後の砦」としてのWAF設定
もちろん、WAFは不要ではない。WAFは「攻撃の試行回数を減らす」「未知の脆弱性へのパッチまでの時間を稼ぐ」ための優れた盾だ。ただし、「シグネチャに頼り切らない」設定が肝になる。
Nginx/ModSecurityの推奨運用指針
- 異常値のブロック(Anomaly Scoring): 特定のシグネチャマッチだけでなく、リクエスト全体の「怪しさ」をスコアリングしてブロックするモードを有効にすること。
- ボディサイズ制限: 不必要に大きなリクエストボディを制限し、難読化ペイロードの注入量を減らす。
- JSON/XMLのバリデーション: APIエンドポイントでは、リクエストのContent-Typeを厳密にチェックし、不要なフォーマットは即座に拒否する。
Nginx設定: 特定の拡張子へのアクセスを禁止する等の基本設定
location ~ \.(php|sql|sh)$ {
deny all; # そもそもアクセスさせるべきではないファイルへの直接アクセスを断つ
}
—
最後に:エンジニアとしての矜持
セキュリティとは、完璧な製品を買ってきて設置すれば終わるような「工事」ではない。「入力値はすべて悪意を持っている」という前提に立ち、コード一行一行を疑い続ける「文化」そのものだ。
WAFはあくまで、君たちが書いたセキュアなコードを守るための「二重の鍵」だ。もしWAFが突破されたとき、君の書いたコードは攻撃者を跳ね返せるか? それを常に自問自答してほしい。
システムがクラッシュしてからでは遅い。今すぐ、自分のコードの「外部入力の取り扱い」を再点検してみてくれ。それが、プロのエンジニアとしての最低限の防衛線だ。
コメント