Gleb Bahmutov PRO
JavaScript ninja, image processing expert, software quality fanatic
You can have your
website hacked but only once.
What if a tree's password is stolen in the forest and no one hears it?
curl -I https://github.com/
$ curl -I https://github.com/
HTTP/1.1 200 OK
Server: GitHub.com
Date: Thu, 21 Jan 2016 00:23:13 GMT
Content-Type: text/html; charset=utf-8
Status: 200 OK
Cache-Control: no-cache
Vary: X-PJAX
X-UA-Compatible: IE=Edge,chrome=1
Set-Cookie: logged_in=no; domain=.github.com; path=/; expires=Mon, 21 Jan 2036 00:23:13 -0000; secure; HttpOnly
X-Request-Id: 49ac67736ceec9bf127572e7c42c8235
X-Runtime: 0.007182
Content-Security-Policy: default-src *; base-uri 'self';
connect-src 'self' live.github.com wss://live.github.com uploads.github.com status.github.com
api.github.com www.google-analytics.com api.braintreegateway.com client-analytics.braintreegateway.com
github-cloud.s3.amazonaws.com; font-src assets-cdn.github.com; form-action 'self' github.com
gist.github.com; frame-src 'self' render.githubusercontent.com gist.github.com checkout.paypal.com;
img-src 'self' data: assets-cdn.github.com identicons.github.com www.google-analytics.com
checkout.paypal.com collector.githubapp.com *.githubusercontent.com *.gravatar.com *.wp.com;
media-src 'none'; object-src assets-cdn.github.com; script-src assets-cdn.github.com;
style-src 'self' 'unsafe-inline' 'unsafe-eval' assets-cdn.github.com
Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
Public-Key-Pins: max-age=300; pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="JbQbUG5JMJUoI6brnx0x3vZF6jilxsapbXGVfjhN8Fg="; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: deny
X-XSS-Protection: 1; mode=block
Vary: Accept-Encoding
X-Served-By: d0e230454cb69aa01d4f86fc3a57b17f
X-GitHub-Request-Id: AC552DBA:3AD3:FB489A:56A024F1
Content-Security-Policy:
default-src *;
script-src assets-cdn.github.com;
object-src assets-cdn.github.com;
style-src 'self' 'unsafe-inline' 'unsafe-eval' assets-cdn.github.com;
img-src 'self'
data: assets-cdn.github.com www.google-analytics.com ...;
media-src 'none';
frame-src 'self'
render.githubusercontent.com gist.github.com www.youtube.com ...;
font-src assets-cdn.github.com;
connect-src 'self' live.github.com ...;
base-uri 'self';
form-action 'self' github.com gist.github.com
Name:
John Doe
<span>{{ name }}<span>
<span>John Doe<span>
Name:
<script>alert('hi')</script>
<span>{{ name }}<span>
<span><script>alert('hi')</script><span>
innerText or innerHTML?
innerText or innerHTML?
Dangerous, but has styling, elements, etc
Really dangerous if one user's content
can be viewed by another user
Will a script embedded in the user-entered content be executed (for another user)?
NEVER EVER EVER?
My content
User comments
Ads, etc
<script>stealStuff()</script>
Angular 1 edition (for Jeff)
Examples where we can embed script code into {{ }} template
{{constructor.constructor('alert(1)')()}}
fixed in Angular 1.2
{{ (_=''.sub).call.call({}[$='constructor'].getOwnPropertyDescriptor ( _.__proto__,$).value,0,'alert(1)')() }}
{{ 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;'' }}
{{ 'this is how you write a number properly. also, numbers are basically arrays.'; 0[['__proto__']].toString = [][['__proto__']].pop; 0[['__proto__']][0] = 'alert("TROLOLOLn"+document.location)'; 0[['__proto__']].length = 1; 'did you know that angularjs eval parses, then re-stringifies numbers? :)'; $root.$eval("x=0", $root); }}
Still in ng 1.4.5
<body>
<script>function willNotRun() {...}</script>
<script src="assets/js/script.js"></script>
</body>
var el = document.createElement('script');
el.innerText = 'alert("hi there");'
document.body.appendChild(el); // runs the code by default
<meta http-equiv="Content-Security-Policy" content="
script-src https://code.jquery.com 'self';
">
Wide support for CSP (browsers, web apps)
app.get('/', function (req, reqs) {
res.render('index', {
title: 'Example',
analyticsId: '4xy-0123456'
});
});
head
title #{ title }
script.
var analyticsId = '#{ analyticsId }';
script.
// use variable analyticsId
// analytics-config.js
var analyticsId = '4xy-0123456';
// analytics.js
// included after analytics-config.js
// uses variable analyticsId to init page analytics
head
meta(http-equiv="Content-Security-Policy",
content="script-src 'self';")
title #{ title }
script(src="js/analytics-config.js")
script(src="js/analytics.js")
// views/config.js
module.exports = {
analyticsId: 'default-id'
};
// server.js
var express = require('express');
var app = express();
var jsToJs = require('js-to-js');
app.engine('js', jsToJs);
app.get('/js/analytics-config.js', function (req, res) {
res.setHeader('content-type', 'application/javascript');
res.render('config.js', {
analyticsId: '4xx-xxxxx'
});
});
// js/config.js
var analyticsConfig = {
"analyticsId": "4xx-xxxxx"
};
User receives
// views/config.js with a function
module.exports = function (options) {
initAnalytics(options.userId);
};
// server.js
var express = require('express');
var app = express();
var jsToJs = require('js-to-js');
app.engine('js', jsToJs);
app.get('/js/config.js', function (req, res) {
res.setHeader('content-type', 'application/javascript');
res.render('config.js', {
userId: userId
});
});
(function (options) {
initAnalytics(options.userId);
}({ userId: 'xx-yy-bb' }));
User receives
var jsToJs = require('js-to-js');
app.get('/js/config.js',
jsToJs('demoConfig', { foo: 42, bar: 21 }));
User receives
// js/config.js
var demoConfig = { foo: 42, bar: 21};
NPM auth token, GitHub tokens, passwords, SSH keys, etc
By Gleb Bahmutov
This presentation will show how to lock down the front end JavaScript code using Content-Security-Policy. I will also show how to prevent sensitive files from being committed to your repos or NPM registry
JavaScript ninja, image processing expert, software quality fanatic