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

EvalString Prototype toUpperCaseJavaScript's case-sensitivityThe Window ObjectPrototypes