Category Archives: Spring Security

Spring Security: Invalid CSRF Token ‘null’ was found on the request parameter ‘_csrf’ or header ‘X-CSRF-TOKEN’

PROBLEM

With Spring Security 4.x, the CSRF protection is enabled by default. You may disable it, but to be more aligned with OWASP and the industry security standard, it’s best to leave this setting the way it is. Learn more about CSRF attack…

To prevent this attack, Spring Security 4.x requires you to attach a server-side generated CSRF token on any POST, PUT or DELETE calls… basically, actions that may modify the request state. Their argument for not attaching this token on GET is to prevent this token value from leaking out.

Further, you will require to call POST /login and POST /logout now. In the past, you can call GET /j_spring_security_logout without problem.

If you invoke POST, PUT or DELETE without this CSRF token, you will get a 403 error with this message: "Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'.".

SOLUTION

To obtain this CSRF token, add this Spring Security custom tag to the JSP file:-

<!DOCTYPE html>
<html>
	<head>
	    <sec:csrfMetaTags/>
	</head>
	<body>
	</body>
</html>

The rendered HTML looks like this:-

<!DOCTYPE html>
<html class="no-js">
	<head>
	    <meta name="_csrf_parameter" content="_csrf" />
	    <meta name="_csrf_header" content="X-CSRF-TOKEN" />
	    <meta name="_csrf" content="e62835df-f1a0-49ea-bce7-bf96f998119c" />
	</head>
	<body>
	</body>
</html>

Finally, set the request header before making the AJAX call:-

var header = $("meta[name='_csrf_header']").attr("content");
var token = $("meta[name='_csrf']").attr("content");

$.ajax({
    url: '/test',
    type: 'POST',
    beforeSend: function(xhr){
        xhr.setRequestHeader(header, token);
    },
    success: function(data) {
        console.log(data);
    },
    error: function (xhr, ajaxOptions, thrownError) {
        console.log(xhr.status + ": " + thrownError);
    }
});

Spring Security: Forcing URLs to use HTTPS

PROBLEM

Your web application supports both HTTP and HTTPS. You want to force all URLs to use HTTPS.

SOLUTION

Spring Security has a simple configuration that allows us to redirect all HTTP-based URLs to HTTPS. All we have to do is to set requires-channel="https" on <security:intercept-url/> tag.

For example:-

<security:http auto-config="true">
	<security:form-login .../>
	<security:logout .../>
	
	<security:intercept-url pattern="/reports" access="ROLE_ADMIN" requires-channel="https"/>
	<security:intercept-url pattern="/login" access="IS_AUTHENTICATED_ANONYMOUSLY" requires-channel="https"/>
	<security:intercept-url pattern="/**" access="ROLE_USER" requires-channel="https"/>
</security:http>

With this configuration, when the user hits http://server/app, it will be redirected to https://server/app.

If we are seeing this “redirect loop” error…

… and the server log went bananas…

"GET /app HTTP/1.1" 302 0 
"GET /app/ HTTP/1.1" 302 0 
"GET /app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/app/app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/app/app/app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/app/app/app/app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/app/app/app/app/app/app/ HTTP/1.1" 302 0 

… then, chances are we are not using the default HTTP port (80) and HTTPS port (443). To fix this, we have to specify the custom port mappings in the Spring Security configuration:-

<security:http auto-config="true">
	<security:form-login .../>
	<security:logout .../>
	
	<security:intercept-url pattern="/reports" access="ROLE_ADMIN" requires-channel="https"/>
	<security:intercept-url pattern="/login" access="IS_AUTHENTICATED_ANONYMOUSLY" requires-channel="https"/>
	<security:intercept-url pattern="/**" access="ROLE_USER" requires-channel="https"/>
	
	<security:port-mappings>
		<!-- Default ports -->
		<security:port-mapping http="80" https="443"/>
		<!-- Websphere default ports -->
		<security:port-mapping http="9080" https="9443"/>
		<!-- Tomcat default ports -->
		<security:port-mapping http="8080" https="8443"/>
		<!-- Jetty custom ports -->
		<security:port-mapping http="7777" https="7443"/>
	</security:port-mappings>
</security:http>

Now, when the user hits http://localhost:7777/app, it will be redirected to https://localhost:7443/app.

Spring Security: Handling 403 Error Page

If you are already using Spring, then you might want to use Spring Security to secure your web resources.

To do that, we specify the URI to be secured with <security:intercept-url/> tag:-

<beans ...>
    <!-- Error pages don't need to be secured -->
    <security:http pattern="/error/**" security="none"/>

    <security:http auto-config="true">
        <security:form-login ... />
        <security:logout ... />
        <security:intercept-url pattern="/top-secrets/**" access="ROLE_TOPSECRET"/>
    </security:http>
		
		...
</beans>

When users without role ROLE_TOPSECRET access /top-secrets/kfc-secret, they will see this default error page:-

This proves that Spring Security is doing its job. However, the default error page looks rather F.U.G.L.Y. Further, the error page may reveal too much information about the application server. The above error page shows the application runs on Jetty. If I’m a motherhacker, I would research all the possible vulnerabilities on this particular application server in attempt to hack it.

A better solution is to provide a friendly error page when the user access is denied. This can be done by specifying <security:access-denied-handler/> tag:-

<beans ...>
    <!-- Error pages don't need to be secured -->
    <security:http pattern="/error/**" security="none"/>

    <security:http auto-config="true">
        <security:form-login ... />
        <security:logout ... />
        <security:access-denied-handler error-page="/error/access-denied"/>
        <security:intercept-url pattern="/top-secrets/**" access="ROLE_TOPSECRET"/>
    </security:http>
		
		...
