I noticed today that I can replace a sensitive built-in JS function like this:
async function _hackedEncrypt(algorithm, key, data) {
console.log('hacked you!');
}
const subtle = global.crypto.subtle; // Assign to get around "read-only" error.
subtle.encrypt = _hackedEncrypt;
global.crypto.subtle.encrypt(); // 'Hacked you!' appears in console.
Yikes!
This exploit is so simple. Any of the thousands of dependencies (direct and transitive) in my web app could make this function reassignment. Note that my question isn't specific to Web Crypto - it's just one of the more dangerous targets for an attacker.
How can I either detect that the function has been reassigned or guarantee that I'm always calling the original browser implementation of it?
In my index.js, I import this module ahead of any other modules. It must be the first import so that other dependencies don't get a chance to replace a function before I can set a reference to the original.
const originalEncrypt = global.crypto.subtle.encrypt;
// Any number of functions worth protecting can be added here
// and also added to the check in wereFunctionsReplaced().
function wereFunctionsReplaced() {
return global.crypto.subtle.encrypt !== originalEncrypt;
}
...and then I call wereFunctionsReplaced() ahead of the function call I'm worried about:
if (wereFunctionsReplaced()) throw Error('Everybody run to the panic room!');
global.crypto.subtle.encrypt(algorithm, key, data);
Of course, I could also wrap the protected functions like:
function safeEncrypt(algorithm, key, data) {
if (wereFunctionsReplaced()) throw Error('Everybody run to the panic room!');
return global.crypto.subtle.encrypt(algorithm, key, data);
}
In some cases, it would be more convenient to store the built-in function to a variable and call the function directly from the variable rather than from global.*. But for crypto.subtle.encrypt it will throw a "Bad invocation" error in Chrome. And thinking harder about it, many subtle, unexpected behaviors could be caused by calling the built-in functions outside their expected location in the global hierarchy.
This is a self-answer for posterity. But I would love to hear better solutions than this one. Criticism on any faults this solution may have is welcome as well.