During a recent Red Teaming assessment we have found an internet-exposed instance of ManageEngine’s Password Manager Pro which was vulnerable to a pre-authentication Remote Code Execution (CVE-2022-35405). After gaining code execution we reverse engineered the password encryption/decryption routine to decrypt all the passwords and hack our way to become Domain Admin.
Red Team(ing) is an abused word in the InfoSec world and it’s commonly used to define various things:
When we - as in Shielder - say Red Team(ing) refer to the latest one: a real simulation of an attack, using the same techniques a real malicious party would use, to understand if the Security Operation Center (SOC) is able to detect and respond properly.
Recently we were engaged for a Red Teaming Assessment and, while analyzing the external perimeter during the initial reconnaissance phase, we detected an instance of ManageEngine Password Manager Pro, which, as suggested by its name, is a password manager. Finding a self-hosted password manager is usually a clue that the company has a good security awareness and you could expect your beloved Password Spraying for initial access to fail.
Besides what stated above, an internet-exposed password manager is also a great target for initial access as being able to compromise it might lead to full infrastructure takeover.
With this potential goal in mind, we checked the version of the ManageEngine Password Manager Pro instance and, based on that, we searched for known vulnerabilities.
Sometimes - as an attacker - you are just lucky and that was the case. The instace exposed by our customer was vulnerable to a fairly recent Unauthenticated Remote Code Execution (CVE-2022-35405). The vulnerability, which scored a CVSS of 9.8 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) has been discovered by Vinicus and there is a great write-up by Y4er (even though you might need some Google Translate-fu).
The Y4er’s write-up is pretty detailed, therefore we won’t dive deep into it but we will just show how to quickly craft a working exploit.
Based on the patch diff we know that the vulnerability is in the XML-RCP handler, where an attacker could add a Java serialized object, which will be unserialized by the server once received.
Java insecure deserialization is very dangerous. If you manage to force a Java application into deserializing an arbitrary object, then you could instanciate arbitrary classes among the available ones and eventually obtain arbitrary code execution. To do so, you must find a gadget chain, which process might be tedious and usually requires you to have the sourcecode or at least the bytecode of the application (if you are interested in how to find your own gadgets you should checkout , , and ).
Hopefully, tools come in handy for this task. @frohoff built a tool called ysoserial which contains a list of known gadgets and simplifies the creation of serialized objects. The power of ysoserial is that you could blindly create serialiazed objects for all the known gadgets and just try them to understand which one is working. Once done you could move on with the proper exploitation.
To do that we usually use some glue bash scripting, even though there is also a Burp Suite extension called Java Deserialization Scanner by our friend Federico Dotta which could be used to automate the detection and even the exploitation of insecure deserializations in Java web applications.
We hope one-liners estimators will love it, for the others - yes we know it could look better but that’s how lazy hackers would do it 😜
The idea behind this brute-force approach is that, if a given gadget works, then the server tries to resolve
$gadgetName.$ourBurpCollaboratorDomain and we would get an entry in our Burp Suite Collaborator client showing that the attack worked and which gadget(s) did the job.
In our case, the winner was …🥁🥁🥁… CommonsCollections5!
From that point on it’s just a matter of Powershell-fu, obfuscation, and AV/EDR evasion to get your reverse shell.
$ nc -lvnp 80 Listening on 0.0.0.0 80 Connection received on 18.104.22.168 56073 Microsoft Windows [Version 10.0.14393] (c) 2016 Microsoft Corporation. All rights reserved. C:\ManageEngine\PMP\bin>
There are various types of password managers around, starting from a post-it on the monitor, going through a txt file on the desktop, evolving in cloud-based SaaS products, till devices with hardware-based encryption.
What we are analyzing is an enterprise-grade self-hosted password manager. These kind of products, besides the usual password manager features (i.e. storing password in an encrypted way and allowing the entries owner to read them), also allow password sharing between different users by setting up groups/collections. In a company environment this is a key feature as you want/need multiple people to collaborate on the same project(s) and let them using/editing the required credentials.
As passwords should be accessed but at the same time protected, password managers encrypt them.
In a single-user scenario a strong and secure password manager would:
This implementation is as secure as the encryption algorithm in use and the passphrase roboustness.
When it comes to multiple-user scenarios the encryption approach is not obvious anymore. Let’s take as example how Bitwarden securerly implements this feature:
Again, this implementation is as secure as the encryption algorithm in use and the passphrase roboustness.
In both the cases the password manager is agnostic and used as an encrypted objects storage only. This means that, even though a user compromises the password manager confidentiality, they could not read the plaintext passwords (unless a weak encryption algorithm or a weak passphrase have been used).
Unfortunately, not all the password managers are the same.
Due to its type of audience an enterprise-grade software usually needs(?) to prioritize business continuity, therefore security might be traded for that. What does it mean? It means that in the aforementioned examples, if a user forgets their own passphrase, then the passwords are lost forever. Obviously, if multiple users could access the same shared passwords, then all of them should forget their passphrase at the same time but that’s still a risk. That’s why some password managers, including ManageEngine Password Manager Pro, use a different approach:
While at a first glance this approach could look secure, it exposes the passwords to various threats:
If these threats look too potential to you - keep reading and learn how we reverse engineered ManageEngine Password Manager Pro to obtain all the plaintext passwords and 🧗 to Domain Admin.
Let’s wrap-up what we have, in case we lost someone in the password managers digression:
Through our reverse shell we collected some information:
By reading the documentation we understood that passwords are stored in the database and that the DB password is stored inside the
database_params.conf file. Unfortunately, the DB password is encrypted itself, therefore we need to understand how to decrypt it.
NOTE: While approaching these kind of tasks it’s important to keep in mind we could assume that all the information needed to encrypt/decrypt are on the server itself, otherwise service restarts would need a manual intervention and such procedure is not mentioned anywhere in the documentation.
The method that takes care of decrypting the DB password is
getDecryptedPassword of the
com.adventnet.passtrix.db.DecryptDBPassword class which is just a proxy for the
decryptPassword method of the
com.adventnet.passtrix.ed.PMPEncryptDecryptImpl class, which accepts two arguments:
NOTE: Keep the
decryptPasswordmethod in mind since it is used to decrypt a lot of passwords with different decryption keys based on the context.
When decrypting a password through
decryptPassword, PMP always performs the following steps:
SHA1 HMACas hashing algorithm.
256 bitof output keylength.
no paddingusing the IV and the derived key.
The decryption routine is nice and all but what’s the password? By analyzing the code it is possible to discover that the password is hardcoded and it is:
md5(substring("@dv3n7n3tP@55Tri*", 5, 10)).
Once we had access to the database we tried to dump all its content but we noticed that the values stored inside the
PASSWORD column of the
Ptrx_PassBasedAuthen table were in the following format
As mentioned before, all the passwords are decrypted using the
decryptPassword method, which accepts the ciphertext base64-encoded, while in the database we had some hex values.
Let’s go ⏪⏪ to the source code.
PMP defines a set of “encrypted columns” which have an extra layer of encryption. In fact the data stored in such colums are encrypted at the database level with the
master key and then again at application level by using the
PMP key is stored in a
pmp_key.key file on the server filesystem and its location is defined in the
manage_key.conf configuration file. Such key is installation-specific and it is automatically generated by PMP during the first startup.
master key, on the other hand, is encrypted with the
PMP Keyand stored inside the database in the
notesdescription column of the first row of the
This latter key is used as the decryption secret in the
decryptsblob database functions.
To wrap-up, in order to decrypt a password or any double-encrypted value we need to:
master keywith the
PMP keyusing the
master keyin the database query to extract the decrypted content (e.g.
SELECT decryptschar(PASSWORD, "master_key") FROM Ptrx_PassBasedAuthen).
decryptPasswordmethod with the
Using what we have learned, we created the following Java program which can:
The program is just a quick’n’dirty proof of concept, therefore is not meant to automate the full process:
PMP keyfrom the ManageEngine PMP server and run the program to decrypt the DB password.
select notesdescription from Ptrx_NotesInfo
master keyobtained at step 2:
select ptrx_account.RESOURCEID, ptrx_resource.RESOURCENAME, ptrx_resource.DOMAINNAME, ptrx_resource.IPADDRESS, ptrx_resource.RESOURCEURL, ptrx_password.DESCRIPTION, ptrx_account.LOGINNAME, decryptschar(ptrx_passbasedauthen.PASSWORD,'master_key') from ptrx_passbasedauthen LEFT JOIN ptrx_password ON ptrx_passbasedauthen.PASSWDID = ptrx_password.PASSWDID LEFT JOIN ptrx_account ON ptrx_passbasedauthen.PASSWDID = ptrx_account.PASSWDID LEFT JOIN ptrx_resource ON ptrx_account.RESOURCEID = ptrx_resource.RESOURCEID
Why didn’t you create a new user and logged in the PMP web panel? Creating a user by interacting directly with the database is a hard process as each user has his own keystore with various information inside. To encrypt/decrypt the keystore the whole encryption/decryption flow should be understood and re-implemented, therefore it’s just easier to go straight with the DB decryption instead of creating a new user.
Will this work also if other Database Management Systems are in use?
Yes and no, the general approach is the same, but the database-level encryption is different for each DBMS (e.g. MySQL uses the
AES_DECRYPT function, MSSQL uses the
decryptbykeyautocert function, some old PostgreSQL versions use the
pgp_sym_decrypt function, etc.).
On which version did you performe your analysis? Password Manager Pro 12.1 in its default configuration (aka PostgreSQL as DBMS).
Sometimes - not even that occasionally - security products might become a single point of failure for your business. Make sure to pick the right ones during you software selection process and to protect them as much as possible.
Along with that, performing Red Teaming Assessments would help you figuring out how your detection and response capabilities are mature against a motivated attacker:
5 September 2022
I’m Abdel Adim Oisfi aka smaury.
Job: CEO, Security Researcher, Penetration Tester at Shielder.
Passions: Hacking, hitchhiking, cliff jumping and skinned knees.
I’m thezero, Security Researcher and Senior Penetration Tester at Shielder.
In the office I’m the one with the soldering iron.