Host matching uses prefix comparison, so an attacker-chosen Host suffix reaches a service #32
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Service host matching is a prefix comparison (
src/proxy.rs:256and:327):So a service declaring
routing_hosts = ["test.example.com"]also answers requests carrying any Host that starts with that string. Verified against a live backend:test.example.comtest.example.com.attacker.examplesub.test.example.comThe prefix match admits exactly the wrong direction.
Impact
The Host header is attacker-controlled, and
rewrite_hostdefaults tofalse— ER deliberately forwards the original Host so S3 and cookie domains work. So the backend receivestest.example.com.attacker.exampleverbatim. Any absolute URL the backend builds from the Host it was given (password-reset links, email confirmations, redirects) then points at the attacker's domain, and any cache keyed on Host gets a poisoned entry.Reaching a listener is still gated by access rules, so this is not remotely exploitable on a listener an attacker can't reach. On any internet-facing listener — a Cloudflare Tunnel binding, for instance — it is.
Root cause
Not a design decision.
starts_withdates from the initial commit (b9f5635), when thehostvalue still carried:port— it was port tolerance.authority_host, which strips the port, only arrived with IPv6 support in #25 (d9e68cd). The prefix match has been dead weight ever since, and no test depends on prefix semantics.Note ER already has the correct rule in a third place:
l4::match_routedoes exact-then-*.suffixfor SNI, deliberately not matching the bare apex, mirroring wildcard-cert SAN rules.[[static_sites]]uses a fourth rule (exact only).SniResolveruses a fifth (exact, else strip one label) — that one is legitimately different, since cert resolution genuinely has its own semantics.Fix
One shared matcher, promoting the rule
l4::match_routealready implements, used by services, static sites and L4 SNI:Exact, or an explicit
*.sub.domainwildcard that matches any deeper label but never the bare apex.SniResolveris left alone.Behaviour change
Any deployment relying on the prefix match loses it. That should be nobody — the only thing it buys is answering hostnames you didn't configure — but it is a behaviour change, so it wants a minor version and a release note. Hosts wanting subdomain coverage gain
*.example.com, which previously did not work at all.Plan
obsidian/Personal/Projects/edge-router/plans/2026-09-29-honest-fallbacks-and-host-matching.md, Task 5. The same plan also fixes a down backend reporting 404 instead of 503, which is what prompted the review that found this.