Overthewire: Natas Level 11

Table of Contents

Overthewire - Natas

This series on the overthewire webpage challenges you to think outside the box and more about communication between client and server in order to find the hidden flag on the website for the next level. The structure of the challenge is that for each level you require a user name and password to authenticate to the next level challenge website. The username is natas[X] with X being the current level (e.g. natas5 for level 5), the corresponding URL is “http: //natas[X].natas.labs.overthewire.org/", X again being the level number e.g. “http://natas5.natas.labs.overthewire.org/" for the fifth level, and the password consists of 32 alphanumeric characters.

And now without further ado, let’s get to it:

natas11

This layout seems very familiar from previous challenges. We have an input field, a “Set color” button, an example for a color “#ffffff” and a “View source code” link. “#ffffff” reminds me of hex color codes used in CSS style sheets. Let’s check this out: HTML Color Picker W3 is one of the best resources for HTML, CSS and JavaScript coding out there and a good reference to look these things up. Ok, so “#ffffff” is totally white. Let’s make the page black aka “#000000”. What happens if we press the “Set color” button with this setting?

White color scheme with “#ffffff” changed to black background color with “#000000”.

Now after we confirmed that we can actually change the background color of the page, we can now dig deeper and look at the source code:

$defaultdata = array( "showpassword"=>"no", "bgcolor"=>"#ffffff");

function xor_encrypt($in) {
    $key = '<censored>';
    $text = $in;
    $outText = '';

    // Iterate through each character
    for($i=0;$i<strlen($text);$i++) {
    $outText .= $text[$i] ^ $key[$i % strlen($key)];
    }

    return $outText;
}

function loadData($def) {
    global $_COOKIE;
    $mydata = $def;
    if(array_key_exists("data", $_COOKIE)) {
    $tempdata = json_decode(xor_encrypt(base64_decode($_COOKIE["data"])), true);
    if(is_array($tempdata) && array_key_exists("showpassword", $tempdata) && array_key_exists("bgcolor", $tempdata)) {
        if (preg_match('/^#(?:[a-f\d]{6})$/i', $tempdata['bgcolor'])) {
        $mydata['showpassword'] = $tempdata['showpassword'];
        $mydata['bgcolor'] = $tempdata['bgcolor'];
        }
    }
    }
    return $mydata;
}

function saveData($d) {
    setcookie("data", base64_encode(xor_encrypt(json_encode($d))));
}

$data = loadData($defaultdata);

if(array_key_exists("bgcolor",$_REQUEST)) {
    if (preg_match('/^#(?:[a-f\d]{6})$/i', $_REQUEST['bgcolor'])) {
        $data['bgcolor'] = $_REQUEST['bgcolor'];
    }
}
saveData($data);
<h1>natas11</h1>
<div id="content">
<body style="background: <?=$data['bgcolor']?>;">
Cookies are protected with XOR encryption<br/><br/>
<?
if($data["showpassword"] == "yes") {
    print "The password for natas12 is <censored><br>";
}
?>

Ok that is a lot to process. Let’s start at the bottom.
If the “$data[“showpassword”]” variable is set to yes, we can see the natas12 password. $data is later changed by calling the loadData function with $defaultdata as parameter, thus $data is initialized via the $defaultdata variable at the start of the page with “no” ($defaultdata = array( "showpassword"=>"no", "bgcolor"=>"#ffffff")).
So what does loadData do? It refers to a global $_COOKIE defines $mydata = $def, with $def being the parameter handed over to $loadData and if there is a key “data” inside that $_COOKIE it calls $tempdata = json_decode(xor_encrypt(base64_decode($_COOKIE["data"])), true). So we base64 decode the value of the data cookie, use the function $xor_encrypt and then decode the JSON string. xor_encrypt has a hidden variable value for $key, takes a value in and iterates through each character of the input value.
It performs the following operation:

for($i=0;$i<strlen($text);$i++) {
    $outText .= $text[$i] ^ $key[$i % strlen($key)];
}

