CWE-290, Authentication Bypass by Spoofing, is the weakness of incorrectly implemented authentication schemes that are subject to spoofing attacks.
It applies whenever a system treats a claim as proof. Source addresses, header values, and hostnames are claims. They describe what the sender says, and a sender can say anything. This one touches web services, network products, building and industrial controllers, and VoIP gear.
How it happens
A developer needs to tell trusted callers from untrusted ones and reaches for something easy to read: the client IP, the Host header, a custom header set by a proxy, a name in a request. If an attacker can set or forge that value, the check protects nothing.
MITRE's two examples are small and clear:
- CVE-2022-30319 (Gold): S-bus functionality in a home automation product performs access control using an IP allowlist, which can be bypassed by a forged IP address.
- CVE-2009-1048 (Gold): a VoIP product allows authentication bypass using 127.0.0.1 in the Host header.
The second shows how code ends up here. "If the request is for localhost, it must be local, so skip the login." The Host header is supplied by the client.
# Vulnerable: trusts a client supplied header
@app.before_request
def auth():
host = request.headers.get("Host", "")
if host.startswith("127.0.0.1"):
return # "internal" request, skip login
require_login()
Another common version trusts a forwarding header:
# Vulnerable: any client can send this header
ip = request.headers.get("X-Forwarded-For", request.remote_addr)
if ip in ALLOWED_IPS:
return grant_access()
What an attacker gains
MITRE lists two consequences: bypassing a protection mechanism, and gaining privileges or assuming identity. The attacker reaches resources that are not otherwise accessible without authentication, by presenting a value the system wrongly treats as trustworthy.
How to find it
Hunt for decisions made on values the client can influence:
- Search for reads of the Host header, X-Forwarded-For, X-Real-IP, Origin, Referer, and custom "internal" headers inside authentication or access control code.
- Look for allowlists of IP addresses or hostnames used as the only control.
- Look for special casing of localhost, private ranges, or "trusted" names.
- Test it directly: send a request with a forged header or from an unlisted address and see whether the response changes.
- Check what sits in front of the service. If a proxy sets a header, confirm that clients cannot send their own copy past it.
How to fix it
- Do not authenticate by claim. Require a real credential: a session, token, signed request, or client certificate.
- Treat network location as a hint, not an identity. An IP allowlist can narrow exposure but should sit on top of real authentication, not replace it.
- Trust forwarding headers only from known proxies. Configure the framework to accept them from specific upstream addresses and strip them at the edge otherwise.
- Take the address from the connection. Use the socket's peer address when you must make a network decision, and handle it at the layer that sees real packets.
- Remove "internal" shortcuts. Admin and management functions should authenticate no matter where the request seems to come from.
- Use mutual authentication for services. Where machines talk to machines, use mutual TLS or signed tokens.
@app.before_request
def auth():
token = request.headers.get("Authorization", "")
if not verify_token(token):
abort(401)
Quick check
Why is the Host header not proof that a request is local?
The client writes it. An attacker can set it to 127.0.0.1 from anywhere, which is what CVE-2009-1048 describes.
Is an IP allowlist ever acceptable?
As an extra layer on top of real authentication, yes. As the only control it can be defeated by a forged address, as in CVE-2022-30319.
Sources
- MITRE CWE-290: https://cwe.mitre.org/data/definitions/290.html
- Safeguard Gold entry: https://gold.safeguard.sh/cwe/CWE-290