Destructuring Tweets – Episode 7 – Even more Evil Eval
Welcome to the series about destructuring a JavaScript quiz from Social Media. This Sunday, you'll learn about the one API you should never use: eval.
⚠️ Executing JavaScript from a string is an enormous security risk. It is far too easy for a bad actor to run arbitrary code when you use eval(). Read more at MDN
The Snippet
function challenge(input){
eval(input.toUpperCase())
}
// provide an input that makes function challenge call alert(1)
challenge('alert(1)');
Here we move into muddy waters. The author declares a function called challenge
, which wraps eval
. The trick is that the argument of the wrapper-function gets piped through .toUpperCase()
first.
The exercise is to make the function execute alert(1)
.
The Output
The output here is not spectacular but feels kind of magic if you do not know what eval
exactly does. However, it is reasonably straightforward: eval
executes whatever piece of code gets passed as a string.
So, for our snippet, the interpreter throws an error since eval
goes and tries to execute “ALERT”, which is undefined in the global scope.
ReferenceError: ALERT is not defined
The Analysis
First things first, let's go back to the snippet and examine what exactly happens there. We pass on the argument alert(1)
. In case we would not have a wrapper function, this piece of code would execute an alert
just fine:
eval('alert(1)');
However, since it gets piped through .toUpperCase()
, the string, thus the called function, is actually ALERT
, and JavaScript is a case-sensitive language!
Now we need to overcome this issue. I came up with three possible solutions. Let's check them one by one.
Altering the Prototype of String
Since the method toUpperCase
is part of the String
prototype, we can easily alter its function body:
function challenge(input){
eval(input.toUpperCase())
}
String.prototype.toUpperCase = () => alert(1);
challenge('alert(1)');
In that case, when toUpperCase
gets called on input
, it does not pipe and parse the string but instead executes an alert. The called function is just overwritten with the target behavior.
Adding the function ALERT
We can also go the other way around and add the missing function ALERT
to the global object.
function challenge(input){
eval(input.toUpperCase())
}
window.ALERT = input => alert(input);
challenge('alert(1)');
That approach is straightforward. We add another function with the right name. Instead of calling alert
, we call ALERT
instead, which then passes its argument on to alert
.
Passing an Object
The first two solutions were actually illegal. The author explicitly stated to solve the challenge by just passing an input. This is what we do in that approach. We pass an object with the function toUpperCase
.
function challenge(input){
eval(input.toUpperCase())
}
challenge({ toUpperCase: () => 'alert(1)' });
Instead of using the implementation in String's prototype, we pass an object with its own version of toUpperCase
.
Further Reading
– Eval – String Prototype toUpperCase – JavaScript's case-sensitivity – The Window Object – Prototypes