Monday, October 29, 2012

Anti-CSRF Token Detection for Fun and Breaking Code

Detecting CSRF countermeasures is an important feature for penetration testing tools of web applications. It helps to detect CSRF issues, as well as to indicate the spider that the targeted request has to be carried out with the secret token in order to perform it successfully.

ZAP currently bases its anti-csrf token detection mechanism on a static list of known tag names, which will generate false negatives in real world apps. I thought it could be funny to enhance the ZAP mechanism by attempting to build a dynamic, behavior-based, approach.

In this post, I will describe the approach I designed and show its results. There is an interesting thread in the ZAP development group about it.


Algorithm Description

I implemented my approach as a ZAP 1.4 extension, which is currently available in the main codebase. There is no plugin jar file available yet. If you want to test it, you will need to either compile it by yourself or to ping me and I'll send it to you.

The algorithm works as follows. For each page, it first checks whether the page has input parameters (user-controllable data) or not. If so, the page potentially performs a critical action, and therefore, it might be vulnerable to CSRF attacks. Otherwise, the page does not perform security critical actions and performing a CSRF attack does not produce any impact.

For each potential vulnerable page, the scanner will attempt to determine whether there is a anti-csrf token or not. To achieve this, the scanner creates a hash map of the input hidden fields of the targeted page. Then, it starts a new session, requests again the page and matches the new input hidden fields with the previous ones. If there is a input hidden field whose value changed in the second request and it passes a set of tests to avoid false positives, then the field will be marked as an anti-csrf token, and consequently, the page will not be marked as vulnerable to CSRF attacks. Otherwise, it will.
The interesting part of the algorithm is the one that attempts to determine which ones of the hidden input fields are actually anti-csrf tokens. To achieve this, it performs thw following checks:

  • Entropy estimation: After performing the second request, it is carried out a randomness estimation (by computing the Levenshtein distance ratio) of both of the token values. If the resulting ratio is lower than a determined threshold, then the token is either not random enough or not an anti-csrf token. This will avoid false positives in fields that have similar behavior to anti-csrf tokens such as counters.
  • Request status: I implemented another function that tries to determine if the second request (without cookies) is either properly carried out or the server is redirecting us to the login page. I perform this check by looking at the HTTP code status.
  • Session Id check: The value is compared with the session id to avoid missdetermining it as an anti-csrf token.

Regarding performance, the plugin only performs one HTTP request on pages that have input tags, so it does not perform many extra requests.



Experimentation

I carried out experiments in both specific anti-csrf frameworks and real world apps.

The first target was NoCSRF, a framework available at http://bkcore.com/blog/code/nocsrf-php-class.html

In the following code block, I show the results of the ZAP plugin in debugging mode:
INFO org.parosproxy.paros.core.scanner.HostProcess-start host http://aopcgr.uab.es:10001 | Csrftokenscan level HIGH

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan-The page has parameters marked temporary vulnerable

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan-Input Tag: csrf_token, MTM1MTUwNjQ4MGJjQUQ0cko2bURvRHBIdlFVQ25xZm5xUGF5RmNuNTBB

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan-Input Tag: csrf_token, whateverkey

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan -The distance between "whateverkey" and "MTM1MTUwNjUxMUp1aVFzcTBsaDZVUUlMakc5dk1JQTlUUmRVcXAzUXVO" is 53 and their ratio is: 0.9464285714285714, random enough

 DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- "Found Anti-CSRF token: csrf_token, MTM1MTUwNjUxMUp1aVFzcTBsaDZVUUlMakc5dk1JQTlUUmRVcXAzUXVO"

INFO org.parosproxy.paros.core.scanner.HostProcess-completed host/plugin http://aopcgr.uab.es:10001 | Csrftokenscan in 0.087s

INFO org.parosproxy.paros.core.scanner.HostProcess-completed host http://aopcgr.uab.es:10001 in 0.111s

As we can see, it successfully detects the csrf_token input field as an anti-csrf token without producing any false positive.

The second framework tested was bodgeit, available at http://code.google.com/p/bodgeit/
INFO org.parosproxy.paros.core.scanner.HostProcess - start host http://localhost:10001 | Csrftokenscan level HIGH

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan - The page has parameters marked temporary vulnerable

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- Input Tag: null,

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- Input Tag: anticsrf, 0.02386741959595151

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- The distance between "0.02386741959595151" and "0.45802788970587016" is 14 and their ratio is: 0.7368421052631579, random enough

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- "Found Anti-CSRF token: anticsrf, 0.45802788970587016"

In this framework we also detected the anti-csrf tokem, called anticsrf, and got none false positives.

The next targeted page I tested was Facebook:
DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- The page has parameters marked temporary vulnerable

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- Input Tag: lsd, AVpYQ7RX

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- Input Tag: default_persistent, 0

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- Input Tag: next, http://www.facebook.com/directory/people/A-80081761-80772120

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- Input Tag: charset_test, €,´,€,´,水,Д,Є

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- Input Tag: timezone,

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- Input Tag: lgnrnd, 020036_NZ89

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- Input Tag: lgnjs, n

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- Input Tag: locale, es_ES

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- Input Tag: o, 2048

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- Input Tag: init, dir200

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- The distance between "AVogYVkR" and "AVqTjOmD" is 6 and their ratio is: 0.75, random enough

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- "Found Anti-CSRF token: lsd, AVqTjOmD"

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- The distance between "020038_ST8S" and "020208_v1NR" is 6 and their ratio is: 0.5454545454545454, random enough

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- "Found Anti-CSRF token: lgnrnd, 020208_v1NR"

The plugin found an anti-csrf token called lsd in several pages, googling a bit I found that it is actually a csrf countermeasure http://www.pallmallmedical.co.uk/cookies I could not find information in google about the lgnrnd parameter but it also looks like a random anti-csrf token.

As we can see in Figure 1, this countermeasure was implemented in all the facebook pages.
Figure 1. CSRF countermeasures in facebook


The last page to test was gmail, and these are the results:
DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- The page has parameters marked temporary vulnerable

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- Input Tag: continue,http://mail.google.com/mail/

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- Input Tag: service, mail

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- Input Tag: dsh, 7965555468608615868

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- Input Tag: ltmpl, default

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- Input Tag: GALX, Ldh-U9oUXrc

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- Input Tag: timeStmp,

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- Input Tag: secTok,

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- Input Tag: rmShown, 1200

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- The distance between "7965555468608615868" and "2970808815108171516" is 14 and their ratio is: 0.7368421052631579, random enough

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- "Found Anti-CSRF token: dsh, 2970808815108171516"

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- The distance between "Ldh-U9oUXrc" and "NywfjREgMcg" is 11 and their ratio is: 1.0, random enough

DEBUG org.zaproxy.zap.extension.csrftokenscan.Csrftokenscan- "Found Anti-CSRF token: GALX, NywfjREgMcg"

I also had to find documentation in google about what inoput fields are actually anti-csrf tokens. I found that dsh is a random anti-csrf token as explained in http://nagarun.wordpress.com/2011/07/02/my-mail-to-google-on-csrf/ On the other hand, GALX is a random anti-csrf token as explained in http://userscripts.org/topics/36200 and http://nagarun.wordpress.com/2011/07/02/my-mail-to-google-on-csrf/

As we show in Figure 2, all the gmail pages have this countermeasure integrated, except the about&contact ones that do no perform any critical action.

Figure 2. CSRF countermeasures in gmail.

We think that the results for this first approach are quite good, and we plan to perform more tests and enhance its results. Any feedback on this is very welcome.

No comments:

Post a Comment