</beans>

Then, we create a simple error controller that returns the error page:-

@Controller
@RequestMapping(value = "/error")
public class ErrorController {
    @RequestMapping(value = "/access-denied", method = RequestMethod.GET)
    public String accessDenied() {
        return "error-access-denied";
    }
}

Now, the user will see this custom error page:-

This solution is better than the previous one. However, SiteMesh doesn’t have the opportunity to decorate this error page before it gets rendered.

To fix this, we can create a simple redirect to allow the request to make a full-round trip to the server so that SiteMesh can decorate the error page:-

@Controller
@RequestMapping(value = "/error")
public class ErrorController {
    @RequestMapping(value = "/router", method = RequestMethod.GET)
    public String errorRouter(@RequestParam("q") String resource) {
        return "redirect:/error/" + resource;
    }

    @RequestMapping(value = "/access-denied", method = RequestMethod.GET)
    public String accessDenied() {
        return "error-access-denied";
    }
}

Then, we tweak the Spring Security to use the error router URI:-

<beans ...>
    <!-- Error pages don't need to be secured -->
    <security:http pattern="/error/**" security="none"/>

    <security:http auto-config="true">
        <security:form-login ... />
        <security:logout ... />
        <security:access-denied-handler error-page="/error/router?q=access-denied"/>
        <security:intercept-url pattern="/top-secrets/**" access="ROLE_TOPSECRET"/>
    </security:http>
		
		...
</beans>

Now, the user will see this nice beautiful error page:-

Java: Invoking Secured Web Service with JSESSIONID

PROBLEM

I wrote a JSP custom tag that invokes a secured web service within the same application to perform some evaluation. This custom tag is only used in the secured views where the user has successfully authenticated against Spring Security, and they have access to these views. The secured web service is also guarded by Spring Security.

My custom tag implementation looks something like this:-

public int doStartTag() throws JspException {
	
	...
	
	HttpClient httpclient = HttpClientBuilder.create().build();
	
	HttpGet httpGet = new HttpGet(url);

	log.debug("Before executing HTTP Get...");

	HttpResponse response = httpclient.execute(httpGet);

	int statusCode = response.getStatusLine().getStatusCode();
	
	log.debug("Status Code: " + statusCode);

	return statusCode == HttpStatus.SC_OK ? EVAL_BODY_INCLUDE : SKIP_BODY;
}

The goal here is… if the response code from the secured web service is a 200 OK, then the custom tag will evaluate its body and include the content. Otherwise, it will hide it.

My first attempt is to test the custom tag with an invalid value so that the secured web service will throw a 400 Bad Request. The hope is the custom tag’s body will not get rendered in the view.

After getting authenticated successfully by Spring Security, the view containing this custom tag gets rendered. However, the response code always return a 200 OK even though I should get a 400 Bad Request from the secured web service.

After poking around, I decided to debug the console log… and this is what I see:-

[DEBUG] [CheckEditAccess] [hasAccess:72] - Before executing HTTP Get...
127.0.0.1 -  -  [29/Dec/2013:10:18:14 -0600] "GET /epic-app/api/request/invalid-key 
HTTP/1.1" 302 0 
127.0.0.1 -  -  [29/Dec/2013:10:18:14 -0600] "GET /epic-app/;jsessionid=1vlnl6eloabfh12r9x465kvuq 
HTTP/1.1" 200 6311 
[DEBUG] [CheckEditAccess] [hasAccess:75] - Status Code: 200

As you can see, there are two calls made even though my custom tag only makes one call. The first call is made by the custom tag but the response code is a 302 Found. Then, it gets directed to the next URL that returns a 200 OK.

Why is this happening?

SOLUTION

The problem here is because when the custom tag invokes the secured web service call, it is not done within the same session where the user is already authenticated by Spring Security. Thus, Spring Security prevents the actual call to go through. Instead, Spring Security redirects the request to the login page first (which is located at /epic-app in this case). As a result, the custom tag will receive a 200 OK from the login page instead of the response code from the secured web service.

To fix this, we need to send the session ID with the HTTP Get before executing it:-

public int doStartTag() throws JspException {
	
	...
	
	CookieStore cookieStore = new BasicCookieStore();
    BasicClientCookie cookie = new BasicClientCookie("JSESSIONID", pageContext.getSession().getId());
    cookieStore.addCookie(cookie);
	
	// very important to set the domain, otherwise this will not work!
    cookie.setDomain(request.getServerName());

    cookieStore.addCookie(cookie);

    HttpClient httpclient = HttpClientBuilder.create().setDefaultCookieStore(cookieStore).build();
	
	HttpGet httpGet = new HttpGet(url);

	log.debug("Before executing HTTP Get...");

	HttpResponse response = httpclient.execute(httpGet);

	int statusCode = response.getStatusLine().getStatusCode();
	
	log.debug("Status Code: " + statusCode);

	return statusCode == HttpStatus.SC_OK ? EVAL_BODY_INCLUDE : SKIP_BODY;
}

When I try it again, now I’m getting the intended 400 Bad Request from the secured web service due to input validation error.

[DEBUG] [CheckEditAccess] [hasAccess:72] - Before executing HTTP Get...
127.0.0.1 -  -  [29/Dec/2013:10:45:19 -0600] "GET /epic-app/api/request/invalid-key 
HTTP/1.1" 400 0 
[DEBUG] [CheckEditAccess] [hasAccess:73] - Status Code: 400