DNS rebinding: how a website reaches your localhost
By Seaworthy · 7 July 2026
Photo by Jordan Harrison on Unsplash
The attack that shouldn't work
DNS rebinding sounds implausible. The browser's same-origin policy is supposed to stop pages from evil.com talking to your localhost. But there's a gap in that model, and attackers have exploited it against Kubernetes dashboards, Jupyter notebooks, and Docker Desktop.
The gap is in how browsers trust DNS.
How it works
Here's the sequence:
- An attacker registers evil.com and sets its DNS TTL to something very short, like 10 seconds.
- You visit evil.com. Your browser resolves the hostname to the attacker's real IP (203.0.113.42) and loads a page with malicious JavaScript.
- The JavaScript waits for the TTL to expire, then makes a second request to evil.com.
- This time, the attacker has rotated the DNS record to point to 127.0.0.1.
- Your browser resolves evil.com, gets 127.0.0.1, and makes the request. Same-origin policy is satisfied: the hostname is still evil.com.
- If you have a dev server, a database UI, or an admin interface running locally with no authentication, that JavaScript can now read its responses.
The attacker never touches your machine directly. They manipulated what evil.com resolves to after the first page load.
What 0.0.0.0 actually exposes
AI setup instructions often say to run your dev server on 0.0.0.0:3000 "so you can test from your phone". That's a reasonable goal. The problem is what else it does.
127.0.0.1 binds to the loopback interface only. 0.0.0.0 binds to all interfaces: loopback, WiFi, Ethernet, VPN adapters. On a home network that might feel safe. It isn't. Anyone on the same network can reach that port directly. And DNS rebinding lets a remote attacker reach it through your browser, without being on your network at all.
The AI-generated setup instructions won't mention this. They're optimising for "it works", not for "it works without exposing your machine".
Real targets
This isn't theoretical. DNS rebinding has been demonstrated against the Kubernetes dashboard, which runs on localhost:8001 via kubectl proxy and had no authentication by default in older setups. Jupyter notebooks are the canonical case: Jupyter shipped for years without authentication and with open localhost binding, and a 2018 campaign exploited this at scale. The Docker Desktop REST API has also been targeted.
If you're running any of these while browsing the web, your browser is the attack surface.
Defences
Bind to 127.0.0.1
Most dev servers and tools let you specify the interface at startup:
# binds to all interfaces - avoid this
python -m http.server 8080
# binds to loopback only
python -m http.server 8080 --bind 127.0.0.1
For Node.js, that's app.listen(3000, '127.0.0.1'). Most frameworks have a --host or --bind flag that works the same way. This alone defeats most DNS rebinding attempts, because the attacker can only get your browser to connect to 127.0.0.1, and your server isn't listening there if it's bound to 0.0.0.0.
Wait, that's backwards: if bound to 0.0.0.0, the server does listen on 127.0.0.1. The point is that binding to 127.0.0.1 exclusively means it's not reachable from other network interfaces, which limits the blast radius even if rebinding works. But Host header validation is the real fix.
Validate the Host header
Even if DNS rebinding succeeds, your server gets to inspect the Host header. A request from evil.com carries Host: evil.com, not Host: localhost. Reject it.
// Express middleware
app.use((req, res, next) => {
const allowedHosts = ['localhost', '127.0.0.1'];
const host = (req.headers['host'] ?? '').split(':')[0];
if (!allowedHosts.includes(host)) {
return res.status(403).send('Forbidden');
}
next();
});
This defeats the attack entirely. The browser sends the original hostname in the Host header, and your server can reject it before processing the request. A few lines of code.
Require auth on local admin interfaces
The Kubernetes dashboard, Jupyter, Docker's admin API, anything that manages infrastructure: none of these should be accessible without credentials, even on localhost. If you're running them without auth, the only protection you have is that nobody finds them. DNS rebinding removes that assumption.
It only needs to work once
The cost of doing this right is low. Binding to 127.0.0.1 is a one-line change. Host header validation takes minutes to add. The argument for skipping it is usually "nobody's targeting my dev machine". That's probably true most of the time. But the browser you use for development is the same browser you use for everything else, and any website you visit can attempt this without asking.
Seaworthy flags configuration files that bind services to 0.0.0.0 as part of its configuration checks.
This article was generated by AI and summarises publicly available sources.
Seaworthy scans your repo for the issues covered in articles like this one.
Security gaps, exposed secrets, and misconfigurations — caught before you deploy. Free to run, no account needed.