Skip to content

fix: add DNS-rebinding protection to _OriginCheckMiddleware#6227

Open
XananasX7 wants to merge 1 commit into
google:mainfrom
XananasX7:fix/dns-rebinding-localhost-protection
Open

fix: add DNS-rebinding protection to _OriginCheckMiddleware#6227
XananasX7 wants to merge 1 commit into
google:mainfrom
XananasX7:fix/dns-rebinding-localhost-protection

Conversation

@XananasX7

Copy link
Copy Markdown

Summary

This PR adds DNS-rebinding protection to the _OriginCheckMiddleware in the ADK web server.

Vulnerability

When the ADK development server is bound to a loopback address (127.0.0.1 / ::1 / localhost) — the default — and no explicit --allow-origins list is configured, a DNS-rebinding attack allows a malicious web page to make authenticated cross-origin requests to the local server.

Attack Steps

  1. Attacker controls evil.com and serves a malicious page there.
  2. Victim visits http://evil.com (or is redirected there).
  3. Attacker changes evil.com's DNS to point to 127.0.0.1.
  4. The attacker's JavaScript issues a POST to http://evil.com:8000/run.
  5. The browser sends Origin: http://evil.com and Host: evil.com.
  6. Before this fix: _get_request_origin() derives the "request origin" from the Host header (http://evil.com) and _is_request_origin_allowed() sees origin == request_originALLOW.
  7. The attacker can call /run with arbitrary prompts, read session history, exfiltrate memory, etc.

This is analogous to the DNS-rebinding protection added to the MCP go-sdk SSEHandler (DisableLocalhostProtection option).

Fix

Added two helpers:

  • _is_loopback_address(host) — correctly identifies 127.x.x.x, localhost, ::1, [::1], and all port-suffixed variants via ipaddress.ip_address().is_loopback.
  • _get_server_host(scope) — reads the actual bound address from the ASGI server tuple (set by uvicorn, not attacker-controlled).

In _is_request_origin_allowed(), when the server is on loopback and no explicit allow-list is configured, the function now additionally requires that the Origin header also resolves to a loopback host — blocking the DNS-rebinding attack regardless of what the Host header says.

Legitimate same-origin requests (browser at http://localhost:8000 → server at 127.0.0.1:8000) continue to work. Explicit --allow-origins configuration overrides the guard as before.

Tests

21 new unit tests in tests/unittests/cli/test_dns_rebinding_protection.py covering:

  • _is_loopback_address for all host formats (IPv4, IPv6, bracketed, with/without port, hostnames)
  • DNS-rebinding attack blocked for IPv4 loopback, named localhost, and IPv6 ::1
  • Legitimate same-origin requests remain allowed
  • Explicit --allow-origins allowlist still overrides the guard
  • Non-loopback servers (e.g. 0.0.0.0) are unaffected

Impact

Without this fix, any web page a developer visits while running adk web can:

  • Invoke the agent with arbitrary prompts via POST /run
  • Read all agent session history and memory
  • Call any state-changing API endpoint on the local ADK server

Severity: Medium (requires the victim to visit a malicious page while the dev server is running; limited to the local machine).

When the ADK web server is bound to a loopback address (127.0.0.1 /
::1 / localhost) and no explicit --allow-origins list has been
configured, a DNS-rebinding attack is possible:

1. Attacker serves a page at evil.com.
2. Victim opens http://evil.com (or is redirected there).
3. Attacker changes the DNS record for evil.com to resolve to 127.0.0.1.
4. The attacker's JS issues a cross-origin POST to
   http://evil.com:8000/run (which now hits the local ADK server).
5. The browser sends Origin: http://evil.com and Host: evil.com.
6. _get_request_origin returns http://evil.com (derived from Host).
7. _is_request_origin_allowed sees origin == request_origin => ALLOW.
8. The attacker can call /run, read session data, etc.

Fix: introduce _is_loopback_address() and, when the ASGI server tuple
shows the server is actually bound to a loopback address, require that
the incoming Origin is also a loopback address before falling through to
the same-origin (Host-derived) comparison.

This mirrors the DNS-rebinding protection added to the MCP go-sdk
SSEHandler (DisableLocalhostProtection option).

Adds 21 unit tests covering:
- _is_loopback_address() for all common forms
- DNS-rebinding attack scenarios (IPv4, named localhost, IPv6)
- Legitimate same-origin requests remain allowed
- Explicit --allow-origins list overrides the guard
- Non-loopback servers are unaffected

Fixes: DNS-rebinding attack against adk web local development server
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant