Well I been doing a little more experimentation with CSRFGuard and realized I have a flawed configuration in my last post. I figured that the initial page (say your login page) had to be outside your CSRF protected filter because you do not have a session and therefore no csrf token and the filter would fail. But it fails open. At first I thought this was a flaw but this makes perfect sense. You can now protect all your content with one csrf filter and not have to keep a special directory unprotected for your login pages and the like.
Another trick that CSRFGuard does is to automatically add your csrf token to your links in most cases. This makes it easier to integrate csrfguard into an existing application and makes it trivial to ensure that every request is sending the csrftoken without you having to code around it. I found that for some dynamically generated code like response.sendRedirect("index.jsp"); this will not work without a little help.
There are a few different response handlers that you can set in the csrfguard.properties for CSRFGuard to automatically add your token to your html. They are:
org.owasp.csrfguard.handlers.HTMLParserHandler will automatically parse the html response for a urls to attach the csrf token. This is performed server side.
org.owasp.csrfguard.handlers.RegExHandler will allow you to specify a regular expression that be searched in the html response and the append the token to the match. This action again is performed on the server side. I have not tried this one yet.
org.owasp.csrfguard.handlers.JavaScriptHandler will include javascript to your response (csrf.js). This will append csrf tokens on the client and save you some processor time on the server.
The Example
I will walk you through my example that consists of a login page and 2 csrf protected areas.
Here is my basic login page. It does 3 things. (1)If there isn't a session then you are presented with the logon page.(2) if you are submitting your credentials then it checks your credentials then it adds the generated csrf token and your userid to the session. The later is solely to represent a user and nothing to do with csrf guard. Ideally this app would query a sql database or ldap but i'm trying to keep it simple. (3) If you are logged in already and access the logon page then you are redirected back to the csrfProtectedArea1.do
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<jsp:directive.page import="org.owasp.csrfguard.util.*"/>
<jsp:directive.page import="javax.servlet.http.*"/>
<%@ page session="true" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>LogIn Page</title>
</head>
<body>
<%
String login ="";
String username = "";
String password = "";
login = request.getParameter("Login");
// Login is set to 1 when the credentials are sent. not really necessary.
if(login != null && login.equals("1"))
{
username = request.getParameter("username");
password = request.getParameter("password");
if(username != null && password != null && username.equals("Guest") && password.equals("Guest1"))
{
HttpSession sess = request.getSession(true);
sess.setAttribute("uid", "1111");
response.sendRedirect("/CSRFTest/csrfProtectedArea1.do?OWASP_CSRFTOKEN=" + request.getParameter("OWASP_CSRFTOKEN") );
}
else // login failed
{
out.write("<h3> Error: Please Log in again </h3><br>");
out.write("<h1>Please Login Below</h1>");
out.write("<form method=\"POST\" action=\"index.jsp\">");
out.write("<br> User Name: <input type=\"text\" name=\"username\">");
out.write("<br> Password: <input type=\"password\" name=\"password\">");
out.write("<input type=\"hidden\" name=\"Login\" value=\"1\">");
out.write("<br> <input type=\"submit\" name=\"loginBtn\" value=\"Login\" > ");
out.write("</form>");
}
}
else if (request.getSession().getAttribute("uid") != null && request.getSession().getAttribute("uid").equals("1111"))
{
//session is active that the userid matches.
HttpSession sess = request.getSession(true);
response.sendRedirect("/CSRFTest/csrfProtectedArea1.do?OWASP_CSRFTOKEN=" + sess.getAttribute("OWASP_CSRFTOKEN") );
//response.sendRedirect("/CSRFTest/csrfServlet.do");
}
else
{
// normal login when a session is not present
out.write("<h1>Please Login Below.</h1>");
out.write("<form method=\"POST\" action=\"index.jsp\">");
out.write("<br> User Name: <input type=\"text\" name=\"username\">");
out.write("<br> Password: <input type=\"password\" name=\"password\">");
out.write("<input type=\"hidden\" name=\"Login\" value=\"1\">");
out.write("<br> <input type=\"submit\" name=\"loginBtn\" value=\"Login\" > ");
out.write("</form>");
}
%>
</body>
</html>
Automatic Generation of Tokens
Now in the above code you will notice the following:
response.sendRedirect("/CSRFTest/csrfProtectedArea1.do?OWASP_CSRFTOKEN=" + request.getParameter("OWASP_CSRFTOKEN") );
I'm calling the request.getParamter but my form submission looks like this with no csrf token parameter because the filter will add it to the html automatically.
out.write("<h1>Please Login Below.</h1>");
out.write("<form method=\"POST\" action=\"index.jsp\">");
out.write("<br> User Name: <input type=\"text\" name=\"username\">");
out.write("<br> Password: <input type=\"password\" name=\"password\">");
out.write("<input type=\"hidden\" name=\"Login\" value=\"1\">");
out.write("<br> <input type=\"submit\" name=\"loginBtn\" value=\"Login\" > ");
out.write("</form>");
My generated html on the client for the the login page looks like this:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>LogIn Page</title>
</head>
<body><h1>Please Login Below.</h1><form method="POST" action="index.jsp"><br> User Name: <input type="text" name="username"><br> Password: <input type="password" name="password"><input type="hidden" name="Login" value="1"><br> <input type="submit" name="loginBtn" value="Login" > <INPUT type=hidden name=OWASP_CSRFTOKEN value=eKKqjn7Pqm6GLvc7bCMoYCwPbpFhXDcQSIKVnvLutX8T></form>
</body>
</html>
As you can see the <INPUT type=hidden name=OWASP_CSRFTOKEN value=eKKqjn7Pqm6GLvc7bCMoYCwPbpFhXDcQSIKVnvLutX8T> is automatically generated by the HTMLParseHandler
Now lets look at the csrfProtectedArea1.java. This page is session protected and csrf protected.
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import org.apache.jasper.runtime.*;
public class csrfProtectedArea1 extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = -6429166168752177032L;
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
HttpSession sess = request.getSession();
if(sess.getAttribute("uid")!= null && sess.getAttribute("uid").equals("1111"))
{
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<body>");
out.println("You Made it<br>");
out.println("<a href=\"csrfProtectedArea2.jsp\" > Click Here </a>");
out.println("<a href=\"index.jsp\" > Click Here </a>");
out.println("</body>");
out.println("</html>");
}
else
{
response.sendRedirect("/CSRFTest/error.jsp");
}
}
}
The generated html from this looks like the following.
<html>
<body>
You Made it<br>
<a href="csrfProtectedArea2.jsp?OWASP_CSRFTOKEN=eKKqjn7Pqm6GLvc7bCMoYCwPbpFhXDcQSIKVnvLutX8T&OWASP_CSRFTOKEN=eKKqjn7Pqm6GLvc7bCMoYCwPbpFhXDcQSIKVnvLutX8T" > Click Here </a>
<a href="index.jsp?OWASP_CSRFTOKEN=eKKqjn7Pqm6GLvc7bCMoYCwPbpFhXDcQSIKVnvLutX8T&OWASP_CSRFTOKEN=eKKqjn7Pqm6GLvc7bCMoYCwPbpFhXDcQSIKVnvLutX8T" > Click Here </a>
</body>
</html>
I have two links in this response. One goes back to the login page (index.jsp) and the other goes to another csrf protected area. As you can see the response handler automatically updates all links found in the html with the csrf token. One error I keep getting is that the first link gets 2 tokens added to it. I have not figured this out yet but it does not prevent the application for functioning properly.
CSRFGuard Handles the Attack
One other cool thing about csrfGuard is that if you submit a request with a missing or incorrect token CSRFGuard will invalidate your session and redirect you to the error page defined in the csrfguard.properties file.
One problem I have found with this is in the logging. For instance here is an example log file from a failed csrf request.
Jun 12, 2008 12:12:31 AM org.apache.catalina.core.ApplicationContext log
INFO: [CSRFGuard] the following properties were loaded into CSRFGuard
Debug: true
ResponseHandler: org.owasp.csrfguard.handlers.HTMLParserHandler
Token Name: OWASP_CSRFTOKEN
Token Length: 32
PRNG: SHA1PRNG
Action Count: 3
Action(0) org.owasp.csrfguard.actions.Redirect
Action(1) org.owasp.csrfguard.actions.Log
Action(2) org.owasp.csrfguard.actions.Invalidate
Jun 12, 2008 12:14:28 AM org.apache.catalina.core.ApplicationContext log
INFO: [CSRFGuard] the following properties were loaded into CSRFGuard
Debug: true
ResponseHandler: org.owasp.csrfguard.handlers.HTMLParserHandler
Token Name: OWASP_CSRFTOKEN
Token Length: 32
PRNG: SHA1PRNG
Action Count: 3
Action(0) org.owasp.csrfguard.actions.Redirect
Action(1) org.owasp.csrfguard.actions.Log
Action(2) org.owasp.csrfguard.actions.Invalidate
Jun 12, 2008 12:14:59 AM org.apache.catalina.core.ApplicationContext log
INFO: [CSRFGuard] caught CSRF attack (IP: 0:0:0:0:0:0:0:1 Method: GET URI: %2FCSRFTest%2FcsrfProtectedArea1.do Referer: Parameters: OWASP_CSRFTOKEN%3DPzEIce4raEWbtC97i8oMgf3Y2yJjf1A1XriZ7GcWaLU)
Jun 12, 2008 12:16:36 AM org.apache.catalina.core.ApplicationContext log
INFO: [CSRFGuard] caught CSRF attack (IP: 0:0:0:0:0:0:0:1 Method: GET URI: %2FCSRFTest%2F Referer: Parameters: )
Jun 12, 2008 12:17:00 AM org.apache.catalina.core.ApplicationContext log
INFO: [CSRFGuard] caught CSRF attack (IP: 0:0:0:0:0:0:0:1 Method: POST URI: %2FCSRFTest%2Findex.jsp Referer: http%3A%2F%2Flocalhost%3A8080%2FCSRFTest%2Findex.jsp%3FOWASP_CSRFTOKEN%3DOX7CJcASSH10DsQFhHQy6nIBHbuYWeXRguxeCjLch5Iu Parameters: password%3DGuest1%2COWASP_CSRFTOKEN%3DOX7CJcASSH10DsQFhHQy6nIBHbuYWeXRguxeCjLch5I%2CLogin%3D1%2CloginBtn%3DLogin%2Cusername%3DGuest)
It will log all parameters from the failed request which in this case will log the username and password of the user. Something to consider if deploying CSRFGuard to a production system.
Additional Information
Below are the csrfguard.properties and the web.xml i used.
csrfguard properties file:
org.owasp.csrfguard.Debug=true
org.owasp.csrfguard.ResponseHandler=org.owasp.csrfguard.handlers.HTMLParserHandler
org.owasp.csrfguard.TokenName=OWASP_CSRFTOKEN
org.owasp.csrfguard.TokenLength=32
org.owasp.csrfguard.PRNG=SHA1PRNG
org.owasp.csrfguard.action.class.Log=org.owasp.csrfguard.actions.Log
org.owasp.csrfguard.action.class.Invalidate=org.owasp.csrfguard.actions.Invalidate
org.owasp.csrfguard.action.class.Redirect=org.owasp.csrfguard.actions.Redirect
org.owasp.csrfguard.action.class.Redirect.param.ErrorPage=error.jsp
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<display-name>CSRFTest</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<filter>
<filter-name>CSRFGuard</filter-name>
<filter-class>org.owasp.csrfguard.CSRFGuardFilter</filter-class>
<init-param>
<param-name>config</param-name>
<param-value>WEB-INF/csrfguard.properties</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CSRFGuard</filter-name>
<servlet-name>csrfProtectedArea1</servlet-name>
</filter-mapping>
<filter-mapping>
<filter-name>CSRFGuard</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CSRFGuard</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>csrfProtectedArea1</servlet-name>
<servlet-class>csrfProtectedArea1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>csrfProtectedArea1</servlet-name>
<url-pattern>/csrfProtectedArea1.do</url-pattern>
</servlet-mapping>
</web-app>