CSRF Mitigation With AspectJ and AOP

This is another installment of using Aspect Oriented Programming techniques to mitigate common vulnerabilities in Java web applications.

First let’s define Cross Site Request Forgery (CSRF) – From OWASP:

CSRF is an attack which forces an end user to execute unwanted actions on a web application in which he/she is currently authenticated. With a little help of social engineering (like sending a link via email/chat), an attacker may force the users of a web application to execute actions of the attacker’s choosing. A successful CSRF exploit can compromise end user data and operation in case of normal user. If the targeted end user is the administrator account, this can compromise the entire web application.

Lets say you have this scenario:

You work on an super important Java web app with bunches of servlets, controllers, actions, JSP’s, etc…
You just learned how CSRF could be a huge threat to your users and you want to implement code for mitigating it on every POST request throughout.

The problem here is that to implement mitigating measures the ‘ol fashioned way, you have to go to every JSP and put in a CSRF token into every form that gets submitted. After that, you have to validate the token and put a new one up.

Using the technique below, you can mitigate CSRF without having to do any significant leg work. Here’s how!
Note: this is assuming you have AspectJ installed as well as Tomcat 6+

1. First you have to set the AspectJ weaver Java agent to the JVM parameter of your Tomcat startup. This is typically in your /bin/catalina.sh in your installation folder.

Add this to the JAVA_OPTS: -javaagent:C:/aspectj1.6/lib/aspectjweaver.jar (my path)

2. Second, you have to create a couple of aspects. I put these in a com.aspects package under the src folder.

Aspect 1: MitigateCSRFAspect – This aspect will create a pointcut that will intercept all POST requests to the application. Once it does that, it will put a CSRF token on the request and a matching one on the users’ session. It will also validate any CSRF token parameters. Props to Mr. Ricardo Zuasti for his post on some of this stuff.

package com.aspects; 
import java.security.SecureRandom;
import java.util.concurrent.TimeUnit;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.RandomStringUtils;
import org.apache.log4j.Logger;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.thesis.aop.util.StopWatch;
 
public aspect MitigateCSRFAspect{ 
	Logger logger; 
	StopWatch watch;
	
	//Constuctor for the Aspect. I do some init of loggers and
	//such here.
	public MitigateCSRFAspect(){ 
		//PropertyConfigurator.configure("log4j.properties"); 
		logger = Logger.getLogger("csrfMitigationLogger"); 
		logger.info("CSRF Prevention Aspect Created"); 
		watch = new StopWatch(); 
	} 
	
	
	pointcut csrf_interceptDoPost(HttpServletRequest req, 
		HttpServletResponse resp) :
			execution(* doPost(HttpServletRequest, 
				HttpServletResponse)) && args(req, resp);
	
	//This is the Advice for the above pointcut definition. 
	//It intercepts the response and request parameters.
	Object around(HttpServletRequest req, 
			HttpServletResponse resp): 
		csrf_interceptDoPost(req, resp){
		
		watch.start();
		logger.info("Inside of MitigateCSRF Advice");
		
		//Validation of salt currently on the request
		String saltFromRequest = 
			(String) req.getParameter("csrfSalt");
			Cache<String, Boolean> csrfCacheFromRequest = 
				(Cache<String, Boolean>)
				req.getSession().getAttribute("csrfCache");
		
		
		//If cache on session is not there, then it must be 
		//the first request. Therefore there would be no 
		//csrf token that exists yet.
		if(csrfCacheFromRequest != null){
	        if (saltFromRequest != null &&
	        	csrfCacheFromRequest.
	        	getIfPresent(saltFromRequest) != null){
	 
	            // If the salt is in the cache, we move on
	        	logger.info("SALT IS GOOD! " + saltFromRequest);
	        } else {
	            // Otherwise we log an error
	            logger.info("CSRF detected! Engage panic mode.");
	        }
		}
		
		
		//Setting the new salt
		Cache<String, Boolean> csrfCache = (Cache<String, Boolean>)
			req.getSession().getAttribute("csrfCache");
		
        if (csrfCache == null){
        	csrfCache = CacheBuilder.newBuilder()
                .maximumSize(10000)
                .expireAfterWrite(30, TimeUnit.MINUTES)
                .build();
        	
        	logger.info("Setting csrfCache: " + csrfCache);
            req.getSession().setAttribute("csrfCache", csrfCache);
        }
		
        String salt = RandomStringUtils.random(20, 0, 0, 
        		true, true, null, new SecureRandom());
        csrfCache.put(salt, Boolean.TRUE);
        
        //Use this on the JSP
        logger.info("Setting CSRF Salft: " + salt);
        req.setAttribute("csrfSalt", salt);
        
		watch.stop();
		logger.info(thisJoinPoint.getSourceLocation().getFileName() + 
			"_" + thisJoinPoint.getSourceLocation().getLine() + 
			"," + watch.getElapsedTime());	
		
		//This passes in the response object with the new header and
		//continues application execution flow as normal. 
		return proceed(req, resp);
	}
} 

