DNS Rebinding: When the Browser Becomes a Network Proxy

DNS rebinding lets an attacker bypass the same-origin policy and use a victim's browser as a proxy to attack services on the internal network. The technique is old but consistently underestimated in modern web applications. This guide covers how it works, what's vulnerable, and how to prevent it.

DNS rebinding is a class of attack that’s been documented for twenty years, repeatedly declared dead or mitigated, and repeatedly rediscovered targeting services people thought were safe. The core technique exploits a fundamental assumption in how browsers enforce the same-origin policy: that an IP address associated with a hostname remains stable for the lifetime of a page interaction.

It doesn’t have to.

The Attack Mechanics

The same-origin policy restricts scripts running on attacker.com from making authenticated requests to internal-service.com. That’s the isolation guarantee. DNS rebinding bypasses it without breaking it — it makes the browser believe both hostnames resolve to the same IP.

Here’s the sequence:

  1. The victim visits attacker.com. The attacker controls the DNS for this domain.
  2. The attacker’s DNS responds with the attacker’s server IP and a very short TTL — one second, or zero.
  3. The page loads JavaScript that makes a subsequent request to attacker.com.
  4. Before that request resolves, the attacker changes the DNS record for attacker.com to point to 192.168.1.1 — the victim’s router, or any internal IP.
  5. The browser resolves the DNS again (TTL expired), gets 192.168.1.1, and makes the request to that IP — but with the Origin: attacker.com header.
  6. The target service at 192.168.1.1 sees a request from attacker.com to itself. If it accepts that origin, or if it doesn’t check origins at all, the attacker’s JavaScript can read the response.

The browser hasn’t violated same-origin policy. It’s making a request to the same hostname (attacker.com) as the origin of the page. The fact that the IP changed underneath is outside the browser’s trust model.

The attacker can now enumerate internal services, read responses, and exfiltrate data — all through the victim’s browser, from within their network perimeter.

What Services Are Vulnerable

The ideal target is a service that:

  • Listens on a predictable private IP or localhost
  • Lacks authentication or uses simple auth the attacker can bypass
  • Doesn’t validate Host headers against an allowlist
  • Returns sensitive data in HTTP responses

This describes a large portion of internal tooling. Development servers (localhost:3000, localhost:8080) are classic targets. Admin interfaces for routers, printers, and IoT devices at default IPs are frequently exploited. Internal microservices that are “protected” only by not being publicly reachable are vulnerable to this pattern.

Kubernetes dashboard deployments, internal Grafana instances, private Jenkins servers, and developer tools like Jupyter notebooks running locally are all real-world targets for DNS rebinding. The attack has been used to extract credentials from router admin interfaces and to reach cloud instance metadata APIs (though most major cloud providers have added mitigations for the metadata endpoint specifically).

Why Modern Applications Are Still Vulnerable

Several factors keep DNS rebinding relevant despite its age.

Localhost is not safe. Applications that assume localhost traffic is trustworthy are particularly exposed. An attacker who can rebind DNS to 127.0.0.1 can interact with any service bound to localhost, including local development environments and internal services that bind to all interfaces.

Frameworks don’t validate Host headers by default. Express.js, FastAPI, Spring Boot, and most web frameworks will accept requests regardless of the Host header unless explicitly configured otherwise. A service that simply listens and responds doesn’t care what DNS record pointed the client to it.

Private Network Access (PNA) spec adoption is incomplete. Chrome implemented the Private Network Access specification (formerly CORS-RFC1918) which adds preflight checks before public-to-private network requests. But this applies to cross-origin requests, not same-origin ones — and rebinding makes the attacker’s origin appear same-origin. PNA helps but doesn’t eliminate the attack surface.

Low-TTL DNS responses work. Browsers cache DNS responses for at least the specified TTL, but minimum TTL enforcement is inconsistent. Attackers can reliably rebind within seconds in most browser configurations.

Prevention

Host header validation is the primary server-side control. Every service that accepts HTTP requests should validate that the Host header matches an expected value. This breaks rebinding because even after the DNS record changes, the Host header will still contain the attacker’s domain — and your service can reject it.

For Node.js/Express:

const ALLOWED_HOSTS = new Set([
  'localhost',
  '127.0.0.1',
  'myapp.internal',
  'myapp.internal:3000'
]);

function validateHost(req, res, next) {
  const host = req.headers.host;
  if (!host || !ALLOWED_HOSTS.has(host)) {
    return res.status(400).json({ error: 'Invalid Host header' });
  }
  next();
}

app.use(validateHost);

For services that only need to be accessed from localhost, restrict the binding address rather than relying on network policies:

// Bind only to localhost — never to 0.0.0.0
app.listen(3000, '127.0.0.1', () => {
  console.log('Listening on 127.0.0.1:3000 only');
});

This doesn’t prevent rebinding, but restricts the attack to localhost rebinding specifically rather than the full internal network.

Authentication on all internal services. Services that require authentication before returning sensitive data are harder to rebind effectively — the attacker needs to know credentials or exploit an auth bypass in addition to the DNS attack. Bearer tokens, not session cookies, are more resistant because the attacker’s script doesn’t have the token.

DNS pinning at the browser level has been proposed repeatedly but not implemented reliably. Don’t rely on browser-side DNS behaviour as a control.

Network-level controls: Web Application Firewalls can inspect Host headers and block rebinding attempts. Reverse proxies (nginx, Envoy) that sit in front of internal services can enforce host validation centrally.

For particularly sensitive internal tooling, a minimal additional control is to randomise the port on each startup — an attacker needs to know the port to rebind to the service:

const port = 49152 + Math.floor(Math.random() * 16383);

This is obscurity, not security, but it raises the bar for automated scanning.

Testing for DNS Rebinding Vulnerabilities

Manual testing: use a rebinding tool like singularity or configure a test DNS server with short TTLs pointing to internal IPs, and verify that your application rejects requests with unexpected Host headers.

In your security testing checklist, include:

  • Does the service validate Host headers?
  • Does the service bind to 0.0.0.0 unnecessarily?
  • Does the service return sensitive data without authentication to any client that reaches it?
  • Would the service accept a request with Host: attacker.example.com?

If any of these answers are unfavourable, the surface exists.

A quick validation in a running Node.js service:

# Test if your service accepts an arbitrary Host header
curl -H "Host: attacker.example.com" http://localhost:3000/api/sensitive-endpoint

If it returns data instead of a 400 or rejection, the service doesn’t validate the Host header and is potentially rebindable.

References