Sunteți pe pagina 1din 53

Secrets of Google VRP

A look from a different angle

Krzysztof Kotowicz, Google Security Team.


@kkotowicz
Google VRP

● We receive & process over 600 reports per month


● Some of them point out amazing vulnerabilities
● … and go largely unnoticed in security community
Let’s talk about them!
Today’s focus is on vulnerabilities in Angular applications.
Proprietary + Confidential

Angular security primer


● Sandbox is not a security feature. Removed in 1.6
● Strict contextual autoescaping ($sce) in templates
● HTML sanitization via ngSanitize
● User-controlled templates are XSS
● User-controlled expressions are XSS

<html ng-app>
<body ng-controller="MyController">
<button ng-click="changeFoo()">{{buttonText}}</button>
<script src="angular.js"></script>
</body>
</html>

Source: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis non erat sem
● Angular XSS vectors don’t look like XSS
<div>
{{
x = {'y':''.constructor.prototype};
x['y'].charAt=[].join;$eval('x=alert(1)');
}}
</div>

● They survive most escaping & sanitization techniques


● Most filters are unaware of them
○ WAFs, sanitizers, browser XSS filters
● Protecting Angular applications from XSSes is unique

Source: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis non erat sem
Proprietary + Confidential

Angular in Google VRP


Angular in Google VRP

Of All XSSes reported to Google VRP:

● in 2015 - 7% were specific to Angular applications


● in 2016 - 16%
● We expect this trend to continue
○ Popularity of the platform
○ More awareness in the security community
Types of issues reported

● DOM XSS in Angular directives


● Template mixing issues
○ Server-side
○ Client-side
● Dynamic template compilation
● Double interpolation
● Oddities...
DOM XSS in Angular
directives
DOM XSS in Angular directives

● Interpolating user data via Angular data binding is safe


○ Thanks to $sce the data is contextually escaped
○ E.g. it interpolates into .textContent
● However, Angular directives may use DOM directly
○ .innerHTML
○ .html() etc.
● Regular DOM XSSes are common
○ .innerHTML = user_content
○ .html(user_content)
DOM XSS in Angular directives

Example bug: XSS in firebase.google.com reported by Manish Bhattacharya

Angular application used to manage cloud projects.

● Create files & folders to store in the cloud


● Navigation bar for the filesystem (breadcrumbs)
● XSS in the folder name
Source code analysis

In one of Angular directives - EllipsisDirective...

while (e.scrollWidth > offsetWidth) {


const shortenedText = e.text().trim();

// Figure out how big each side should be


const halfLength =
Math.floor(shortenedText.length / 2) - Math.ceil(trimLength / 2);

// Split string in half


let leftHalf = shortenedText.substr(0, halfLength);
let rightHalf = shortenedText.substr(-halfLength);

e.innerHTML = leftHalf + ELLIPSIS_ + rightHalf;


}
Source code analysis

In one of Angular directives - EllipsisDirective...

while (e.scrollWidth > offsetWidth) {


const shortenedText = e.text().trim();

// Figure out how big each side should be


const halfLength =
Math.floor(shortenedText.length / 2) - Math.ceil(trimLength / 2);

// Split string in half


let leftHalf = shortenedText.substr(0, halfLength);
let rightHalf = shortenedText.substr(-halfLength);

e.innerHTML = leftHalf + ELLIPSIS_ + rightHalf;


}
Root cause & variant analysis

When the folder name is too long, Angular directive:

1. Takes the text content of the node (i.e. full folder name)
2. Shortens it (cuts the middle part)
3. Assigns the result to .innerHTML

This triggers a DOM XSS for long folder names.

Several fixes proposed which underwent verification by the security team.

The final fix assigns the shortened value to text content.


XSS in Angular directives

● Common - just like regular DOM XSS


● Straightforward to understand and fix
● Relatively easy to prevent
○ Code review
■ What data is untrusted?
■ Source-to-sink tracing
○ Banning APIs
■ Developer pushback
○ Automatic scans (with taint tracking)
Template mixing
Server-side template mixing

<script src="angular.js">
<body ng-app>
<div>
<?php echo htmlspecialchars($_GET['foo']); ?>
</div>
</body>

● Widely-known problem for Angular applications


● Surfaced as a result of Mustache-security research by Mario Heiderich
● Exploitation via sandbox escape
● Vulnerability somewhat obvious in the code
Client-side template mixing

<head>
<script src="foo.com/tracking.js"></script>
<script src="angular.js"></script>
</head>
<body ng-app>
<p>Hello world!</p>
</body>
Where’s the bug?
Client-side template mixing

In tracking.js:

var d = document.createElement('div');
d.id = 'tracking_div';
d.setAttribute('data-url', location.href);
document.body.appendChild(d);

● User-controlled source (location.href) added into a sensitive sink


