From 7d4bc5ef1363b99f5d965d3ddea52a96f0605737 Mon Sep 17 00:00:00 2001 From: mushtaf saheb Date: Sun, 15 Nov 2026 14:44:20 +0530 Subject: [PATCH] http: do not forward credentials on a cross-origin redirect HTTP_Client.request() followed redirects with the original Host and Path, and with any Authorization, Proxy-Authorization or Cookie header passed by the caller, even when the Location points to another host or scheme. AI-Assisted: yes (Claude Opus 5.5) --- scapy/layers/http.py | 21 +++++++++++++++++- test/scapy/layers/http.uts | 44 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/scapy/layers/http.py b/scapy/layers/http.py index 3e0f7c78e0b..a2202bbfbaa 100644 --- a/scapy/layers/http.py +++ b/scapy/layers/http.py @@ -1053,8 +1053,27 @@ def request( continue # Second case: follow redirection if resp.Status_Code in [b"301", b"302"] and follow_redirects: + location = resp.Location.decode() + # Host and Path must be computed from the new URL + headers.pop("Host") + headers.pop("Path") + # Do not forward credentials to another origin + try: + redirect = urlsplit(location) + redirect_port = redirect.port + except ValueError: + raise ValueError("Bad URL !") from None + if redirect_port is None: + redirect_port = 443 if redirect.scheme == "https" else 80 + if (redirect.scheme, redirect.hostname, redirect_port) != ( + transport, + host, + port, + ): + for hdr in ["Authorization", "Proxy_Authorization", "Cookie"]: + headers.pop(hdr, None) return self.request( - resp.Location.decode(), + location, data=data, timeout=timeout, follow_redirects=follow_redirects, diff --git a/test/scapy/layers/http.uts b/test/scapy/layers/http.uts index 5ef9b57e9fe..a90e82834b1 100644 --- a/test/scapy/layers/http.uts +++ b/test/scapy/layers/http.uts @@ -346,6 +346,50 @@ assert client.connected == ("allowed.example", 8080, False) assert client.sent_request.Host == b"allowed.example:8080" assert client.sent_request.Path == b"/resource?item=1" += HTTP - HTTP_Client does not forward credentials on a cross-origin redirect +~ http-client + +from scapy.layers.http import HTTP_Client, HTTPResponse + +class RedirectClient(HTTP_Client): + def __init__(self, location): + super().__init__(verb=False) + self.location = location + self.sent = [] + def _connect_or_reuse(self, host, port=None, tls=False, timeout=5): + self.connected = (host, port, tls) + def sr1(self, req, **kwargs): + self.sent.append((self.connected, req)) + if len(self.sent) == 1: + return HTTPResponse(Status_Code=b"302", Location=self.location) + return HTTPResponse(Status_Code=b"200") + +client = RedirectClient(b"http://other.example/landing") +resp = client.request( + "http://origin.example/api", + Authorization=b"Bearer s3cret", + Cookie=b"session=abcd", +) +assert resp.Status_Code == b"200" +connected, req = client.sent[1] +assert connected == ("other.example", 80, False) +assert req.Host == b"other.example" +assert req.Path == b"/landing" +assert req.Authorization is None +assert req.Cookie is None + +client = RedirectClient(b"http://origin.example/landing") +resp = client.request( + "http://origin.example/api", + Authorization=b"Bearer s3cret", + Cookie=b"session=abcd", +) +assert resp.Status_Code == b"200" +connected, req = client.sent[1] +assert req.Path == b"/landing" +assert req.Authorization == b"Bearer s3cret" +assert req.Cookie == b"session=abcd" + = HTTP - HTTP_client asks HTTP_server with NTLMSSP ~ http-client