It’s Friday, and time again for some Friday Fixes: selected problems I encountered during the week and their solutions.
This week’s challenges ran the gamut, but there’s probably not much broad interest in consolidated posting for store-level no advice chargebacks, image format and compression conversion, SQLs with decode(), 798 NACHA addenda, or many of the other crazy things that came up. So I’ll stick to the web security vein with a CSRF detector I built.
Sea Surf
If other protections (like XSS) are in place, meaningful Cross-Site Request Forgery (CSRF) attacks are hard to pull off. But that usually doesn’t stop the black hats from trying, or the white hats from insisting you specifically address it.
The basic approach to preventing CSRF (“sea surf”) is to insert a synchronizer token on generated pages and compare it to a session-stored value on subsequent incoming requests. There are some pre-packaged CSRF protectors available, but many are incomplete while others are bloated or fragile. I wanted CSRF detection that was:
- Straightforward – Please, no convoluted frameworks nor tons of abstracted code
- Complete – Must cover AJAX calls as well as form submits, and must vary the token by rendered page
- Flexible – Must not assume a sequence of posts or limit the number of AJAX calls from one page
- Unobtrusive – Must “drop in” easily without requiring @includes inside forms.
I also wanted to include double submit protection, without having to add another filter (certainly no PRG filters – POSTs must be POSTs). Here below is the gist of it.
First, we need to insert a token. I could leverage the fact that nearly all of our JSPs already included a common JSPF file, so I just added to that. The @include wasn’t always inside a form so I added the hidden input field via JavaScript (setToken). I used a bean to keep the JSPF as slim as possible.
<jsp:useBean id="tokenUtil" class="com.writestreams.TokenUtil"> <% tokenUtil.initialize(request); %> </jsp:useBean> <script> var tokenName = '<c:out value="${tokenUtil.tokenName}" />'; var tokenValue = '<c:out value="${tokenUtil.tokenValue}" />'; function setToken(form) { $('<input>').attr({ type: 'hidden', id: tokenName, name: tokenName, value: tokenValue }).appendTo(form); } $("body").bind("ajaxSend", function(elm, xhr, s){ if (s.type == "POST") { xhr.setRequestHeader(tokenName, tokenValue); } }); </script> |
I didn’t want to modify all those $.ajax calls to pass the token, so the ajaxSend handler does that. The token arrives from AJAX calls in the request header, and from form submits as a request value (from the hidden input field); that gives the benefit of being able to distinquish them. You could use a separate token for each if you’d like.
The TokenUtil bean is simple, just providing the link to the CSRFDetector.
public class TokenUtil { private String tokenValue = null; public void initialize(HttpServletRequest request) { this.tokenValue = CSRFDetector.createNewToken(request); } public String getTokenValue() { return tokenValue; } public String getTokenName() { return CSRFDetector.getTokenAttribName(); } } |
CSRFDetector.createNewToken generates a new random token for each page render and adds it to a list stored in the session. It does JavaScript-encoding in case there are special characters.
public static String createNewToken(HttpServletRequest request) { HttpSession session = request.getSession(false); String token = UUID.randomUUID().toString(); addToken(session, token); return StringEscapeUtils.escapeJavaScript(token) } |
A servlet filter (doFilter) calls CSRFDetector to validate incoming requests and return a simple error string if invalid. You can limit this to only validating POSTs with parameters, or extend it to other requests as needed. The validation goes like this:
public String validateRequestToken(HttpServletRequest request) { HttpSession session = request.getSession(false); if (session == null) return null; // No session established, token not required String ajaxToken = decodeToken(request.getHeader(getTokenAttribName())); String formToken = decodeToken(request.getParameter(getTokenAttribName())); if (ajaxToken == null && formToken == null) return "Missing token"; ArrayList<String> activeTokens = getActiveTokens(session); if (ajaxToken != null) { // AJAX call - require the token, but don't remove it from the list // (allow multiple AJAX calls from the same page with the same token). if (!activeTokens.contains(ajaxToken)) return "Invalid AJAX token"; } else { // Submitted form - require the token and remove it from the list // to prevent any double submits (refresh browser and re-post). if (!activeTokens.contains(formToken)) return "Invalid form token"; activeTokens.remove(formToken); setActiveTokens(session, activeTokens); } return null; } |
There you have it. There are several good tools available for testing; I recommend OWASP’s CSRFTester.
Linkapalooza
Some useful / interesting links that came up this week:
- Grep Console – Eclipse console text coloring (but no filtering)
- Springpad – Think Evernote++
- Pocket – New name for ReadItLater
- RFC 2616 – The HTTP 1.1 spec at, well, HTTP