Coinbase AngularJS DOM XSS via Kiteworks

8:45 AM



In this post, I will explore a dom based angular XSS I found in Coinbase and explain why the AngularJS bypass actually executes client side code.
AngularJS is a popular JavaScript framework that allows embedding expressions within double curly braces. For example, the expression {{2+2}} will render as 4, In angular, you can also use $eval('1+1') to evaluate anything, its same as {{1+1}}
We could try providing user-input of {{alert('hi')}}, but that would be blocked by Expression Sandboxing. that might seem like we are safe. However, despite appearing as a security feature, it was not really supposed to be for sandboxing anything for security.
The subdomain of coinbase, filetransfer.coinbase.com was running a service called Kiteworks that is developed by Accellion. by using angular.version on the site, we can see it's running AngularJS 1.4.7.
So lets submit {{alert('hi')}} and see why Angular doesn't execute the code. In earlier versions of AngularJS (<=1.0.8) , the bypasses were simpler.
Assume an Object a with properties b and c. (a = { b: 1, c: 2 }) -- we can check a has the property b by using the function hasOwnProperty('b') which shall return true. and a has automatic magic functions like toString() so functions are just properties of an object. which can be accessed using a.toString() or a["toString"](), in angular, the getterFn function creates a new function which tries to check the property alert from object before execute it.
As you may know getterFn creates a function that tries to get the property alert of the object. and Angluar got a scope that predefines everything that should be valid or not.  so accessing the scope of Angular with alert scope.hasOwnProperty('alert') shall return false or in angulars case becomes noop; which is just an empty function.  so basically, get property, understand and parse the evaluation and check scope and call it as a function.
This javascript sandbox will link {{document.window}} to scope.document which shall return false, {{document.confirm}} as scope.confirm, which is also false. something like {{name}} would be evaluated if it's known by the scope. which basically means we can't access global functions.
In earlier Angular versions,  breaking out of the scope object was not very complicated because the scope object is like any other javascript object. it automatically got magic functions like constructors. and the constructor of a scope object is object constructor.
As you probably have figured out, the constructor of the object constructor is a Function, which is the highest constructor in javscript. and we can create an arbitrary javascript function using the constructor, which will call a new javascript function. and we will get an alert by calling the highest constructor with an alert. we are simply doing:
scope.hasOwnProperty('constructor') -- evaluating true, which means by using constructor of the constructor of any object, we should be able to create a function with alert and execute that.
Angualr < =1.0.8: scope.constructor.constructor("alert(0)")() equivalent to {{constructor.constructor("alert(0)")()}} as each object gets attached to the scope anyways.
This was a clever and simple way found by Mario Heiderich to bypass the sandbox.
Now notice this payload is fixed and doesn't work on Angular 1.4.7, sending the payload and debugging and we can see that angular refuses to work with a function constructor.
As a fix and feature, Angular implemented lexical analysis, which is the process of converting a sequence of characters into a sequence of tokens, used in the process of compiling.
AngularJS implements a compiler that takes an angularjs expression and complies it to real javascript. A compiler in angular, compiling expressions to javascript. It uses the lexer function parser as a string and extract tokens (identifiers) from it using the while loop below.
lex: function(text) {
this.text = text;
this.index = 0;
this.tokens = [];
while (this.index < this.text.length) {
  var ch = this.text.charAt(this.index);
  if (ch === '"' || ch === "'") {
The while loop iterates the full length of the text, and in each iteration it uses text.CharAt() to get the next character of the string. And checks for single or double quotes because those are usually the start & end of a string. And after that the lex is just a load of helper functions trying to compile and help the lex and return an array of tokens.
If we try accessing the tokens after compiling, (this.tokens) they are just objects. the first one (this.tokens[0]) is the first character string because it’s a fixed string (text) if it starts with a quote or double quote. we can then use an identifier (another token) of the string.
Now notice while running the earlier sandbox bypass it doesn’t work and if you debug it you can see the error is coming from a new function called ensureSafeObject:
function ensureSafeObject(obj, fullExpression) {
...
 if (obj) {
if (obj.constructor === obj) {
This is a clever way to check if the param coming as obj is the function constructor which means we will keep ending up in the function of what we are accessing instead of the highest object despite how many constructors we add.
Note that every string we use is a decedent from the String() object. As prototype in javascript can be used to refer to the actual method or function that is inherited to all string objects.  If we access a string's constructor prototype, it will give us access to the String constructor.


By accessing a string's constrcutor, ('a'.constructor) we will call ensureSafeObject to check if the value is safe, and since it’s a string constructor, it is considered safe which will bypass ensureSafeObject.
We can then access the string constructor's prototype to access the main constructor. Defeating the whole purpose of angular's safechecks. We can then access the charAt the String constructor with a value that doesn't exist and assign it a built in conjuction function (join, concat...) which will also call ensureSafeObject as a text(string) consturctor, it is safe yet again. Then angular assigns that to an object using ensureSafeAssignContext. Since the checks are done using charAt, we would need to break it and redefine it to our own prefered unity function to chain our required payload.
We can reference the charAt function to all the other javascript functions inherit and assign a variable or function. We then can try to crash or redefine the charAt function because Angular uses charAt multiple times while calling checks and assigning expressions before compiling.
Final bypass: {{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };alert(1)//');}} by @garethheyes


Kiteworks, implemented by Coinbase was evaluating user-names sent with post using Angular. Which caused this AngularJS DOMXSS in https://filetransfer.coinbase.com




P.S: in the video, you can see a different sandbox bypass payload, this is because after initial report, they just updated the AngularJS version from 1.2.7 (when I recorded) to 1.4.7 as a fix but that one is also bypassed.

Angular starting from 1.6 have stopped the sandbox and one can easily break out of it using multiple ways.





Exploiting odd behaviors in MS Edge & IE to bypass Facebook’s Linkshim

2:32 PM


The following post is inspired by an all charset unicode shazz by @insertscript. After I saw it being referred in a tweet by Gareth Heyes, I immediately started fuzzing for more characters Internet Explorer & MS Edge may interpret horribly, and confuse the anchor host parser to ultimately use and bypass Facebook's Linkshim.

Fuzzing:

<a href=”/[$]example.com” id=”fuzzmeiah”>fuzz</a>
<script>

chars = [];
for(i=0;i<=0xffff;i++){
    if(document.getElementById('fuzzmeiah').hostname==example.com) {
      chars.push(i);
      console.log(chars.join('\n'))
     }
}

</script>

Result:


Character 1: 〱- 'VERTICAL KANA REPEAT MARK' (Ux3031)


The first one works for both IE & Edge is 0xE3 0x80 0xB1 (e380b1 aka 〱) , when plugged alone or after a forward slash, confusing the parsers to misinterpret the hostname.


<a href=”/〱example.com” id=”fuzzmeiah”>fuzz</a>


This can be tested in both MS Edge and Internet Explorer by changing links Facebook expects to be relative URIs and thus not passing them to a linkshim validation endpoint, allowing redirection to a blacklisted site (in this example, user must click on continue or cancel)




Character 2: 〵- 'VERTICAL KANA REPEAT MARK LOWERHALF' (Ux3035)




Character 3: ゝ- 'HIRAGANA ITERATION MARK' (0x309d)




Character 4: ー 'KATAKANA-HIRAGANA PROLONGED SOUND MARK' (0x30fc)



Character 5: ー 'HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK' (0xff70)




This specific issue is not fixed by Facebook as they believe it is more of the browser vendors fault and shouldn't be fixed from their side. Even though I disagree with their decision, I believe a very few percent of Facebook users use IE or Edge so I didn't bother trying to change their mind. I hope this post inspires either Microsoft or Facebook to fix this issue though.

I hope you enjoyed the read. =)

Why CSP Should be carefully crafted: Twitter XSS & CSP Bypass

3:09 AM


Few months back, I came across an oauth xss accompanied by a nice CSP bypass in Twitter. While creating an application, a developer can set their terms and service URL for their app, which Twitter configured to be: ([https?:])\w+

Unfortunately the regexp is missing a ^ char in the start  making malicious URLs like data:CONTENT#https:// work -- so we got HTML Injection, but almost useless for a practical attack because of the CSP rules. After checking the header, I noticed there are multiple CSP misconfigurations in the script-src and object-src blocks, making it possible to bypass CSP in twitter.com. The CSP Rule looks like:

script-src https://connect.facebook.net https://cm.g.doubleclick.net https://ssl.google-analytics.com https://graph.facebook.com https://twitter.com 'unsafe-eval' ‘unsafe-inline’ https://*.twimg.com https://api.twitter.com https://analytics.twitter.com https://publish.twitter.com https://ton.twitter.com https://syndication.twitter.com https://www.google.com;frame-ancestors 'self';object-src https://twitter.com https://pbs.twimg.com; default-src 'self';...

Looking at this, the object-src and the script-src blocks got my immediate attention.
After some research, I saw one of the trusted domains (cdn.syndication.twimg.com aka syndication.twitter.com) hosts JSONP endpoints.

Originally I thought, by exploiting the object-src block (https://pbs.twimg.com) --  one can upload a Flash file (as picture/video extension with few bytes header) to Twitter CDN -- refer it to as an embedded Object to gain code execution. However, because of character limitation, the payload I was trying to make was too long and being cut off, so this method wasn't practical as we were working on a limited payload space. At this point, I sticked to the JSONP bypass for the script-src blocks and started playing with multiple parameters until I found a shorter version, when injected generating an alert in twitter.com.

http://syndication.twitter.com/widgets/timelines/246079887021051904?dnt=true&domain=twitter.com&lang=en&callback=alert

The above JSONP response from syndication.twitter.com comes back with a Content-Disposition header forcing a download. However, browsers like Chrome still execute the returned file even when returned as an attachment. At this point, this misconfiguration added with the ‘unsafe-inline’ CSP block -- meant we are able to execute code.

By setting the Terms & Services URL of an App to


A developer will be able to pop-up an alert.

POC

After some digging I noticed ssl.google-analytics.com, www.google.com and even graph.facebook.com host JSONP endpoints -- which I wrote to twitter over email -- but will not be fixed anytime soon because it may break the sites usage and call to these sites and performance.

Edit: Ben Hayak mentioned we can use same origin method execution (SOME) attack to manipulate the page as we like: https://syndication.twitter.com/widgets/timelines/246079887021051904?callback=document.body.firstElementChild.Reference.submit -- as used by my Instagram XSS.

I hope it was a fun read,  :) --