「WAFをすり抜けるSQLi」の現実と、現場で生き残るための正規化戦略
現場で戦うエンジニア諸君、お疲れ様。
「WAFを入れているからうちは大丈夫だ」という言葉を、私はインシデント対応の現場で聞き飽きた。WAFは強力な盾だが、それはあくまで「既知のパターン」という名のフィルターに過ぎない。攻撃者は常に、そのフィルターをどうやって「無害な文字列」として解釈させるかという、パズルに興じているんだ。
今回は、SQLインジェクション(SQLi)におけるWAF回避の泥臭い手口と、それを根底から無力化する「正規化」という技術的防波堤について、現場の視点で語ろう。
—
1. WAFが「見逃す」仕組み:多重エンコーディングとコメントアウトの罠
攻撃者がWAFを回避する際、最も多用するのが「正規化の不一致」を突く攻撃だ。WAFは特定のパターンを検知して遮断するが、その検知ロジックと、バックエンドのデータベースやアプリケーションが解釈する文字列の間にギャップがあると、そこが致命的な穴になる。
具体的な回避の手口(PoCの概念)
例えば、WAFが UNION SELECT というキーワードを監視しているとする。攻撃者はこれを次のように変形させる。
- URLエンコーディングの多重化:
%2555NION %2553ELECT
WAF側が一度デコードして「安全」と判断しても、バックエンド側で二重デコードが発生すれば、それは UNION SELECT として実行される。
- コメントアウトの挿入:
UNI//ON SEL//ECT
多くのWAFは UNION という連続した文字列を探すが、途中にコメントを挟むことでシグネチャを回避しつつ、MySQL等のSQLエンジンではコメントが無視される性質を利用してクエリを通してしまう。
これらは氷山の一角に過ぎない。WAFを「最後の砦」と考えるのは危険だ。
—
2. なぜ「正規化(Normalization)」が最強の防御なのか
WAFをすり抜けた攻撃を最後に関門で止めるには、「アプリケーションに届く前に、すべての入力を『正規の姿』に戻す」という工程が不可欠だ。
正規化とは、二重エンコーディングや不自然な空白、コメントなどを取り除き、その値が「本来は何を意味しているのか」という純粋なデータに還元する処理を指す。
実践:バックエンドでの対策コード(Python/Flask)
多くのエンジニアがやりがちなのが、WAF任せにして自前でサニタイズをしないことだ。プリペアードステートメント(静的プレースホルダ)の利用は必須だが、それに加えてアプリケーション層での正規化を組み込むことで、多層防御が完成する。
import urllib.parse
import re
def normalize_input(data):
“””
入力データを正規化する関数
1. 二重デコードを防ぐため、完全にデコードする
2. SQLの特殊なコメント構造や制御文字をクリーニングする
“””
# 1. デコードのループ処理(二重エンコード対策)
while True:
decoded = urllib.parse.unquote(data)
if decoded == data:
break
data = decoded
# 2. SQLのコメントアウトを意図的に無効化する例(正規表現でトリミング)
# ※本質的にはライブラリのプリペアードステートメントに任せるべきだが、
# 異常な文字列の混入を早期検知するために正規化を行う
clean_data = re.sub(r’/\.?\/’, ”, data)
return clean_data
使用例:脆弱なクエリの直接実行は厳禁。必ずプレースホルダを使うこと!
cursor.execute(“SELECT FROM users WHERE id = %s”, (normalize_input(user_input),))
—
3. インフラレベルでの防御:Nginxによる正規化の強化
アプリケーションにデータが到達する前に、Webサーバー(Nginx)側でも最小限の正規化やバリデーションを行うことは、攻撃者の足元をすくうのに有効だ。
nginx.conf での例を挙げよう。あからさまなSQLiパターンをサーバー側で弾く設定だ。
nginx.conf の server ブロック内
SQLのコメントやUnion系キーワードを含むリクエストを拒否する設定
if ($query_string ~ “(\%27)|(\’)|(\-\-)|(\%23)|(\#)”) {
return 403;
}
if ($query_string ~ “(union|select|insert|update|delete|drop|truncate)”) {
# 誤検知の可能性もあるが、APIの仕様が厳格なら非常に有効なフィルタ
return 403;
}
※注意:この設定はあくまで補助だ。union がユーザー名として入力されるようなアプリケーションでは誤検知を引き起こすため、自社のビジネスロジックと照らし合わせて調整してほしい。
—
最後に:エンジニアが持つべき「疑いの精神」
セキュリティの現場で最も信頼できるのは、ツールではなく「入力されたデータは、常に攻撃の意図を含んでいるかもしれない」と疑うエンジニアの直感だ。
1. WAFは信じるな: フィルタを回避する新しいエンコーディング手法は毎日生まれている。
2. 正規化を徹底せよ: 入力値は必ず「期待される形式」に変換してから処理する。
3. プリペアードステートメント一択: SQLiの根本対策はこれに尽きる。例外を作るな。
WAFは「攻撃のノイズを減らして運用負荷を下げるためのもの」、本当の防御は「コードそのものの堅牢性」にある。この認識をチーム全員で共有してほしい。
もし、今動いているコードの中に db.execute("SELECT FROM table WHERE id = " + user_input) という記述を見つけたら……それは、今日この瞬間から「インシデント予備軍」として扱うこと。それが世界最高峰のセキュリティ基準だ。健闘を祈る。
コメント