I am trying to systematically address HTTP response splitting. I have developed a wrapper class for HttpServletResponse called HardenedHttpServletResponse that mitigates splitting attempts.
Regrettably, I cannot get Jersey to call my resource method with my HardenedHttpServletResponse. I get nulls when I try.
Here is a contrived JAX-RS resource with a HTTP response splitting vulnerability which is exploitable by putting percent-encoded CRLFs (%0d%0a
) in the filename query parameter:
AttachmentResource.java:
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
@Path("/attachment")
@Produces(MediaType.APPLICATION_JSON)
public final class AttachmentResource {
@GET
@Path("/file")
public StreamingOutput getAttachment(
@Context HttpServletResponse response,
@QueryParam("filename") String filename
) throws Exception {
response.setHeader(
"content-disposition",
"attachment; filename=" + filename
);
return new DummyStreamingOutput();
}
}
Here is a dummy implementation of StreamingOutput to make it a somewhat full example:
DummyStreamingOutput.java:
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.StreamingOutput;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
private static DummyFileStreamingOutput implements StreamingOutput {
@Override
public void write(OutputStream outputStream) throws IOException, WebApplicationException {
String message = "Hello, World!";
byte[] bytes = message.getBytes(StandardCharsets.UTF_8);
outputStream.write(bytes);
outputStream.flush();
outputStream.close();
}
}
Here is the HttpServletResponse wrapper class that mitigates HTTP response splitting by throwing an exception if it detects CR or LF characters in header names or values:
HardenedHttpServletResponse.java:
import javax.inject.Inject;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.ws.rs.core.Context;
final class HardenedHttpServletResponse extends HttpServletResponseWrapper {
@Inject
HardenedHttpServletResponse(@Context HttpServletResponse response) {
super(response);
}
@Override
public void setHeader(String name, String value) {
mitigateResponseSplitting(name);
mitigateResponseSplitting(value);
super.setHeader(name, value);
}
@Override
public void addHeader(String name, String value) {
mitigateResponseSplitting(name);
mitigateResponseSplitting(value);
super.setHeader(name, value);
}
@Override
public void setIntHeader(String name, int value) {
mitigateResponseSplitting(name);
super.setIntHeader(name, value);
}
@Override
public void setDateHeader(String name, long date) {
mitigateResponseSplitting(name);
super.setDateHeader(name, date);
}
private void mitigateResponseSplitting(String value) {
if (value != null && (value.contains("\r") || value.contains("\n"))) {
throw new HttpResponseSplittingException();
}
}
}
Jersey supplies the actual response object if the response parameter has type @Context HttpServletResponse, but null if the response parameter has type @Context HardenedHttpServletResponse.
How do I get Jersey to call a resource method with an HttpServletResponse wrapper?
You can just make it injectable by adding it the DI system.
resourceConfig.register(new AbstractBinder() {
@Override
public void configure() {
bindAsContract(HardenedHttpServletResponse.class)
.proxy(false)
.proxyForSameScope(false)
.in(RequestScoped.class);
}
});
You will need to make the class public
and also its constructor public, so that the DI system can create it. This will allow you to inject HardenedHttpServletResponse
See also: