~$ Dissecting the Central-Infosec Static Code Analysis challenge

Posted on Apr. 20th, 2021.

Tags:InfoSecCTFWrite-Up


This is a write-up for the hardest of the "14. Web Exploitation: Advanced (CIS-WEBSRV01)" series of challenges in the context of the Central Infosec CTF.

The challenge consists of finding the statically encoded credentials to then find the flag, so we need to work through the obfuscated code and see how it works.

Summary


Step 1: Visiting the page

The first step in this challenge is actually finding it. As per the $MACHINE_IP/robots.txt file, the URL for the challenge is $MACHINE_IP/hack-the-static-hard, and as we load it up in a browser, we see the following page:

Challenge page

As is intimated with the name of the challenge, we'll have to work with the page's static assets. And since it is a login behavior we are targeting (and not some hidden values), we'll take a look at anything related to scripting, usually in the form of some JavaScript.

To check out the script, we will first check the source code for a <script> tag, and check if it is either loading a script from somewhere, or if it is embedded into the page. To do this, we right click the page and press Inspect element, which renders the following in the sidepane:

Challenge page source code

The two circled areas are the function that is called when the "Login" button is pressed, and the second is the actual script, which contains that function.


Step 2: Deobfuscating the script

Let us focus on that script and directly start by separating the functions and indenting the code: (Trust me, you do not want this oneliner in a code box on this page...)

```javascript function checkUsernameAndPassword() { var _0x50da=['Admin','_0x425216','getElementById','btoa','password', 'user_id','value','username','V0hwQ05FNVhWVEJPUkdONlBUMD0','atob']; (function(_0x374ae7,_0x50da49){ var _0x49c249=function(_0x1f03b9){ while(--_0x1f03b9){ _0x374ae7['push'](_0x374ae7['shift']()); } }; _0x49c249(++_0x50da49); }(_0x50da,0x72)); var _0x49c2=function(_0x374ae7,_0x50da49){ _0x374ae7=_0x374ae7-0x0; var _0x49c249=_0x50da[_0x374ae7]; return _0x49c249; }; document['getElementById'](_0x49c2('0x3'))[_0x49c2('0x2')] == _0x49c2('0x6') ? ( document[_0x49c2('0x8')]('isAdmin')['value'] = !![],window[_0x49c2('0x9')]( document[_0x49c2('0x8')](_0x49c2('0x0'))[_0x49c2('0x2')] )==window[_0x49c2('0x5')](_0x49c2('0x4')) ? document['getElementById'](_0x49c2('0x1'))['value'] = window[_0x49c2('0x9')](_0x49c2('0x7')) : document[_0x49c2('0x8')]('user_id')[_0x49c2('0x2')]='1') : document[_0x49c2('0x8')]('isAdmin')[_0x49c2('0x2')]=![]; } function attempt_focus(){ setTimeout( function(){ try{ d = document.getElementById('username'); d.focus(); d.select(); } catch(e){} }, 200); } attempt_focus(); if(typeof onload=='function')onload(); ```

There's a saying that goes: "What in the fresh hell is this?". It is apt in the context.

So let's start at the top:

```javascript function checkUsernameAndPassword() { var _0x50da=['Admin','_0x425216','getElementById','btoa','password', 'user_id','value','username','V0hwQ05FNVhWVEJPUkdONlBUMD0','atob']; // ... } ```

This sets up an array of strings and function names. Quite nice, let's rename all occurences of _0x50da in our code to data, because that will make things easier.

```javascript function checkUsernameAndPassword() { var data=['Admin','_0x425216','getElementById','btoa','password', 'user_id','value','username','V0hwQ05FNVhWVEJPUkdONlBUMD0','atob']; (function(_0x374ae7,_0x50da49){ var _0x49c249=function(_0x1f03b9){ while(--_0x1f03b9){ _0x374ae7['push'](_0x374ae7['shift']()); } }; _0x49c249(++_0x50da49); }(data,0x72)); // ... } ```

What does this section do? Well, it certainly doesn't make itself readable, that's for sure.

