With csrf disabled I can upload file however I need it enabled. The problem only occurs when the form enctype is multipart/form-data, namely 'Invalid CSRF Token' with 403.
Generally when I set the enctype as multipart/form-data even for a form without file upload, I get the same error.
Using this dependency:
<dependency>
<groupId>org.synchronoss.cloud</groupId>
<artifactId>nio-multipart-parser</artifactId>
<version>...</version>
</dependency>
Tried including hidden csrf input in the form and also tried appending it to the url but same error
<form method="post" th:action="${'/add/' + id + '/documents?' + _csrf.headerName + '=' + _csrf.token}" enctype="multipart/form-data">
<input type="file" name="documents" multiple="multiple">
<input type="hidden"
th:name="${_csrf.headerName}"
th:value="${_csrf.token}" />
<input type="hidden" name="_csrf" th:value="${_csrf.token}">
<button class="btn btn-success btn-l">Upload</button>
</form>
Have a controller advice like this for csrf injection
@ControllerAdvice
public class SecurityAdvice {@ModelAttribute("_csrf")Mono<CsrfToken> csrfToken(final ServerWebExchange exchange) {
final Mono<CsrfToken> csrfToken = exchange.getAttributeOrDefault(org.springframework.security.web.server.csrf.CsrfToken.class.getName(), Mono.empty());
return csrfToken;
}
In security I have the following bean:
@Bean
public ServerCsrfTokenRepository csrfTokenRepository() {
WebSessionServerCsrfTokenRepository repository =
new WebSessionServerCsrfTokenRepository();
repository.setHeaderName("X-CSRF-TK");
return repository;
}
and using it like this in my SecurityWebFilterChain:
.and().csrf().csrfTokenRepository(csrfTokenRepository())
UPDATE:
Disabling csrf for a few urls would be enough too. Found a few examples but all of them are for Servlet based version. https://sdqali.in/blog/2016/07/20/csrf-protection-with-spring-security-and-angular-js/
The “Invalid or missing CSRF token” message means that your browser couldn't create a secure cookie or couldn't access that cookie to authorize your login. This can be caused by ad- or script-blocking plugins or extensions and the browser itself if it's not allowed to set cookies.
3.1 Enabling CSRF Token in Spring Securitydisable() in your Spring security config class. With default setup, if you look at the source code of the page, you will see the _csrf parameter being added automatically to the form by Spring security.
You can disable CSRF protection by setting the csrf. protection. enabled system configuration item to the value false. This can be done via REST API.
But till now in all our examples we had disabled CSRF. CSRF stands for Cross-Site Request Forgery. It is an attack that forces an end user to execute unwanted actions on a web application in which they are currently authenticated.
Take a look at Spring Security's official recommendation: https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html#csrf-multipart
There are basically two ways of doing it: (1) placing MultipartFilter before Spring Security filter and (2) include the CSRF token in the form action, as you are doing. The first option is the recommended one:
The first option is to ensure that the MultipartFilter is specified before the Spring Security filter. Specifying the MultipartFilter before the Spring Security filter means that there is no authorization for invoking the MultipartFilter which means anyone can place temporary files on your server. However, only authorized users will be able to submit a File that is processed by your application. In general, this is the recommended approach because the temporary file upload should have a negligble impact on most servers.
To ensure MultipartFilter is specified before the Spring Security filter with java configuration, users can override beforeSpringSecurityFilterChain as shown below:
public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
@Override
protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
insertFilters(servletContext, new MultipartFilter());
}
}
To ensure MultipartFilter is specified before the Spring Security filter with XML configuration, users can ensure the element of the MultipartFilter is placed before the springSecurityFilterChain within the web.xml as shown below:
<filter>
<filter-name>MultipartFilter</filter-name>
<filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
</filter>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>MultipartFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Note that, if you still want to use form action, query parameters can be leaked. Try to change your "headerName" to "parameterName":
<form action="./upload?${_csrf.parameterName}=${_csrf.token}" method="post" enctype="multipart/form-data">
EDIT: In case you can't switch to a servlet-based container (such as Jetty or Tomcat) and the form action recommendation doesn't work, there's a recent Stack Overflow thread discussing this issue.
One of the developers reported to workaround the issue using AJAX:
I solved this problem by:
- sending the multi-part file using vanilla javascript, like in Mozilla's guide
- adding the _csrf token in the HTML header, in meta tags, like in the Spring guideline for sending the CSRF token with Ajax
- instead of using jquery, adding it directly to the XHR object
var csrfToken = $("meta[name='_csrf']").attr("content"); var csrfHeader = $("meta[name='_csrf_header']").attr("content"); XHR.setRequestHeader(csrfHeader, csrfToken); XHR.setRequestHeader('Content-Type','multipart/form-data; boundary=' + boundary); XHR.send(data);
The same developer reported this issue to Spring, but didn't get any attention yet.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With