How to alter the body of a http response in a filt

2019-05-14 21:41发布

问题:

I am attempting to use a filter to check for HTML tags in a response body. The problem is that if I alter the body in the filter, it isn't altered when it gets to the client. I tried the solution shown here: Looking for an example for inserting content into the response using a servlet filter but it didn't help.

I have two filters. SecureWrapperFilter wraps the request/response objects in our custom wrapper, and XSSFilter uses OWASP encode to encode for html content. The filters look like this:

public class SecureWrapperFilter implements Filter {

    @Override
    public void init(final FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(final ServletRequest request, final ServletResponse response,
        final FilterChain chain) throws IOException, ServletException
    {
        final ServletRequestWrapper securityRequest =
            new ServletRequestWrapper((HttpServletRequest)request);
        final ServletResponseWrapper securityResponse =
            new ServletResponseWrapper((HttpServletResponse)response);
        ESAPI.httpUtilities().setCurrentHTTP(securityRequest, securityResponse);
        chain.doFilter(ESAPI.currentRequest(), ESAPI.currentResponse());
    }

    @Override
    public void destroy() {
    }
}

and:

public class XSSFilter implements Filter {

    @Override
    public void init(final FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(final ServletRequest request, final ServletResponse response,
        final FilterChain chain) throws IOException, ServletException
    {      
        final ServletRequestWrapper requestWrapper = (ServletRequestWrapper)request;
        final String body = Encode.forHtmlContent(requestWrapper.getBody());
        requestWrapper.setBody(body);
        chain.doFilter(requestWrapper, response);
        final ServletResponseWrapper responseWrapper = (ServletResponseWrapper)response;
        final byte[] copy = responseWrapper.getCopy();
        final String oldBody = new String(copy, response.getCharacterEncoding());
        final String newBody = Encode.forHtmlContent(oldBody);
        if (!StringUtils.equals(oldBody, newBody)) {
            responseWrapper.getResponse().getOutputStream().write(newBody.getBytes());
        }
    }

    @Override
    public void destroy() {
    }
}

If I add some debug Logging, I can see that the securityResponse has the modified body in the SecureWrapperFilter, but on the client side, the body looks as if it was never modified.

Any suggestions would be greatly appreciated. Thanks.

回答1:

The problem was that in my XSSFilter, I was appending the new response body onto the old one. This was causing invalid json like {"x"="y"}{"escapedx"="escapedy")

Our client deserializer was only printing the first json object so {"x"=y"} was all we were seeing on the client side.

To resolve this problem, I added the following line to the XSSFilter:

responseWrapper.getResponse().resetBuffer();

before

responseWrapper.getResponse().getOutputStream().write(newBody.getBytes());

This clears the buffer, allowing me to rewrite it on the line below. My json on the client side now looks like: {"escapedx"="escapedy"}



回答2:

You need to make sure the HttpResponse is buffered. If the buffer is not big enough, then the reponse will be streamed to the client befire your filter is called.

Or maybe the servler is calling flush() on the response?



回答3:

Sending back json can be done with jackson:

val res = response as HttpServletResponse

res.status = HttpStatus.UNAUTHORIZED.value()
res.contentType = MediaType.APPLICATION_JSON_UTF8_VALUE

res.outputStream.write(ObjectMapper().writeValueAsString(ResponseError(
    "two_factor_auth_failed", "Two Factor Authorization is required to proceed."
)).toByteArray())