Natas - Level 12

2 November 2017

Connection information

Information given

The white box is composed of three parts:

  1. The first part is a text displaying : " Cookies are protected with XOR encryption"
  2. The second part is an input box with the label Background color:
  3. The third is a button named Set color
  4. The last is a link View sourcecode pointing to http://natas11.natas.labs.overthewire.org/index-source.html

Getting the password

$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>";
    }
  ?>

Understanding the code

If we read the source code, we can see that the password is displayed if data["showpassword"]isequalto"yes".Thearraydata["showpassword"] is equal to "yes". The arraydata is actually taken from a cookie (named data). The array $data contains two pieces of info:

  1. showpassword: which activates the display of the password if set to "yes"
  2. bgcolor: which sets the page background color.

If we try and change the page background color to #000000 or #f0f0f0 we will see that the cookie’s changed.

When we send a new background color, the server reads the cookie. If the cookie is an array and contains the indexes "showpassword" and "bgcolor", then the background color and the value of "showpassword" are updated from the cookie’s values. Then a new cookie is generated with these new values.

So we just have to change the value of "showpassword" from "No" (default value) to "Yes". Except that the cookie’s value is encrypted with the function xor encrypt (one time pad). And we don’t know the key!

Json encoding - Getting the clear text

Here is the knowledge we have for the moment:

cookie["data"] = base64 encode(xor encrypt(json encode(dataArray)))

To understand a little bit more how every step of the encryption works let’s try to do some of it ourself. The following code creates the default array and encode it into json.

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

And here is what we get:

{"showpassword":"no","bgcolor":"#ffffff"}

The hexdump will be:

sammy@server: ̃$ CLEAR=$(echo{"showpassword":"no","bgcolor":"#ffffff"}| xxd −p | tr −d ’\n’)

sammy@server: ̃$ echo $CLEAR
7b2273686f7770617373776f7264223a226e6f222c226267636f6c6f72223a2223666666666666227d0a

base64 decryption - Getting the cipher text

Now we know what is fed to the function xor encrypt (the clear text). And we also know the cipher text since we just have to perform a base64 decryption on the cookie’s value to get it.

When we have a #ffffff background color, the cookie’s value is: ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw%3D

Except that we have to convert non-alphanemeric printable characters: %3D is actually an equal sign.

Here we decode the cookie value to get the cipher text:

sammy@server: $BASE="ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw="
sammy@server: ̃$ echo −n $BASE | base64 −d
UK" [non displayable ascii] ˆh

Here is the hexdump of the cipher text:

sammy@server: ̃$ CIPHER=$(echo −n $BASE | base64 −d | xxd −p | tr −d ’\n’)
sammy@server: ̃$ echo $CIPHER
0a554b221e00482b02044f2503131a70531957685d555a2d121854250355026852115e2c17115e680c

Finding the key

The general idea

Since the encryption used is a xor, we should be able to retrieve the key. We know that:

mkey (xor) cleartext = ciphertext

Where mkey is multiple concatenations of the key. So we will have:

cleartext (xor) ciphertext = nkey

Where nkey is a part of the key or multiple concatenations of the key.

First attempt

Here is the cleartext (xor) ciphertext

sammy@server: ̃$ printf ’0x%X\n’ $(( 0x$CLEAR ˆ 0x$CIPHER))
0x77384A71777C1506

Let’s see what the key is!

sammy@server: ̃$ echo "77384A71777C1506" | xxd −r −p
w8Jqw|..

The last two characters (15 and 06) could not be displayed in Latex so I remplaced them with dots. When I tried to decode a message using xor encrypt with this key, nothing worked properly.

Second attempt

So I thought that the problem was coming from the way I performed the xor.

So I installed calc:

sudo apt−get install apcalc

When using calc, I set the base to 16 to display the results in hexadecimal. Then I used the xor function, and as you can see, I got a very different result:

sammy@server: ̃$ calc
C−style arbitrary precision calculator (version 2.12.5.0)
Calc is open software. For license details type: help copyright
[Type "exit" to exit, or "help" for help.]
; base(16)
  0xa
; xor(0x7b2273686f7770617373776f7264223a226e6f222c226267636f6c6f72223a2223666666666666227d0a, 0x0a554b221e00482b02044f2503131a70531957685d555a2d121854250355026852115e2c17115e680c)
  0x7b2826234d6970295871732057673120523d7675447f373d4e7d743b57216f204b3477384a71777c1506

What is intringuing is that the result we found in the first attempt is actually just a part of this longer result: we can find again this dump 77384a71777c1506.

