Server-side security war games: Part 11

This is level 11. Your clue is that "XOR encryption" is not encryption. Let's look in the cookies to find out they have XOR-ed, so we can mess with it.

ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw%3D

Now we need to know what they XOR-ed it with, so open up the source view.

Read through the PHP code carefully. Here's the basic flow:

  1. First, we create an array with showpassword set to "no", and bgcolor set to #ffffff.
  2. Next, we declare a function called xorencrypt. It uses a key (omitted) and the XOR operation on the input string. It is important to realize that XOR is a weak point. I'll mention why soon.
  3. Declare a function called loadData. It takes the default array in, looks in the client-provided (important!) cookie for data that can be base64_decoded, then does XOR, and JSON-decodes it into a PHP array.
  4. If the JSON decoded to an array, and the values the code expects are present, then $mydata gets updated with the values provided in the cookie.
  5. The function then returns $mydata, which either contains the default values, or the data from the last step.
  6. A function called saveData is created, which sets a cookie. The value of the cookie is a PHP array that gets JSON-encoded, XOR-ed, and then base64_encoded.
  7. When the PHP code excutes, it calls loadData, to handle any data provided as a cookie, then sets the background color, and whatever data has been loaded is set as the cookie value.

Our goal is to get the password, and we can see at the end of the source that if $data['showpassword'] is set to 'yes', then the password will be displayed. That's what we need to achieve.

So, we need to be clear about what needs to happen to accomplish that. We need to know what data we control, and we need to think about how we can use that to our advantage.

We want to set $data['showpassword'] to 'yes'. That will only happen when loadData takes our cookie and sees the correctly encoded and "encrypted" array. To produce the "encrypted" input, we need the key.

We control:

  • The default array used to generate the default cookie value
  • The default cookie value
  • The functions used to encrypt and encode our data

The XOR encryption has an interesting property:

plaintext XOR key = ciphertext
plaintext XOR ciphertext = key

Since we're given the default array (plaintext) and the cookie value (ciphertext), we can get the key. Score!

In this code, we're using the base64_decoded value of our cookie as our ciphertext and the json_encoded default array as our plaintext.

function xor_encrypt($str) {
    $key = json_encode(array("showpassword" => "no", "bgcolor" => "#ffffff"));
    $out = '';
 
    for($i=0; $i<strlen($str); $i++) { // Iterate through each character
        $out .= $str[$i] ^ $key[$i % strlen($key)];
    }
    return $out;
}
$orig_cookie = base64_decode('ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw');
print xor_encrypt($orig_cookie) . "\n";

Run this to obtain the key. Notice how the key actually contains the same four-character sequence, repeated many times? That suggests the those 4 characters are the key, not the whole string. Now we can use that key generate the encrypted output for our attack.

Let's adjust our PHP code to XOR the json_encoded new array, which sets the 'showpassword' value to 'yes':

function xor_encrypt($str) {
    $key = 'qw8J';
    $out = '';
 
    for($i=0; $i<strlen ($str); $i++) { // Iterate through each character
        $out .= $str[$i] ^ $key[$i % strlen($key)];
    }
    return $out;
}
$text = json_encode(array("showpassword" => "yes", "bgcolor" => "#ffffff"));
print base64_encode(xor_encrypt($text)) . "\n";

Execute the code, and we'll use this value as our cookie and see what happens. Bingo!

Level 12 will be posted shortly.