First “.=” means string concatenation in PHP. We take the i-th character of our input and perform a bitwise XOR with the $key variable at the “i-th modulo len($key)” position. To give an example of themodulo operation here: “5 modulo 3 = 2” So we perform “division with residues” like in elementary school, but keep the residue in this case. This helps us determine the length of the $key string as we know the length of our string, the i-th position and can look for a repetition in pattern, which we will use to determine the length of $key.
Ok now assuming we have set $tempdata, we have to make sure that there exists a key in our $tempdata variable with the key “showpassword” in it (if(is_array($tempdata) && array_key_exists("showpassword", $tempdata) && array_key_exists("bgcolor", $tempdata))). If the $bgcolor variable content now matches a valid hex color value (#000000 - #ffffff), tested via this regular expression if (preg_match('/^#(?:[a-f\d]{6})$/i', $tempdata['bgcolor'])) - the $mydata[‘showpassword’] = $tempdata[‘showpassword’] is set and returned at the end of the loadData function.
After the loadData function, there is also a saveData function. It does setcookie("data", base64_encode(xor_encrypt(json_encode($d)))), so json encoding the input value, then using xor_encrypt again and finally encodes the entire string in base64. So that is exactly the reverse operation that the loadData function performs. As xor_encrypt is symmetric, this upholds: data = xor_encrypt(xor_encrypt(data)).
So the saveData function is our way in! Let’s get to it!

We fire up burp and set the proxy settings correctly:

Burp and Firefox proxy settings.

When we reload the page with “#ffffff” set and intercept the traffic with burp, we see the following traffic:

So we know now, that depending on the value entered as color, that cookie value must change. So let’s insert “#000000” and intercept again.

Yup, based on our changed input we changed the cookie value from …EV4sFxFe… to …Rwh6QUcI… We invoked a change exactly seven characters long, just like our input! Ok now let’s reverse the operations of the saveData function:
First we need to URL decode the “%3D” in ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw%3D and we get ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw=. Let’s use python to base64 decode it:

>>> import base64
>>> base64.b64decode("ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw=") 
>>> '\nUK"\x1e\x00H+\x02\x04O%\x03\x13\x1apS\x19Wh]UZ-\x12\x18T%\x03U\x02hR\x11^,\x17\x11^h\x0c'
>>> [ord(x) for x in base64.b64decode("ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw=")]
>>> [10, 85, 75, 34, 30, 0, 72, 43, 2, 4, 79, 37, 3, 19, 26, 112, 83, 25, 87, 104, 93, 85, 90, 45, 18, 24, 84, 37, 3, 85, 2, 104, 82, 17, 94, 44, 23, 17, 94, 104, 12]

So now for the XOR part. What do we know about the string that should be there? We know that the Cookie data is filled with the $defaultdata aka “showpassword"=>"no”, “bgcolor"=>”#ffffff”. We furthermore know that PHP uses a JSON conversion, thus the correct JSON notation for the content in $defaultdata is: “{“showpassword”:“no”,“bgcolor”:"#ffffff”}".
We can test our hypothesis by comparing the lengths of both strings:

>>> len([ord(x) for x in base64.b64decode("ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw=")])
>>> 41
>>> len([ord(x) for x in '"{showpassword":"no","bgcolor":"#ffffff"}']
>>> 41

The lengths match! So know we can XOR both strings and look for a pattern in there to receive the content of the $key variable:

>>> content = [ord(x) for x in '"{showpassword":"no","bgcolor":"#ffffff"}']
>>> cookie = [ord(x) for x in base64.b64decode("ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw=")]
>>> key_list = []
>>> for i in range(0, len(cookie)):
...     key_list.append(cookie[i] ^ content[i])
...
>>> print(key_list)
>>> [113, 119, 56, 74, 113, 119, 56, 74, 113, 119, 56, 74, 113, 119, 56, 74, 113, 119, 56, 74, 113, 119, 56, 74, 113, 119, 56, 74, 113, 119, 56, 74, 113, 119, 56, 74, 113, 119, 56, 74, 113]

So we see a pattern that repeats: “113, 119, 56, 74”. So our $key is “qw8J”.
Ok now we know the XOR key and must craft a correct answer for this puzzle. At the end, we need a “showpassword”:“yes”.
So our string must be: {“showpassword”:“yes”, “bgcolor”:"#ffffff”}
However, we still need to “craft” that string into the cookie, using our key. So we start by taking that string, XOR it with our key and base64 encode it in the end:

import base64

key = [113, 119, 56, 74]
enc_ascii_val = []
solution = [ord(c) for c in '{"showpassword":"yes","bgcolor":"#ffffff"}']
for i in range(0, len(solution)):
  if i % 4 == 0:
    enc_ascii_val.append(solution[i] ^ key[0])
  elif i % 4 == 1:
    enc_ascii_val.append(solution[i] ^ key[1])
  elif i % 4 == 2:
    enc_ascii_val.append(solution[i] ^ key[2])
  elif i % 4 == 3:
    enc_ascii_val.append(solution[i] ^ key[3])

solution_cookie = "".join([str(chr(c)) for c in enc_ascii_val])
solution_cookie_ready = base64.b64encode(solution_cookie)
print(solution_cookie_ready)

The program returns: ClVLIh4ASCsCBE8lAxMacFMOXTlTWxooFhRXJh4FGnBTVF4sFxFeLFMK
Now we use burp to test it out, alter the content of the data Cookie and hopefully receive the password for natas12:

Spoiler natas12: Username: natas12
Password: EDXp0pS26wLKHZy1rDBPUZk0RKfLGIR3
URL: http://natas12.natas.labs.overthewire.org

Wrap Up

Alright folks, this level was already much harder than the ones before. We had to carefully read through the PHP code, understand the saveData, loadData and xor_encrypt function and work reversly through the base64, xor, json chain. Then we had to reverse the key for the XOR function by XORing the default content of the data cookie in clear with the base64 encoded version, craft a payload that would work and finally craft that payload into a suitable format.

We used a lot of python scripts, burp and a lot of online reading resources to crack this level. Let’s keep going!

See you soon!