(document.body).

● In DOM XSS, the sinks are .innerHTML etc.

● For Angular applications, the sink is the template i.e. usually the whole
DOM.
Example: Bug in +1 widget

Example bug in widget reported by ramzes600:

https://www.google.com/safetycenter/tools/#?&authuser=1%3F%7B%5Bc=(%5B%5D%2b1).co
nstructor;f=c.fromCharCode(120,61,97,108,101,114,116,40,108,111,99,97,116,105,111
,110,41);x=%7B1:c.prototype%7D;x%5B1%5D.charAt=%5B%5D.join;$eval(f)%5D%7Dssssssss
sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss
sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss
sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss
sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss
ssssssssssssssssssssssssssssssssssssssssssssss[...]sss

https://foo.bar#?&param=1?{{angular_sandbox_escape}}{{padding}}
Example: Bug in +1 widget

● +1 widget creates an iframe for RPC communication


● One of the RPC payloads contains document URL fragment

?&param=1?{{angular_sandbox_escape}}{{padding}}

● When payload is > 2000 characters, it’s stored in <iframe


data-postorigin> attribute
if (2E3 < f.length) {
m["data-postorigin"] = f;
}

● When Angular bootstraps, the sandbox escape is already in the DOM


● Result: XSS in Angular applications using the +1 widget.
Client-side template mixing

● Not apparent from the code (unlike server-side template mixing)


● Occurs when using JS libraries
○ That modify DOM,
○ … adding user-controlled content (XSS-safe!)
○ ... before Angular bootstraps
● Not Angular bug - it’s a feature
● Potential fixes:
○ Move the modifications outside ng-app
○ Use ng-non-bindable for all modified DOM nodes
○ Run after bootstrap

Are we safe if all libraries run after bootstrap?


Not really!

● Angular can compile templates and execute expressions dynamically


○ $compile,
○ $parse,
○ $eval
● Template / expression compiling functions are equivalent to eval() in
DOM XSS
● Untrusted data reaching those functions => XSS
● Not only JavaScript modifies the DOM
Back to the DOM
Back to the DOM

For high severity bugs, Google Security Team:

● Performs a root cause and variant analysis


● Verifies the fix to a vulnerability

This process helps us patch the bug in a correct way and prevent similar
issues in the future. Sometimes we also find interesting variants.

Example: Back to the DOM vulnerability found by Enzo Puig from our team
Back to the DOM

● Browsers themselves remember what you typed in forms previously


● … so that you don’t lose the form contents when you navigate-away
● Hidden form elements value are also preserved
● … and prefilled when loading a page from BFCache (back-forward
cache)

How can this become a vulnerability?


Back to the DOM

1. Server response contains the template:


<input id=foo type=hidden value={{user.name}}>

2. Angular bootstraps, and evaluates the expressions. Some field values are
modified, e.g.
<input id=foo type=hidden value=”Innocent Joe”>
Back to the DOM

1. Server response contains the template:


<input id=foo type=hidden value={{user.name}}>

2. Angular bootstraps, and evaluates the expressions. Some field values are
modified, e.g.
<input id=foo type=hidden value={{Bobby Tables}}>

{{Bobby Tables}} is now just a interpolated string


Back to the DOM

1. Server response contains the template:


<input id=foo type=hidden value={{user.name}}>

2. Angular bootstraps, and evaluates the expressions. Some field values are
modified, e.g.
<input id=foo type=hidden value={{Bobby Tables}}>

{{Bobby Tables}} is now just a interpolated string

3. User navigates away from the page and calls history.back()


4. Browser sets input#foo.value to {{Bobby Tables}}
5. Angular bootstraps…
6. XSS
Double interpolation
Double interpolation

1. The main template is in the DOM (<* ng-app>)


2. It’s interpolated with user data
○ no XSS - contextual autoescaping
○ {{Bobby Tables}} expressions are just strings
3. Resulting HTML is put into DOM again
4. Angular applications takes the data from the DOM and again
interpolates it
○ {{Bobby Tables}} expressions get evaluated => XSS
Double interpolation

Example bug reported to Google VRP by Oren Hafif (@OrenHafif):

1. Go to https://datastudio.google.com
2. Click the (+) plus sign at the bottom right corner of the screen
3. A new report has been created.
4. Rename the report by clicking "Untitled Report"
5. Insert you favorite template injection test expression: e.g. Google
Security {{7777-1111}}. Confirm the report name has been changed.
6. Go to https://datastudio.google.com, hit inspect element and search
for "6666" in the DOM. You will find "Google Security 6666", meaning
our expression was evaluated.
Double interpolation

● There were two double interpolation bugs in


AngularJS Material.
● UI component framework for Angular Apps
● Both patched in version 1.1.2.
Double interpolation

