SQLインジェクション:WAFの「お守り」だけで満足してはいけない理由
現場でインシデント対応をしていると、よく耳にするセリフがある。「WAFを入れているからSQLiは大丈夫ですよ」。残念だが、それは「鍵のついていない玄関に、防犯カメラを置いているだけ」の状態に近い。
WAFは強力な盾だが、攻撃者は常にその「シグネチャの隙間」を縫って刺してくる。今日は、WAFの限界を理解しつつ、アプリ開発側でどうやって「絶対に破られない壁」を作るか、その泥臭い現場の知見を共有しよう。
—
1. 攻撃者が「WAFを回避する」手口
WAFは通常、UNION SELECT, OR 1=1, -- といった既知のキーワードを検知する。しかし、攻撃者はURLエンコードやコメントアウト、あるいはデータベース特有の関数を利用してこれを回避する。
例えば、単純な UNION SELECT は弾かれても、以下のようなクエリはWAFをすり抜けることがある。
— WAFの正規表現次第では通ってしまうケース
‘//UN//ION//SEL//ECT//user,password//FROM//users–
WAFは「攻撃の兆候」を止めるための第一線だが、これに依存しすぎると、WAFの更新が追いつかない未知の攻撃(ゼロデイに近い手法)でいとも簡単に突破される。
—
2. アプリ開発側で実装すべき「絶対防御」
SQLインジェクションを完全に防ぐ唯一の解は、「SQL文の構造とデータを分離すること」だ。これに尽きる。現場では「プリペアドステートメント(静的プレースホルダ)」以外は認めないという文化を作るべきだ。
Python (Flask + SQLAlchemy) での実装例
ORMを使わず直接クエリを書く場合でも、必ずパラメータ化を行う。
import sqlite3
def get_user_data(user_id):
# 悪い例: 文字列結合でクエリを作るとインジェクションの餌食になる
# query = f”SELECT FROM users WHERE id = ‘{user_id}'”
# 良い例: プレースホルダ (?) を使用し、値をドライバ経由で渡す
conn = sqlite3.connect(‘app.db’)
cursor = conn.cursor()
# データベースドライバが適切にエスケープ処理を行うため安全
query = “SELECT username, email FROM users WHERE id = ?”
cursor.execute(query, (user_id,))
return cursor.fetchone()
Node.js (mysql2) での実装例
JavaScript界隈では mysql2 を使うのが定石だ。
const mysql = require(‘mysql2/promise’);
async function getUser(userId) {
const connection = await mysql.createConnection({/ config /});
// プレースホルダを用いた安全なクエリ実行
const [rows] = await connection.execute(
‘SELECT FROM users WHERE id = ?’,
[userId] // 値はここで渡す。エスケープはライブラリが自動で行う
);
return rows;
}
—
3. ログ分析:異常を早期発見するための「守りの要」
どんなに強固なコードを書いても、ゼロリスクは存在しない。重要なのは「異常なリクエストをログから炙り出すこと」だ。
Nginxのアクセスログには、インジェクションの痕跡が確実に残る。以下のパターンに合致するログが短時間に多発していないか、監視ツール(ELKスタックやDatadog等)でアラートを飛ばすべきだ。
- URLに
UNION,SELECTが含まれている - クエリパラメータに
%27(シングルクォート) や--が含まれている - HTTP 403 (WAFブロック) が同一IPから急増している
Nginxログの異常検知フィルタ例(正規表現)
ログファイルからSQLiの兆候になりそうな文字列を抽出するワンライナー
tail -f /var/log/nginx/access.log | grep -E “UNION|SELECT|–|OR%201=1|%27”
—
4. 現場のシニアとしてのアドバイス
最後に、エンジニアとしてのキャリアを積む君たちに伝えたい。
1. 「ユーザー入力は常に悪意があるもの」と仮定せよ: バリデーションは「入力形式のチェック」であり、エスケープではない。エスケープはデータベースへ投げる直前の処理だ。
2. DB権限を絞れ: アプリケーション用のDBユーザーには、DROP TABLE や GRANT などの権限を与えてはならない。SELECT, INSERT, UPDATE だけに絞る。これだけで、万が一インジェクションが成功した時の被害を最小限に抑えられる。
3. WAFを「信頼」するな、WAFを「活用」せよ: WAFは防御のすべてではなく、あくまで「攻撃の傾向を把握するためのセンサー」だ。
セキュリティは、一度設定して終わりという作業ではない。日々のログを見つめ、コードの行間にある脆弱性を疑う、その泥臭い執念こそが、システムを、そして会社を守る一番の武器になる。
自信を持ってコードを書け。ただし、常に「もし自分が攻撃者だったら、ここをどう突破するか?」という視点を忘れるな。それが、一人前のエンジニアへの近道だ。
コメント