Aspect 2: JSPCSRFTokenInjection – This aspect will intercept the JspWriter.write and the _jspService  methods. It will take the csrfToken value from the request and stick it in every single form on the page.

package com.aspects; 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspWriter;

import org.apache.log4j.Logger;

import com.thesis.aop.util.StopWatch;
 
public aspect JSPCSRFTokenInjection{ 
	Logger logger; 
	StopWatch watch;
	
	private String currentCSRFToken = null;
	 
	//Constuctor for the Aspect. I do some init 
	//of loggers and such here.
	public JSPCSRFTokenInjection(){ 
		logger = Logger.getLogger("csrfMitigationLogger"); 
		logger.info("CSRF Injection Aspect Created"); 
		watch = new StopWatch(); 
	} 
	
	//Capturing the CSRF Token from the request 
	//by intercepting the 
	//_jspService method inside of the JSP
	public pointcut csrf_jspServiceIntercept(HttpServletRequest req, 
		HttpServletResponse resp) : 
	    execution(* *._jspService(HttpServletRequest, 
	    		HttpServletResponse)) 
	    && args(req, resp);
	
	
	before(HttpServletRequest req, HttpServletResponse resp) : 
		csrf_jspServiceIntercept(req, resp){
		currentCSRFToken = (String) req.getAttribute("csrfSalt");
		logger.info("Got CSRF Token from request: " 
				+ currentCSRFToken);
	}
	
	//Pointcut and advice for capturing the writing into a JSP.
	public pointcut csrf_captureFormWriting(String msg) :
		call(public void JspWriter.write(String)) 
		&& args(msg) 
		&& if(msg.toLowerCase().contains("</form>"));

	Object around(String msg) : csrf_captureFormWriting(msg){
		
		msg = msg.replace("</form>", "<input type='hidden' " +
				"name='csrfSalt' value='" 
				+ currentCSRFToken + "'/></form>");
		logger.info("MSG Value: " + msg);
		return proceed(msg);
	}
} 

3. After that, you have to create an XML file that will map your aspects to the files you want to weave at load time. This file will be located in your applications classes folder under a META-INF directory. In my case it was WebGoat/WebContent/WEB-INF/classes/META-INF/aop-ajc.xml

<aspectj>
	<aspects>
		<aspect name="com.aspects.MitigateCSRFAspect"/>
		<aspect name="com.aspects.JSPCSRFTokenInjection"/>
	</aspects>
</aspectj>

4. Compile and Run your application using AJDT or the AspectJ compiler and see the results below. I used the OWASP WebGoat application to test this out. As you can see below in the log files, the CSRF token is being put on the request, put into the JSP’s, taken off the request, and validated.

WARNING: DO NOT ACTUALLY LOG THIS STUFF, IT CAN BITE YOU IF YOUR LOGS ARE COMPROMISED!!!

579022 [http-8080-2] INFO  csrfMitigationLogger  :: Inside of MitigateCSRF Advice
579022 [http-8080-2] INFO  csrfMitigationLogger  :: SALT IS GOOD! nLcD6Y02xmy7JprdeEgO
579023 [http-8080-2] INFO  csrfMitigationLogger  :: Setting CSRF Salft: DdEeErMfxOuugPkm3x4Z
579023 [http-8080-2] INFO  csrfMitigationLogger  :: HammerHead.java_120,1
579026 [http-8080-2] INFO  csrfMitigationLogger  :: Got CSRF Token from request: DdEeErMfxOuugPkm3x4Z
579030 [http-8080-2] INFO  csrfMitigationLogger  :: MSG Value:         </select><input type=’hidden’ name=’csrfSalt’ value=’DdEeErMfxOuugPkm3x4Z’/></form>

579068 [http-8080-2] INFO  csrfMitigationLogger  :: Got CSRF Token from request: DdEeErMfxOuugPkm3x4Z
580649 [http-8080-2] INFO  csrfMitigationLogger  :: Got CSRF Token from request: DdEeErMfxOuugPkm3x4Z
580651 [http-8080-2] INFO  csrfMitigationLogger  :: MSG Value:             <input type=’hidden’ name=’csrfSalt’ value=’DdEeErMfxOuugPkm3x4Z’/></form>

583787 [http-8080-2] INFO  csrfMitigationLogger  :: Inside of MitigateCSRF Advice
583788 [http-8080-2] INFO  csrfMitigationLogger  :: SALT IS GOOD! DdEeErMfxOuugPkm3x4Z
583788 [http-8080-2] INFO  csrfMitigationLogger  :: Setting CSRF Salft: plZJegGquQaFzcxmjBxX
583789 [http-8080-2] INFO  csrfMitigationLogger  :: HammerHead.java_120,2
583834 [http-8080-2] INFO  csrfMitigationLogger  :: Got CSRF Token from request: plZJegGquQaFzcxmjBxX
583836 [http-8080-2] INFO  csrfMitigationLogger  :: MSG Value:         </select><input type=’hidden’ name=’csrfSalt’ value=’plZJegGquQaFzcxmjBxX’/></form>

583876 [http-8080-2] INFO  csrfMitigationLogger  :: Got CSRF Token from request: plZJegGquQaFzcxmjBxX
583876 [http-8080-2] INFO  csrfMitigationLogger  :: Got CSRF Token from request: plZJegGquQaFzcxmjBxX
583877 [http-8080-2] INFO  csrfMitigationLogger  :: MSG Value:             <input type=’hidden’ name=’csrfSalt’ value=’plZJegGquQaFzcxmjBxX’/></form>

587996 [http-8080-2] INFO  csrfMitigationLogger  :: Inside of MitigateCSRF Advice
587996 [http-8080-2] INFO  csrfMitigationLogger  :: SALT IS GOOD! plZJegGquQaFzcxmjBxX
587997 [http-8080-2] INFO  csrfMitigationLogger  :: Setting CSRF Salft: Kwct0YrzeGSVEkb9YzGp
587997 [http-8080-2] INFO  csrfMitigationLogger  :: HammerHead.java_120,2
588017 [http-8080-2] INFO  csrfMitigationLogger  :: Got CSRF Token from request: Kwct0YrzeGSVEkb9YzGp
588022 [http-8080-2] INFO  csrfMitigationLogger  :: MSG Value:         </select><input type=’hidden’ name=’csrfSalt’ value=’Kwct0YrzeGSVEkb9YzGp’/></form>

588073 [http-8080-2] INFO  csrfMitigationLogger  :: Got CSRF Token from request: Kwct0YrzeGSVEkb9YzGp
588075 [http-8080-2] INFO  csrfMitigationLogger  :: Got CSRF Token from request: Kwct0YrzeGSVEkb9YzGp
588091 [http-8080-2] INFO  csrfMitigationLogger  :: MSG Value:                         <input type=’hidden’ name=’csrfSalt’ value=’Kwct0YrzeGSVEkb9YzGp’/></form></td>

588104 [http-8080-2] INFO  csrfMitigationLogger  :: MSG Value:                         <input type=’hidden’ name=’csrfSalt’ value=’Kwct0YrzeGSVEkb9YzGp’/></form>

588115 [http-8080-2] INFO  csrfMitigationLogger  :: MSG Value:                         <input type=’hidden’ name=’csrfSalt’ value=’Kwct0YrzeGSVEkb9YzGp’/></form>

592720 [http-8080-2] INFO  csrfMitigationLogger  :: Inside of MitigateCSRF Advice
592720 [http-8080-2] INFO  csrfMitigationLogger  :: SALT IS GOOD! Kwct0YrzeGSVEkb9YzGp
592721 [http-8080-2] INFO  csrfMitigationLogger  :: Setting CSRF Salft: 9ojOc5YNuYyPAC6LZ0Ak
592721 [http-8080-2] INFO  csrfMitigationLogger  :: HammerHead.java_120,1
592731 [http-8080-2] INFO  csrfMitigationLogger  :: Got CSRF Token from request: 9ojOc5YNuYyPAC6LZ0Ak
592736 [http-8080-2] INFO  csrfMitigationLogger  :: MSG Value:         </select><input type=’hidden’ name=’csrfSalt’ value=’9ojOc5YNuYyPAC6LZ0Ak’/></form>

Let me know if you have questions and I’ll gladly answer them.

Share

Leave a Reply

Your email address will not be published. Required fields are marked *

* Copy This Password *

* Type Or Paste Password Here *