Search code examples
dependency-injectionjax-rsjersey-2.0hk2

How do I get Jersey to call a resource method with an HttpServletResponse wrapper?


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?


Solution

  • 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: