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.