Alain O'Dea
Alain O'Dea

Reputation: 21716

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?

Upvotes: 1

Views: 1460

Answers (1)

Paul Samsotha
Paul Samsotha

Reputation: 209112

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:

Upvotes: 1

Related Questions