What we can say is that it is an unnamed function that is directly executed, and it takes 2 parameters: our list of values and functions, and the number 0x72 (which is the hexadecimal value of 114. So what we can do is rename _0x374ae7 to data (again) and rename _0x50da49 to n:

```javascript function checkUsernameAndPassword() { var data=['Admin','_0x425216','getElementById','btoa','password', 'user_id','value','username','V0hwQ05FNVhWVEJPUkdONlBUMD0','atob']; (function(data,n){ var _0x49c249=function(_0x1f03b9){ while(--_0x1f03b9){ data['push'](data['shift']()); } }; _0x49c249(++n); }(data, 114)); // ... } ```

In the middle of our unnamed function we have a named function called dataGetter49 (which just rolls of the tongue). It takes a value related to n, which is ++n. What ++n really is doing to n is saying: "Hey, before you go and do anything, here's a 1 on me". Effectively, at the moment it is then used, the value of n is n + 1.
Since it seems to be muddling with the contents of data, we will rename the internal function to dataMover.

Let's make our code reflect those changes:

```javascript function checkUsernameAndPassword() { var data=['Admin','_0x425216','getElementById','btoa','password', 'user_id','value','username','V0hwQ05FNVhWVEJPUkdONlBUMD0','atob']; (function(data, n){ var dataMover=function(m){ while(--m){ data['push'](data['shift']()); } }; dataMover(++n); }(data, 114)); // ... } ```

Finally, and it's a feature of JavaScript that is heavily abused when dealing with obfuscation, since all JavaScript variables have the prototype structure, you can call a function on them using the string accessor. This is what we see with data['push'](...), which is the annoying way of writing data.push(...).
Let's follow up on those changes:

```javascript function checkUsernameAndPassword() { var data=['Admin','_0x425216','getElementById','btoa','password', 'user_id','value','username','V0hwQ05FNVhWVEJPUkdONlBUMD0','atob']; (function(data, n){ var dataMover=function(m){ while(--m){ data.push(data.shift()); } }; dataMover(++n); }(data, 114)); // ... } ```

If we were to run lines 2 until 11 in a JavaScript console, and then ask the console to give us the contents of the data variable we would see the following:

New content in data.

We can run it as many times as we want, and that isn't going to change, so we might as well replace lines 2 until 11 of the entire script with:

```javascript function checkUsernameAndPassword() { var data = ["password", "user_id", "value", "username", "V0hwQ05FNVhWVEJPUkdONlBUMD0", "atob", "Admin", "_0x425216", "getElementById", "btoa"]; // ... } ```

Onwards then, let us now look at the function dataGetter:

```javascript function checkUsernameAndPassword() { var data = ["password", "user_id", "value", "username", "V0hwQ05FNVhWVEJPUkdONlBUMD0", "atob", "Admin", "_0x425216", "getElementById", "btoa"]; var dataGetter=function(_0x374ae7,_0x50da49){ _0x374ae7=_0x374ae7-0x0; var _0x49c249=data[_0x374ae7]; return _0x49c249; }; // ... } ```

What does this function do? Well, it takes two parameters. The first one is subtracted by 0x0, which is the hexadecimal format for the number 0.
This line is however far from useless: Given the dynamic nature of javascript, if a number were to be provided in string format, this simple operation would do the job of converting the value to a number for certain.

What it then does is access our data variable (an array) at the index defined by the variable we just converted to a number, and then returns that value.

The _0x50da49 has absolutely no effect whatsoever on this function, and thus can be removed.

As such, we've determined that the function acts as a data "getter" sorts, so we can rename it to dataGetter.

Let's put these changes in writing:

```javascript function checkUsernameAndPassword() { var data = ["password", "user_id", "value", "username", "V0hwQ05FNVhWVEJPUkdONlBUMD0", "atob", "Admin", "_0x425216", "getElementById", "btoa"]; var dataGetter = function(index){ index = index - 0x0; var accessedData = data[index]; return accessedData; }; // ... } ```

The next bit is the complicated bit: It mixes some calls to our previously defined functions, some prototype access shenanigans, some ternary operators and various JavaScript specific WTF elements.

Let's already replace the things we know, and then we will get to identifying what is going on:

```javascript function checkUsernameAndPassword() { var data = ["password", "user_id", "value", "username", "V0hwQ05FNVhWVEJPUkdONlBUMD0", "atob", "Admin", "_0x425216", "getElementById", "btoa"]; var dataGetter = function(index){ index = index - 0x0; var accessedData = data[index]; return accessedData; }; document['getElementById'](dataGetter('0x3'))[dataGetter('0x2')] == dataGetter('0x6') ? ( document[dataGetter('0x8')]('isAdmin')['value'] = !![],window[dataGetter('0x9')]( document[dataGetter('0x8')](dataGetter('0x0'))[dataGetter('0x2')] )==window[dataGetter('0x5')](dataGetter('0x4')) ? document['getElementById'](dataGetter('0x1'))['value'] = window[dataGetter('0x9')](dataGetter('0x7')) : document[dataGetter('0x8')]('user_id')[dataGetter('0x2')]='1') : document[dataGetter('0x8')]('isAdmin')[dataGetter('0x2')]=![]; } ```

Weirdly enough, this has made things worse for us. Well then, I guess we will have to run dataGetter for all of these various values and run with that!

| 0xvalue | Result for `dataGetter(0xvalue)` | |:-:|---| | "0x0" | "password" | | "0x1" | "user_id" | | "0x2" | "value" | | "0x3" | "username" | | "0x4" | "V0hwQ05FNVhWVEJPUkdONlBUMD0" | | "0x5" | "atob" | | "0x6" | "Admin" | | "0x7" | "_0x425216" | | "0x8" | "getElementById" | | "0x9" | "btoa" |

Now let us replace the replacement game:

```javascript function checkUsernameAndPassword() { var data = ["password", "user_id", "value", "username", "V0hwQ05FNVhWVEJPUkdONlBUMD0", "atob", "Admin", "_0x425216", "getElementById", "btoa"]; var dataGetter = function(index){ index = index - 0x0; var accessedData = data[index]; return accessedData; }; document['getElementById']("username")["value"] == "Admin" ? ( document["getElementById"]('isAdmin')['value'] = !![], window["btoa"]( document["getElementById"]("password")["value"] ) == window["atob"]("V0hwQ05FNVhWVEJPUkdONlBUMD0") ? document['getElementById']("user_id")['value'] = window["btoa"]("_0x425216") : document["getElementById"]('user_id')["value"]='1') : document["getElementById"]('isAdmin')["value"]=![]; } ```

Ok, this has gotten much better. But we can still play around with a few things, notably replacing the prototype string accessors, transforming the JavaScript WTF values into their actual counterparts (![] => false and !![] => true), and maybe transforming the ternary operators (which are one-liner if-else statements in the form of condition ? do_if_condition_true : do_if_condition_false) into actual if-else statements:

```javascript function checkUsernameAndPassword() { var data = ["password", "user_id", "value", "username", "V0hwQ05FNVhWVEJPUkdONlBUMD0", "atob", "Admin", "_0x425216", "getElementById", "btoa"]; var dataGetter = function(index){ index = index - 0x0; var accessedData = data[index]; return accessedData; }; if (document.getElementById("username").value == "Admin") { document.getElementById('isAdmin').value = true; if (window.btoa(document.getElementById("password").value) == window.atob("V0hwQ05FNVhWVEJPUkdONlBUMD0")) { document.getElementById("user_id").value = window.btoa("_0x425216"); } else { document.getElementById('isAdmin').value = false; } } else { document.getElementById('user_id').value = '1'; } } ```

Woow, far out! This has made the code readeable, and let's us progress onto the next part!


Step 3: Determining the password validation scheme

Well, we now have found the diagram for how this works. But what makes the engines turn? How does it work?

If you would look at line 11, the first thing to notice is that it is looking for whether or not the username provided in the form is "Admin".

If that condition is met, it will set the isAdmin hidden field to true.

Then we get into a condition we haven't "decoded" yet. Firstly, window.btoa and window.atob are functions that exist by default in JavaScript, so let us check what they produce with our two static values:

```javascript document.getElementById('isAdmin').value = true; if (window.btoa(document.getElementById("password").value) == "WHpCNE5XVTBORGN6PT0=") { document.getElementById("user_id").value = "XzB4NDI1MjE2"; } else { document.getElementById('isAdmin').value = false; } ```

So this password validation scheme is the following:

  1. It checks whether or not the username entered "Admin". It sets the isAdmin page value to 1, which here stands for true.
  2. If that is the case, it will then check whether the password entered is correct and if that is right, sets a hidden user_id field to some obscure value ("XzB4NDI1MjE2"), which is what the form uses to check for the password's validity.
  3. Otherwise, it resets the isAdmin page value.
  4. If the username was incorrect, it sets the hidden user_id field to a value that won't be recognised by the password validity checker.

Step 4: Finding the password

Well, this doesn't help us much. Or does it? If you look at the naming convention of the two functions (atob and btoa) you might notice that they are two sides of the same coin. The one goes "[from] a to b" and the other goes "[from] b to a".

So if we apply that principle to our code, and remember our first algebra classes, then we know that if you do one thing to one side, then you do the same to the other:

```javascript window.atob(window.btoa(document.getElementById("password").value)) == window.atob("WHpCNE5XVTBORGN6PT0=") ```

Which, simplified further is:

```javascript document.getElementById("password").value == "XzB4NWU0NDcz==" ```

We now know of a username ("Admin") and a password ("XzB4NWU0NDcz=="). Let's go grab the flag!

The flag.

The flag is Central-InfoSec{[email protected][email protected]_C0D3D_4_7H3_L055}, and was pretty fun to get. Hope you had fun reading this!