environment • Based on Google's V8 JavaScript engine • Single threaded • Everything is designed to be asynchronous • A set of C++ APIs were added to V8 – Files – Crypto – Execution – Processes – … • On top of that, a fairly large set of JavaScript runtime APIs.
say node.js is catching on • Tons of people are writing node.js code • With that, tons of people will make security mistakes • Hopefully, after this presentation, they’ll create fewer security bugs (and fix the ones already there) • I tried to get some real world examples – Couldn’t find any on sf.net. Then realized I had to look on github • Sign I’m getting old? …
You can obviously write webapps in node.js • As such you’ll see xss bugs • Reflected, stored, both can occur • Node.js has no magic way to get rid of them
into these … • If you’re writing node.js code, you should escape user input (escaping depends on context (html, javascript, url, xml, json, …) • For html this looks like: function htmlEscape(text) { return text.replace(/&/g, '&'). replace(/</g, '<'). replace(/"/g, '"'). replace(/'/g, '''); }
prone. • There are modules that can help with this: – Node-validators – Secure-filters var validator = require('validator'); var escaped_string = validator.escape(someString);
too • These too look _exactly_ as do they do elsewhere • Node.js apps seem to favor usage of no-sql like databases • Sql doesn’t seem to occur as much as with classic webapps/webservices • Node.js doesn’t have any kind of native SQL support. Everything is done through modules
example with YQL injection (from a real case found on github ) Provider.prototype.findById = function(conn, id, callback) { conn.query('SELECT * FROM ' + this.table + ' WHERE id = ‘ + id, function localCallback(err, rows) { if(err) return callback(err); else return callback(null, rows[0]); };); };
if input even remotely smells like it can come from the user • No exceptions Provider.prototype.findById = function(conn, id, callback) { conn.query('SELECT * FROM ' + this.table + ' WHERE id = ?’, id, function localCallback(err, rows) { if(err) return callback(err); else return callback(null, rows[0]); };); };
code, it’s hard to show an example. • Node.js doesn’t offer native xsrf protection – Though, I think it should! A framework like node.js is *perfect* for this • There are some frameworks that do – Kraken
• Generate a token that must be cryptographically secure • Must send it to the client when it’s building a form to submit stuff on (and keep it in the session). – Hidden field • On form submit, token will be send back. • Compare the two, if they match execute request, else someone is probably trying to attack your webapp
secure! – Model is old, when there was one .html that did a <form> • Doesn’t always fit well in web 2.0 apps where all you send back and forth is json – Is per session token enough ? Or do you want it per request ? • Use a framework (e.g. kraken) if you can. Will save you some hassle. – If things go wrong, you get to blame someone else
• Node.js is pretty clever when it comes to response splitting • It will not allow splitting of the header value • But it will allow splitting of the header name itself • Rare that client gets to have control over header value server sends back – Probably not unprecedented
Connected to localhost. Escape character is '^]'. GET /?name=aaaa HTTP/1.0 HTTP/1.1 200 OK aaa: bbbbb cccccc: bbbbbb Content-Type: text/html Date: Tue, 27 Jan 2015 10:17:02 GMT Connection: close byeConnection closed by foreign host. macbox:nodeApps ilja$
part of the HTTP header name is probably rare • Is there really a case for it ? You can probably work around it. • If you must have it, and can’t work around it there are some options: – Filter out anything not a-z – Bail if outside of a-z – Filter out \n – Bail if \n
• Use of user supplied data in eval() • Server side javascript code injection! • Could happen when app uses library that uses eval() to parse JSON (iso JSON.parse()) for example.
system() in c • Any user input in there is BAD. • Child_process.spawn() • Forks off one process and runs command • If attacker has control over arguments would allow for switch injection
• If you must pass untrusted data along to applications, use spawn() or execFile() – Be careful! – Is app you’re passing it on to really made to handle untrusted input ? – Make sure attacker can’t do switch injection
you open files requested by the user • And you assume the user will be nice, and not do ../../../../../file/outside/httproot/../../../etc/pa sswd for example.
to localhost. Escape character is '^]'. GET /../../../../../../etc/passwd HTTP/1.0 HTTP/1.1 200 OK Content-Type: text/plain Date: Tue, 27 Jan 2015 09:38:21 GMT Connection: close ## # User Database # …. ## nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false root:*:0:0:System Administrator:/var/root:/bin/sh daemon:*:1:1:System Services:/var/root:/usr/bin/false
on github • The 90’s called, they want their bugs back. • Soooooo, don’t do that. • Do not pass untrusted file names to readFile / writeFile • If you must do so, make sure to validate for directory traversal !! – Doing it right is actually harder than it looks!
if (session === undefined) { throw new Error('lusca requires req.session to be available in order to maintain state'); } var secret = session[secretKey]; // Save the secret for validation if (!secret) { session[secretKey] = crypto.pseudoRandomBytes(LENGTH).toString('base64'); secret = session[secretKey]; } … }
is that it should block, and wait for entropy (it doesn’t, and fails) • This does not fit in well with the node.js asynchronous model. • How to fix this ? Pre-allocate piles of random data ? Maybe node.js should expose when the pseudo API doesn’t return cryptographically strong data ? • DanK has some solutions iirc. – http://www.slideshare.net/dakami/yet-another-dan- kaminsky-talk-black-ops-2014
passing it on to console.log() • This isn’t really that bad per se. – Not really a bug on it’s own • Administrators tend to use tools to view logs that assume trusted content. • If not filtered, attacker could inject: – Terminal escape codes – Unprintable characters that fool the administrator (e.g. make it look like certain log entries are missing)
supposed to be lightning fast • Normal JavaScript data structures (string, array, …) come with managed language overhead • Node.js introduces the JavaScript Buffer class • Really implemented in C++ • A thin layer around new [] / delete []
applications • This stuff is used _EVERYWHERE_ in node.js applications • There’s 3 ways to create a Buffer – With a length – With an array (+ optional encoding) – With a string (+ optional encoding)
block of memory. • Uninitialized – To developers used to managed programming languages • For perf reasons obviously • The potential for heartbleed-like bugs is ENORMOUS!
• Either with [] operator • Or read*() and write*() APIs. – You can specify an offset • So we can read uninitialized data • Can we read or write out of bound ? • The [] operator does boundchecks.
write*() API documentation. • Both say that if noAssert is set, no out of bound exception will be thrown. The write APIs documentation explicitly says it will discard any out of bound access. – read*() documentation says no such thing.
all the read*() and write*() APIs for integers will silently ignore out of bound (with noAssert set). • For floats and doubles however, out of bound reads and writes (with noAssert set) go through. This can cause crashes and buffer overflows! – Suspect this is an implementation oversight. • Tested on v0.10.31
Sending uninitialized memory over the network can have disastrous consequences! – Think heartbleed • Out of bound read and write through readFloat*() and writeFloat*() APIs are most likely an implementation oversight.
languages/frameworks have – Sometimes it feels like 90’s style perl cgi bugs • It exposes a few new cutes ones – E.g. Buffer • Interesting area of security research