function addAriaLabel(override) {
var rawText = override || element.text().trim();
var interpolatedText = $interpolate(rawText)(parent.scope());
parent.attr('aria-label', interpolatedText);
}

addAriaLabel();

scope.$watch(function() {
return element.text().trim();
}, addAriaLabel);

<a href="{{1338-1}}" aria-label="1337">

http://jsfiddle.net/xvfoc1L5/3/
Double interpolation

https://github.com/angular/material/pull/10159
Double interpolation

https://github.com/angular/material/pull/10190
Double interpolation

● Common in Angular
apps / libs
● Look for
$interpolate, $watch
● Hard to prove
exploitability in library
code only
● Not obvious what is
user-controlled

github.com/angular/material/pulls?q=is:pr+xss
Angular oddities
Angular oddities

Some reports point to very peculiar bugs in Angular framework itself.

Example: XSS in www.google.com reported by ramzes600:

https://www.google.com///intl/images;%7B%5Bx=%7B"y":"".constructor
.prototype%7D;x%5B"y"%5D.charAt=%5B%5D.join;$eval("x=alert(documen
t.domain)");%5D%7D/work/solutions.html
Angular oddities

It’s usually /intl/en-US


/images is not even Angular!
Multiple slashes?

https://www.google.com///intl/images;%7B%5Bx=%7B"y":"".constructor
.prototype%7D;x%5B"y"%5D.charAt=%5B%5D.join;$eval("x=alert(documen
t.domain)");%5D%7D/work/solutions.html

Angular application URL


Sandbox escape
Simplifying the vector...

● Let’s look at the Angular application that initiates the XSS

https://www.google.com///intl/foo/work/solutions.html

<div ng-include="'../work/contact-form/root.html'"></div>

● Should load
https://www.google.com///intl/foo/work/contact-form/root.html
● Instead it loads
https://www.google.com/foo/work/contact-form/root.html

● https://foo.bar///a/b/c => https://foo.bar/b/c


Angular URL parsing

function parseAppUrl(relativeUrl, locationObj) {


var prefixed = (relativeUrl.charAt(0) !== '/');
if (prefixed) {
relativeUrl = '/' + relativeUrl;
}
var match = urlResolve(relativeUrl); // Creates <a> element.
// Set the path, search and hash properties of locationObj
}
Angular URL parsing

function parseAppUrl("//a/b/c", locationObj) {


var prefixed = ("//a/b/c".charAt(0) !== '/');
if (prefixed) {
// Nope.
}
var match = urlResolve("//a/b/c"); // Creates <a> element.
// Set the path, search and hash properties of locationObj
}
// triggers RPO-like bug in Angular

● Similar to Relative-Path-Override by Gareth Heyes


http://www.thespanner.co.uk/2014/03/21/rpo/
● /// causes Angular to parse a protocol-relative URLs
● The top directory becomes the (ignored) hostname
● This may be used to load a template from a different directory
● Still not enough to XSS. Template would have to be user-controlled
● In our case the template loaded is:

https://www.google.com///intl/images;{{sandbox-escape}}
/ignored…
The final template

https://www.google.com/images;{{angular-expression}}/ignored...

● It returns https://google.com/images page (; is a path separator)


● Content after ; ignored when searching for the handler
● Regular HTML page, same origin with the embedding page
● Reflects its URL in the response
● Correctly escapes XSS vectors
● ...But not Angular expressions

User controlled template => XSS (here via sandbox escape)


Triple-slashes

What caused the XSS?

● URL parsing issue in Angular


○ RPO-like, via protocol-relative URLs
● Ambiguous URL matchers server side (probably common)
● Loading templates at runtime
● XSS-safe reflection in server responses (universal)
Triple-slashes - Remediation

● Stop-gap: Hardened some URL mappings


○ E.g. www.google.com/intl/foo is much more limited now.
● Fixed the URL parsing bug in AngularJS 1.5.9
○ https://github.com/angular/angular.js/pull/15365

$location: throw if the path starts with double (back)slashes

● Google-wide upgrade of Angular version (not trivial!)

● For most applications, all templates are bundled statically


Proprietary + Confidential

Summary
Summary

● Angular enables non-standard XSS vulnerabilities


○ i.e. no <script>alert(1)</script>
● Some vulnerabilities are hard to spot in the code
● Variant & root cause analysis helps us find additional vectors
● Remediation is sometimes hard - not even clear what is a proper fix

● Our VRP researchers are great! Thank you!!!111


Additional references

● Sebastian Lekies, Securing AngularJS applications


● Sebastian Lekies, AngularJS security
● Mario Heiderich, Mustache-Security
● Angular Sandbox removal
● Angular 2+:
○ Angular 2 security docs
○ Raphaël Jamet, Martin Probst, Angular 2 security

S-ar putea să vă placă și