Destructuring Tweets - Episode 7 - Even more Evil Eval
Kai
Posted on February 14, 2021
Grüß dich! Welcome to the series about destructuring a JavaScript quiz from Twitter. This Sunday, you'll learn about the one API you should never use: eval.1
Snippet of the Week
This week's snippet is from h43z:
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
So, 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 straight forward. 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
.
Snippet Summary
- Trickery: how to manipulate an existing function call
-
Key Learning: JavaScript is case-sensitive,
eval
executes code from a string (and shouldn't be used!), prototyping & global scope - Further Reading:
Posted on February 14, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.