Here is what I got when I tried to decode the potential key: {(&#Mip)Xqs Wg1 R=vuD7=N}t;W!o K4w8Jqw|..

Once again I replace 15 and 06 by dots (but only here! I used the unicode characters in my PHP code).

When I tried to decode a message using xor encrypt with this key, I would get a message partially decrypted.

q}m.Si8.Zu<.Tt+P.$!..bgcolor":"#ffffff"}

Once again, the dots are unicode characters that can’t be displayed (01, 02, 05, 10, 19). But you can see that we are getting closer, the following text is being decrypted correctly bgcolor":"#ffffff"}. This means that a part of the key is correct!

The problem

But why only a part of this key is correct? The problem lies in the way the xor is performed.

When we do xor(0x1000, 0x1) we get 0x1001. Which means that xor pads its arguments to the left and what is really computed is xor(0x1000, 0x0001). This is logic, but in conflict with the way the function xor encrypted does its computing.

When using xor encrypt, the function takes the first character of the key (key[0]) and xor it to the first character of the data (data[0]) and so on... For instance, if the key is "IamtheKey" and the data is "Hello", the script will do:

  1. I ˆH
  2. a ˆe
  3. m ˆl
  4. t ˆl
  5. h ˆo

But when we do our xor with calc or the integrated bash operator, what we really do is:

  1. I ˆ
  2. a ˆ
  3. m ˆ
  4. t ˆ
  5. h ˆH
  6. e ˆe
  7. k ˆl
  8. e ˆl
  9. y ˆo

Third attempt

If we put the clear text and the cipher text side to side we can see that the cipher is two bytes short.

CLEAR="7b2273686f7770617373776f7264223a226e6f222c226267636f6c6f72223a2223666666666666227d0a"
CIPHR="0a554b221e00482b02044f2503131a70531957685d555a2d121854250355026852115e2c17115e680c"

So what I did is pad the right of the cipher with two zeros

sammy@server: ̃$ calc
C−style arbitrary precision calculator (version 2.12.5.0)
Calc is open software. For license details type: help copyright
[Type "exit" to exit, or "help" for help.]
; base(16)
  0xa
; xor(0x7b2273686f7770617373776f7264223a226e6f222c226267636f6c6f72223a2223666666666666227d0a, 0x0a554b221e00482b02044f2503131a70531957685d555a2d121854250355026852115e2c17115e680c00)
  0x7177384a7177384a7177384a7177384a7177384a7177384a7177384a7177384a7177384a7177384a710a

Now just from looking at the result we can see that we have indeed a repetition of the key 7177384a as expected. If we translate that to ASCII here is what we get:

sammy@server: ̃$ echo ’7177384a7177384a7177384a7177384a7177384a7177384a7177384a7177384a7177384a7177384a710a’ | xxd −r −p
qw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jq

If we use the key qw8J in xor encrypt, we will get the expected results. Note that the problem really was the padding and the first method actually gave the exact result when the padding was done correctly.

Php code used

$defaultdata = array( "showpassword"=>"no", "bgcolor"=>"#ffffff");
function xor_encrypt($in) {
  $key3 ={(&#Mip)Xqs Wg1 R=vuD7=N}t;W!o K4w8Jqw|..’;
  $key = ’qw8J’;
  $text = $in;
  $outText = ’’;
  // Iterate through each character
  for($i=0;$i<strlen($text);$i++) {
    $outText .= $text[$i] ˆ $key[$i % strlen($key)];
  }
  return $outText;
}

$jsondata = json encode($defaultdata);
$xordata = xor encrypt($jsondata);
$base64data = base64 encode($xordata);
$encodeddata = "ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw=";
$decodeddata = base64 decode($encodeddata);

$decrypteddata = xor encrypt($decodeddata);

# echo $defaultdata;
echo "Encoding process −−−−−−−−−−−\n";
echo $jsondata;
echo "\n\n";
echo $xordata;
echo "\n\n";
echo $base64data;

echo "\n\n\nDecoding process (from cookie) −−−−−−−−−\n";
echo "\nEncoded data \n";
echo $encodeddata;
echo "\nDecoded data \n";
echo $decodeddata;
echo "\nDecrypted data \n";
echo $decrypteddata;

Now that we have the key we can use it to forge the cookie we need. The cookie should have the data: "showpassword":"yes","bgcolor":"#ffffff". I used the PHP code listed above and modified $defaultdata accordingly.

I obtained the following value:

ClVLIh4ASCsCBE8lAxMacFMOXTlTWxooFhRXJh4FGnBTVF4sFxFeLFMK

I modified the cookie with firefox dev tools and reloaded the page. The following message appeared:

The password for natas12 is EDXp0pS26wLKHZy1rDBPUZk0RKfLGIR3