Securing a JavaScript based Web Applicaiton
Erlend Oftedal
—
@webtonull
Web Rebels
Who am I?
But first...
_=[]|[];$=_++;__=(_<<_);___=(_<<_)+_;____=__+__;_____=__+___;$$=({}+"")[_____]+({}+"")[_]+({}[$]+"")[_]+(($!=$)+"")[___]+(($==$)+"")[$]+(($==$)+"")[_]+(($==$)+"")[__]+({}+"")[_____]+(($==$)+"")[$]+({}+"")[_]+(($==$)+"")[_];$$$=(($!=$)+"")[_]+(($!=$)+"")[__]+(($==$)+"")[___]+(($==$)+"")[_]+(($==$)+"")[$];$_$=({}+"")[_____]+({}+"")[_]+({}+"")[_]+(($!=$)+"")[__]+({}+"")[__+_____]+({}+"")[_____]+({}+"")[_]+({}[$]+"")[__]+(($==$)+"")[___]; ($)[$$][$$]($$$+"('"+$_$+"')")()
Yosuke Hasegawa - 472 bytes
OWASP Top 10
- A1
- Injection
- A2
- Cross Site Scripting (XSS)
- A3
- Broken Authentication and Session Management
- A4
- Insecure Direct Object Reference
- A5
- Cross Site Request Forgery (CSRF)
- A6
- Security Misconfiguration
- A7
- Insecure Cryptographic Storage
- A8
- Failure to Restrict URL Access
- A9
- Insufficient Transport Layer Protection
- A10
- Unvalidated Redirects and Forwards
A10 - Unvalidated Redirects and Forwards
A10 - Unvalidated Redirects and Forwards
http://example.com/redirect?url=http://evil.com
http://example.com/login?url=http://evil.com
Twitter september 2010
(function(q) {
var a = location.href.split("#!")[1];
if (a) {
g.location = a;
}
})
https://twitter.com/#!/web_rebels
↓
https://twitter.com/web_rebels
https://twitter.com/#!http://evil.com
↓
http://evil.com
http://blog.mindedsecurity.com/2010/09/twitter-domxss-wrong-fix-and-something.html
Fixing Unvalidated Redirects and Forwards
- Sanitize or white-list your redirect locations
- Do it properly
A9 - Insufficient Transport Layer Protection
http://www.flickr.com/photos/edgoodwin/
A9 - Insufficient Transport Layer Protection
- If your application has private data - always use
https:
- Lock down your SSL/TLS config - SSL Server Test
A8 - Failure to restrict URL access /
A4 - Insecure Direct Object Reference
http://www.flickr.com/photos/56369179@N00/
A8 - Failure to restrict URL access /
A4 - Insecure Direct Object Reference
- Vertical access control - roles
- Horisontal access control - data access
- Client side access control is no substitute for server side access control
A7 - Insecure Cryptographic Storage
http://xkcd.com/538/
A7 - Insecure Cryptographic Storage
- Browser storage
- Deleted on logout?
- Deleted when closing browser?
- localStorage - stored untill deleted
- sessionStorage - deleted on browser exit/restart
- Cache/Offlinerising (Hello @jaffathecake)
Server side crypto
A6 - Security Misconfiguration
http://failblog.org/2008/01/03/fail-camera/
A6 - Security Misconfiguration
Open crossdomain.xmls
Alexa top 100 local domains:
Tested 18. mai 2012
What about Access-control-Allow-* ?
Access-Control-Allow-From: *
- Allow browser to read response regardles of origin
Access-Control-Allow-Credentials: true
- Allow browser to include credentials in request
- Let's Shodan it
And now...
$=[$=[]][(!$+'')[-~-~-~$]+({}+$)[+!'']+($$=(!''+$)[+!''])+(_=(!+$+$)[+$])],$()[(!$+$)[+!'']+(!$+'')[-~-~$]+(!''+'')[-~-~-~$]+$$+_](+!'')
Gareth Heyes - 136 bytes
A5 - Cross Domain Request Forgery
Image source: http://www.philatelicdatabase.com/forgeries/stamp-forgeries-1951/
A5 - Cross Domain Request Forgery
Does your server know if the request originated from a page or javascript delivered by itself?
CSRF - Is it really a problem?
CSRF + Cross Domain XHR
function fileUpload(url, fileData, fileName) {
var fileSize = fileData.length;
var boundary = "xxxxxxxxx";
var xhr = new XMLHttpRequest();
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-Type",
"multipart/form-data, boundary="+boundary);
xhr.setRequestHeader("Content-Length", fileSize);
var body = "--" + boundary + "\r\n";
body += 'Content-Disposition: form-data; name="contents"; filename="'
+ fileName + '"\r\n';
body += "Content-Type: application/octet-stream\r\n\r\n";
body += fileData + "\r\n";
body += "--" + boundary + "--";
xhr.send(body);
return true;
}
http://blog.kotowicz.net/2011/04/how-to-upload-arbitrary-file-contents.html
Use anti forgery tokens
$("body").bind("ajaxSend", function(elm, xhr, s){
if (s.type === "POST") {
xhr.setRequestHeader('X-CSRF-Token', authentication.csrf_token);
}
});
A3 - Broken Authentication and Session Management
Image source: @anoras
A3 - Broken Authentication and Session Management
- How is my session terminated on log out?
- Client-side only?
- Server-side?
- What is left behind in Web Storage?
- How does the client know who is logged in?
- Let's not lose track of the current session policies
- Sessions should time out
- Return a 401 and handle it from JS on the client
A2 - Cross Site Scripting (XSS)
Image source: @johnwilander
Reflected Cross Site Scripting
Image source: @johnwilander
Stored Cross Site Scripting
Image source: @johnwilander
DOM-based Cross Site Scripting
Image source: @johnwilander
DOM-based - XSS
- Occurs in javascript
- Not necessarily visible at the server
http://ex.fm/#!/explore/<script>alert("@vlycser");</script>
- Insecure handling of input in javascript
Unsafe javascript functions
eval(string)
setTimeout(string, delay)
setInterval(string, delay)
new Function(string)
Unsafe jQuery functions
- $.after()
- $.append()
- $.appendTo()
- $.before()
- $.html()
- $.insertAfter()
- $.insertBefore()
- $.prepend()
- $.prependTo()
- $.replaceAll()
- $.replaceWith()
- $.unwrap()
- $.wrap()
- $.wrapAll()
- $.wrapInner()
- $.prepend()
http://twitpic.com/95n3ak
Safe jQuery functions
- $.text()
- $.attr() - unless attr is JS event handler
https://github.com/chrisisbeef/jquery-encoder
jQuery encoder
- $.encoder.canonicalize()
- $.encoder.encodeForCSS()
- $.encoder.encodeForHTML()
- $.encoder.encodeForHTMLAttribute()
- $.encoder.encodeForJavaScript()
- $.encoder.encodeForURL()
Templates?
- What kinds of coding/output-possibilites does it have?
- Does it escape input?
- What kinds of escaping?
- Is the escaping context based?
Templating example - Underscore.js
- Tags:
<% %>
- evaluate code
<%= %>
- output
<%- %>
- HTML-escaped output
- Escaping
_.escape = function(string) {
return (''+string).replace(/&/g, '&').
replace(/</g, '<').
replace(/>/g, '>').
replace(/"/g, '"').
replace(/'/g, ''').
replace(/\//g,'/');
};
This is all well and good as long as...
- ... you are not outputing inside javascript event handlers.
<button onclick="return confirm('Really delete <%- model.title %>')">Delete</button>
<button onclick="return confirm('Really delete ');alert(&x27;XSS')">Delete</button>
- ... you are not using quote-less attributes:
<img title=<%- model.title %> ... >
<img title=monkey onmouseover=alert(/XSS/.source) ... >
- ... you are not outputting data inside
style
attributes or tags
- ... you are not outputting data inside
script
tags
Meanwhile - elsewhere in Norway
<script language="JavaScript" type="text/javascript">
var qs = location.search.substring(1);
var nv = qs.split('&');
var url = new Object();
for(i = 0; i < nv.length; i++)
{
eq = nv[i].indexOf('=');
url[nv[i].substring(0,eq).toLowerCase()]
= unescape(nv[i].substring(eq + 1));
}
</script>
<script LANGUAGE="JavaScript">
document.write('<SCRIPT LANGUAGE="JavaScript" ' +
'SRC="'+url['source']+'?"\></SCRIPT\>');
</script>
This is the entire HTML file...
Content Security Policy
A1 - Injection
This is the Syringe unicode character - U+1F489
Client Side Injections
Server Side Injections
- SQL-injection can of course still be a problem on the server side
- So can NoSQL-injection
- But what about JSON APIs and JS-based backends?
node.js
var http = require('http');
http.createServer(function (request, response) {
if (request.method === 'POST') {
var data = '';
request.addListener('data', function(chunk) { data += chunk; });
request.addListener('end', function() {
var stockQuery = eval("(" + data + ")");
var price = getStockPrice(stockQuery.symbol);
…
});
while(1);
http://www.youtube.com/watch?v=3Vwr24MCCVg
http://bishankochher.blogspot.com/2011/12/nodejs-security-good-bad-and-ugly.html
node.js server side eval
- DOS
- Attack other servers
- Read local files
- Grab entire database (Hello SQL injection)
- Upload arbitrary file and execute on server
- This is NOT a node.js bug - it's unsafe use of javascript
JSHint says "eval is evil"...
...I tend to agree...
Mass assignment / overposting
- Are we still in control of our model?
- Altered timestamp for when an object was created
- Added his own SSH key to the Rails project and made a commit to master
http://chrisacky.posterous.com/github-you-have-let-us-all-down
And finally
<($=[$=[]][(__=!$+$)[_=-~-~-~$]+({}+$)[_/_]+($$=($_=!''+$)[_/_]+$_[+$])])()[__[_/_]+__[_+~$]+$_[_]+$$](_/_)
Mario Heiderich - 106 bytes
alert(1)
XEE - XML Entity Expansions
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE foo [
<!ENTITY a "1234567890" >
<!ENTITY b "&a;&a;&a;&a;&a;&a;&a;&a;" >
<!ENTITY c "&b;&b;&b;&b;&b;&b;&b;&b;" >
<!ENTITY d "&c;&c;&c;&c;&c;&c;&c;&c;" >
<!ENTITY e "&d;&d;&d;&d;&d;&d;&d;&d;" >
<!ENTITY f "&e;&e;&e;&e;&e;&e;&e;&e;" >
<!ENTITY g "&f;&f;&f;&f;&f;&f;&f;&f;" >
<!ENTITY h "&g;&g;&g;&g;&g;&g;&g;&g;" >
<!ENTITY i "&h;&h;&h;&h;&h;&h;&h;&h;" >
<!ENTITY j "&i;&i;&i;&i;&i;&i;&i;&i;" >
<!ENTITY k "&j;&j;&j;&j;&j;&j;&j;&j;" >
<!ENTITY l "&k;&k;&k;&k;&k;&k;&k;&k;" >
<!ENTITY m "&l;&l;&l;&l;&l;&l;&l;&l;" >
]>
<foo>&m;</foo>
Length: 687,194,767,360 bytes ~ 687 GB
Memory exhaustion - a pile of foo
<?xml version="1.0" encoding="utf-8"?>
<foo>
<foo>
<foo>
<foo>
<foo>
<foo>
...1 million foos later...
<foo>
Hola el mundo
</foo>
...there and back again...
</foo>
</foo>
</foo>
XXE - XML eXternal Entity Attacks
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY>
<!ENTITY xxe SYSTEM "/etc/passwd">
]>
<foo>&xxe;</foo>
Some final advice...
- Build robust JSON services with clearly defined transformations covered by automated tests
- Remember the evil test cases - e.g. what should NOT be possible
- Seek frameworks with secure defaults - should default to HTML escaping etc.
- As a developer, default to the secure functions - it's better to err on that side
- You're testing your javascript, right? Why not also test your templates?
Remember...
A Lannister always pays his technical debts
@johnkors
Questions?