Introduction
This article will show you how to fix a CSRF vulnerability on a website built with Wicket framework. For simplicity this article will focus on fixing a CSRF vulnerability on one form only rather than all forms.
I already talked briefly about CSRF prevention here :
https://julienprog.wordpress.com/2017/08/01/what-is-the-difference-between-xss-and-csrf-vulnerabilities/
First of all i will talk about the CSRF prevention of the Wicket framework and then about the solution i chose based on this article :
https://dzone.com/articles/preventing-csrf-java-web-apps
I would suggest to use Spring security to perform a global solution from the start : http://www.baeldung.com/spring-security-csrf
Is Wicket built-in CSRF prevention efficient ?
Wicket has some built-in listener to detect CSRF and cancelled request :
https://ci.apache.org/projects/wicket/apidocs/6.x/org/apache/wicket/protocol/http/CsrfPreventionRequestCycleListener.html
This solution checked if the origin of the request and the requested URL have the same domain. If it is not true then it is considered as a CSRF attack.
Few problems with this solution:
Legitimate request from the same domain can be considered as a CSRF attack. For example the request url was like : example.com/sendform and referer/origin example.com .It seems because the two url are not identical it was considered as an attack.
To overcome this problem I allowed all the requested URL coming from the host domain to bypass the CSRF prevention :
allowedaddAcceptedOrigin("example.com")
I don’t like this solution because we desactivate CSRF prevention for the website domain. All POST/Ajax request are authorised from the website domain.
Second problem is that i need to put the name of the domain in configuration file.
What if the sender does not have Origin/Referrer in the POST request? It happens some legitimate client do not have it. It is considered a CSRF attack in that case.
For all these reasons i gave up on the Wicket built-in CSRF prevention technique .
Token CSRF prevention solution
I started doing the classic token solution to prevent CSRF . I followed this principle :
https://dzone.com/articles/preventing-csrf-java-web-apps
Except in my case i applied anti CSRF measure only to one form. This solution can be improved with the previous article to make the solution more generic to all forms.
This solution is decomposed like this :
Modification of the wicket form to add a anti CSRF token
Added a hidden field to the form to identify the form exactly.
Add the same anti CSRF token in HttpRequest
Add a Filter in the application to filter every request of the form
1. Modification of the wicket form to add a anti CSRF token
I modified the wicket form to add hidden input “csrftoken”. The input hidden was created in Java in the same time as the token CSRF in HttpRequest. Therefore i skipped the class LoadSalt from the previous article.
I modified a Wicket form based on this link :
http://apache-wicket.1842946.n4.nabble.com/Implementing-a-SecureForm-to-avoid-CSRF-attacks-td4666175.html
public class SecureForm<T> extends Form<T> {
...
private static final String TOKEN_NAME = "SECURE_FORM_TOKEN";
private static final String TOKEN_CSRF_SESSION = "tokenCSRF";
private String token;
@Override
protected void onBeforeRender() {
super.onBeforeRender();
this.token = UUID.randomUUID().toString();
}
@Override
public void onComponentTagBody(MarkupStream markupStream, ComponentTag openTag) {
// render the hidden field
if (isRootForm()) {
AppendingStringBuffer buffer = new AppendingStringBuffer(
"<div style=\"display:none\"><input type=\"hidden\" name=\"");
buffer.append(TOKEN_NAME)
.append("\" id=\"")
.append(TOKEN_NAME)
.append("\" value=\"")
.append(token)
.append("\" /></div>");
getResponse().write(buffer);
}
// CRF prevention applied to only one form "SpecificForm"
if(panelConf.isSpecificForm())) {
String identifyForm = "identifyForm";
AppendingStringBuffer buffer = new AppendingStringBuffer(
"<div style=\"display:none\"><input type=\"hidden\" name=\"");
buffer.append(identifyForm)
.append("\" id=\"")
.append(identifyForm)
.append("\" value=\"")
.append(identifyForm)
.append("\" /></div>");
getResponse().write(buffer);
}
HttpServletRequest request = ((HttpServletRequest) getRequest().getContainerRequest());
<strong>request.getSession().setAttribute(TOKEN_CSRF_SESSION, this.token);</strong>
// do the rest of the processing
super.onComponentTagBody(markupStream, openTag);
}
As you can in the previous code I have added a hidden field “identifyForm” to the form to identify it uniquely. It will be used later on in the filter class in order to apply CSRF prevention only to this form.
I have added the anti CSRF token in HttpRequest at the following line
request.getSession().setAttribute(TOKEN_CSRF_SESSION, this.token);
This token has also been added in the hidden field SECURE_FORM_TOKEN
2. Add a Filter in the application to filter every request of the form
My second task was to add filter similar to ValidateSalt of dzone link. Because the CSRF protection is on one form only, i have to check that a request was sent for the specific form before checking csrf token.
public class ValidateSalt implements Filter {
private static final Logger LOG = LoggerFactory.getLogger(ValidateSalt.class.getName());
private static final String TOKEN_NAME = "SECURE_FORM_TOKEN";
private static final String TOKEN_CSRF_SESSION = "tokenCSRF";
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// Assume its HTTP
HttpServletRequest httpReq = (HttpServletRequest) request;
// Get the salt sent with the request
String sessionTokenCSRF = (String) httpReq.getSession().getAttribute(TOKEN_CSRF_SESSION);
LOG.debug("tokenCSRF : " + sessionTokenCSRF);
String token =(String) httpReq.getParameter(TOKEN_NAME);
LOG.debug("token : " + token);
String identifyForm= (String) httpReq.getParameter("identifyForm");
LOG.debug("identifyForm: " + identifyForm);
if(identifyForm!= null) {
if (sessionTokenCSRF != null &&
token != null && token.equals(sessionTokenCSRF)){
chain.doFilter(request, response);
} else {
LOG.error("Potential CSRF detected!! Inform a scary sysadmin ASAP.");
// Otherwise we throw an exception aborting the request flow
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST);
}
} else {
chain.doFilter(request, response);
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
In this example, first i check if the request contain the identity of the form. Then i checked for the CSRF token.
I have also modified web.xml to add the filter :
<!-- Filter Example 1 -->
<filter>
<filter-name>ValidateSalt</filter-name>
<filter-class>com.acme.filter.ValidateSalt</filter-class>
</filter>
<filter-mapping>
<filter-name>ValidateSalt</filter-name>
<url-pattern>/root/forms/*</url-pattern>
</filter-mapping>
<!-- end Filter Example 1 -->
How to test for CSRF vulnerability ?
First of all authenticate into you webapp, then execute a form of your webapp within a html file. The html file contains a copy of the form you want to test. The execution of the html file should fail if there is a CSRF prevention. Example of html file :
<html>
<body onload='document.CSRF.submit()'>
<meta charset="utf-8"/>
<form action='http://example.com/forms/myform' method='POST' name='CSRF'>
<input type='hidden' name='id' value=''>
<input type='hidden' name='identifyForm' value='identifyForm'>
<input type='hidden' name='variable1' value='0'>
<input type='hidden' name='variable2' value='1'>
<input type="buttonSearch" value="Submit">
</form>
</body>
</html>
I put a link to owasp testing here :
https://julienprog.wordpress.com/2017/08/01/what-is-the-difference-between-xss-and-csrf-vulnerabilities/