Out-of-band (OOB) techniques confirm blind vulnerabilities — cases where the application response gives no direct feedback (blind SSRF, blind XXE, blind command injection). Instead of diffing responses, CyberAI plants a payload that forces the target to call back to phantom-grid, which captures the DNS/HTTP interaction out of band. A captured callback is proof of execution.
cyberai scan --oob runs this workflow as part of the web exploitation phase.
Every parameter the direct walk dropped as inert -- one that answered every
payload with the response a benign value already produced -- gets a second
question through an out-of-band callback, capped at three parameters per run.
Off by default. It needs a reachable collector, and a parameter that is not vulnerable costs the collector's full wait across every payload before it says so, while one that is answers in a fraction of a second.
The callback address is the configured phantom.grid_url with its host
replaced by the Docker bridge gateway, read at run time. A containerised target
reaches its own loopback, not ours, so a loopback address in the payload
confirms nothing -- and that failure is indistinguishable from a target that is
not vulnerable.
The library API below remains available for direct use.
cyberai/integrations/phantom_grid.py—PhantomGridClient(token-flow, v2 API)cyberai/integrations/phantom_grid_poller.py—PhantomGridPoller(wait_for_callback)cyberai/integrations/oob_payloads.py— payload corpus per vuln categorycyberai/agents/exploit/oob_workflow.py—OOBWorkflow,OOBFinding,confirm_oob
One workflow covers every blind vector. VULN_TO_CATEGORY maps a coarse vuln
class to a payload category: ssrf, xxe, sqli, crlf, ssti, and
rce/command_injection/cmdi to cmdi. An unknown class falls back to
ssrf.
The grid runs on port 9090. The capture URL is derived from a server-issued token, not a client-generated id:
from cyberai.integrations.phantom_grid import PhantomGridClient
grid = PhantomGridClient(base_url="http://127.0.0.1:9090")
if not grid.available: # health check; graceful if the grid is down
...
token = grid.create_token(label="ssrf-example") # POST /api/tokens -> token
url = grid.capture_url(token) # http://<host>/c/<token>
# ... inject `url` into the target ...
hits = grid.get_interactions(token) # GET captured interactionsDelivery stays with the caller: deliver_fn receives one payload dict and is
responsible for sending it (query parameter, body, header, XML entity). The
workflow never injects into a live target by itself.
import httpx
from cyberai.agents.exploit.oob_workflow import confirm_oob
def deliver(payload: dict) -> None:
httpx.get("https://target.example/fetch", params={"url": payload["payload"]})
finding = confirm_oob("ssrf", deliver_fn=deliver, label="target-fetch-param")
if finding.confirmed:
print(finding.severity, finding.confidence) # HIGH 1.0
finding.record(session) # persists a session FindingOOBWorkflow(grid=..., poller=..., llm=...) is the same thing with explicit
dependencies; confirm_oob builds them for you. Passing an llm adds a short
written analysis of a confirmed callback (finding.ai_analysis); a failure in
that call is swallowed and never affects the verdict.
- Resolve the vuln class to a payload category.
- Return early with
error="phantom-grid unavailable"if the grid is down. - Mint a capture token and build the payload set for that category.
- Deliver each payload through
deliver_fn, polling for a callback. - On a total miss, retry once with mutated payload variants.
- Correlate any captured interaction back to the token.
OOBFinding.confidence is 1.0 only when a callback actually landed, and
0.0 otherwise; severity follows as HIGH or INFO. There is no middle value on
this path — either the target called back or it did not. record() persists
that verdict onto the session with the same calibration, so an unconfirmed
attempt is visible in the report without ever inflating into a HIGH.
- No callback within the poll window is reported as unconfirmed, never a false HIGH. Absence of evidence is not evidence.
- phantom-grid absent or unreachable degrades gracefully; the deterministic pipeline still completes.
- WebSocket push is on the phantom-grid roadmap; the current client polls over HTTP.
- Authorized targets only. Confirm scope before running.