0

Scenario

  • Single-page (AngluarJS) web application using HTTP API for client-server communication
  • Spring Security configured to use CSRF protection (via XML)
  • CSRF token is usually sent in request header (works fine)
  • Application needs to support file upload in IE9

Problem

The file upload is acheived though a multipart/form-data POST request. Usually this is done using client-side AJAX, but IE9 does not support FileAPI (http://www.w3.org/TR/FileAPI/).

The work-around for IE9 is to create a form in a hidden iframe, and submit the form. The CSRF token is added to the request body by adding it as a form input -- reason being that I cannot manipulate request headers to add the CSRF header before form submission.

Spring Security's org.springframework.security.web.csrf.CsrfFilter first tries to get the CSRF token from the headers, and if not found then tries to get it from the parameters (via HttpServletRequest.getParameter())

This doesn't work for a multipart request with the CSRF token in the body -- getParameter() will always return null.

(As an aside, this call to getParameter() also reads the request InputStream to the end, so we're forced to wrap the request before it gets to the CsrfFilter so that the request InputStream is 'cached')

I want to create a CsrfFilter that invokes getPart(), but cannot do so while still using the nice+clean Spring Security XML namespace elements.

The reason is that there is no place to include a custom CSRF filter in the configuration -- and the CsrfConfigurer is hard-coded to use the org.springframework.security.web.csrf.CsrfFilter, so one cannot be injected.

I can add code to the overridden getParameter() method of my request wrapper class to also attempt to parse the parameter out of multipart request -- but really this is quite tricky to get right, and would rather avoid such maintenance costs.

TL;DR

  • We can't add CSRF token to request headers, need to add to request body
  • Request is multipart/form-data
  • Spring Security CSRF filter is not configurable to parse CSRF token from multipart request

Any help -- either suggestions to fix on the client-side or server-side -- is welcome!

TIA

Chinbold Gansukh
  • 1,809
  • 1
  • 15
  • 6
southp4w
  • 3
  • 1
  • 4
  • does it duplicate http://stackoverflow.com/questions/18796881/spring-security-3-2-0-rc1-csrf-with-multipart-form-data?rq=1 ? Maybe, but those answers are not secure approaches, and therefore not a viable solution (e.g. in query string, or before Spring Security) – southp4w Feb 06 '15 at 22:10
  • You can also follow my solution here [1], and instead of putting the header name and token value as meta tags in header, put them in some elements with a know HTML ID, retrieve them and attach them to the XHR object. [1] http://stackoverflow.com/questions/21514074/spring-csrf-token-does-not-work-when-the-request-to-be-sent-is-a-multipart-requ/43122490#43122490 – Andrei Epure Mar 30 '17 at 15:50

2 Answers2

5

You should read the section in the reference that discusses CSRF and Multipart requests. You have two options:

Each has its pros/cons that are described in the reference.

Ultimately, if you want to provide a custom filter you can do so using the XML element which simply refers to a Spring Bean that implements Filter. For example:

<http ...>
   ...
   <custom-filter ref="customCsrfFilter" position="CSRF_FILTER"/>
</http>
Rob Winch
  • 21,440
  • 2
  • 59
  • 76
  • Thanks @rob-winch I appreciate the time taken to answer -- but both of those solutions share the same 'con': they introduce security vulnerabilities. I've posted another answer that is working, but it's not particularly pretty. Any thoughts? – southp4w Feb 06 '15 at 23:20
  • 1
    What security issue do you perceive in having the multi-part filter before the CSRF filter? How does the CSRF token secure secure the uploaded temporary file? To secure file uploads you will need other mechanisms with or without CSRF protection, such as, virus and malware scanners. – manish Feb 07 '15 at 13:36
  • @southp4w Getting a multipart parameter means that you are implementing the exact same thing as "Include MultipartFilter Before Spring Security" and you have the same disadvantage. You must allow the user to upload a file (this is how retrieving multipart parameters works) in order to verify the CSRF token. You are better off using the MultipartFilter – Rob Winch Feb 07 '15 at 21:13
  • @RobWinch good point, I agree. I've updated the configuration to place the org.springframework.web.multipart.support.MultipartFilter before Spring Security. I've also added some wrapping around the request to allow the InputStream to be read after the `MultipartResolver` parses the request. Thanks for your input on this. @manish you too – southp4w Feb 09 '15 at 18:32
0

I've implemented a solution that seems to be working now, but there is something about it I really don't like...

  1. Still using Spring Security's <csrf>
  2. I've added a custom filter BEFORE the CSRF filter:

    <security:custom-filter before="CSRF_FILTER" ref="csrfRequestWrapperFilter" />

  3. I've injected the same RequestMatcher into the CsrfRequestWrapperFilter, as to only process requests that will be checked for the CSRF token
  4. The filter wraps/decorates an HttpServletRequest in a CsrfProtectedHttpServletRequest, and continues the chain (next is CsrfFilter)
  5. CsrfProtectedHttpServletRequest extends HttpServletRequestWrapper, and overrides the getParameter(String name) method. Something like:

... copy Request InputStream to member OutputStream ...

    if(this.getContentType() != null && requestBody != null) {
            if(this.getContentType().contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE) && requestBody.contains(parameterName+"="))
                return getFormEncodedParameter(requestBody, parameterName);
            else if(this.getContentType().contains(MediaType.MULTIPART_FORM_DATA_VALUE)  && requestBody.contains("name="+"\""+ parameterName +"\"")) {
                return getMultipartParameter(requestBody, parameterName);
            }
        }
    }

Both of those methods do some String parsing... that's the part I really don't like. It's not at all related to core business logic, and I'm not 100% confident in the implementation, although it IS working to get the _csrf "parameter" out of a multipart request.

southp4w
  • 3
  • 1
  • 4