Destructuring Tweets - Episode 7 - Even more Evil Eval

odddev

Kai

Posted on February 14, 2021

Destructuring Tweets - Episode 7 - Even more Evil Eval

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)');
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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)');
Enter fullscreen mode Exit fullscreen mode

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)');
Enter fullscreen mode Exit fullscreen mode

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)');
Enter fullscreen mode Exit fullscreen mode

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)' });
Enter fullscreen mode Exit fullscreen mode

Instead of using the implementation in String's prototype, we pass an object with its own version of toUpperCase.

Snippet Summary


  1. Warning: 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 

💖 💪 🙅 🚩
odddev
Kai

Posted on February 14, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related