ManageEngine Password Pro
Weak Master Encryption Key Generation.
This is a public disclosure, WHAT? It's okay, I feel like public disclosure in this scenario is okay for a couple reasons. One, even though the flaw is serious it still leaves the customers with enough protection against most people. Two, all of ManageEngine products seems to have a few vulnerabilities due to lack of effort. Why should I go through the extra effort getting them to fix code that shouldn't be broken in the first place. I feel like the public should know. Really the only people that can still crack the encryption key is state sponsored actors.
Below I show my process of how I tracked down the function that generates the Master Encryption Key.
After installing ManageEngine Password Pro evaluation copy. Started poking around, one of the first items I went looking for was the encryption key that encrypts the data in the database. The Encryption key can be found here:
~/ManageEngine/PMP/conf/pmp_key.key
Contents of pmp_key.key
#This file contains the master AES encryption key for this installation, automatically generated by Password Manager Pro.
#The default location of this file is <PMP_HOME>conf and it is not secure to leave this file here, unless
#the server is sufficiently hardened to protect any illegal access of this file.
#It is highly recommended to move this file out of its default location and for instructions to securely store this file refer.
#Thu Nov 30 13:48:09 CST 2017
ENCRYPTIONKEY=x7J2821f*831*7ub*oXVsJsHbeTbmaZY
Awesome, Now how is the key made.
Found a helper tool in /bin/ called 'RotateKey.sh'. Which is used as a helper script to rotate the master key.
Looking at 'RotateKey.sh'
../jre/bin/java -Dswing.aatext=true -Djsse.enableCBCProtection=false -Djava.library.path=../lib/native -Dserver.dir="$SERVER_HOME" -Dserver.home="$SERVER_HOME" -cp "$CLASS_PATH" com.adventnet.pmp.PMPStarter "com.adventnet.passtrix.utils.RotateKey" "rotatekey"
You can see which java file handles this function "com.adventnet.passtrix.utils.RotateKey"
Using cfr.jar you can decompile all the Java files. I typically just decompile them all into one directory so it keeps the file structure.
find . -name '*.jar' -exec java -jar /opt/cfr.jar --outputdir output/ {} \;
After that magic runs, its time to find the RotateKey function. Its located here:
~/ManageEngine/PMP/output/com/adventnet/passtrix/utils/RotateKey
Looking at this code you see the libraries imported and the function call.
import com.adventnet.mfw.ConsoleOut;
import com.adventnet.passtrix.role.RoleConstants;
import com.adventnet.passtrix.utils.KeyRotationUtils;
import com.adventnet.passtrix.utils.StandAloneUtils;
...
else {
KeyRotationUtils.rotatePasswords();
Next in the chain, look at "com.adventnet.passtrix.utils.KeyRotationUtils;". Find the rotatePasswords() function and looked for what might be getting or setting the key.
PMPAPI.initializePMPED();
String oldKey = PMPAPI.get32BitKey();
ClientUtil.setPMPMasterKey();
if (!KeyRotationUtils.backupDatabase()) {
ConsoleOut.println((String)PMPApplicationResourcesUtil.getMsg(rb, "java.KeyRotationUtils.Error_backup", null));
throw new Exception("Error occurred while taking backup of the database.");
}
String new256BitKey = KeyRotationUtils.createPMPEncryptKey();
pmpEncryptDecrypt = KeyRotationUtils.getNewPMPEDInstance();
Here you see the 'createPMPEncryptKey();'. Searching for this in the same file you find the following:
public static String createPMPEncryptKey() throws Exception {
String generatedKey = null;
...
generatedKey = PasswordGenerator.generatePassword(32, 32, true, true, 3, true, true);
String keyFile = PMPAPI.getConfKeyPath();
...
try {
File f = new File(keyFile);
Properties props = new Properties();
props.load(new FileInputStream(f));
String oldKey = props.getProperty("ENCRYPTIONKEY");
String comments = "#OLDENCRYPTIONKEY=" + oldKey;
Properties properties = new Properties();
properties.setProperty("ENCRYPTIONKEY", generatedKey);
key = new BufferedWriter(new FileWriter(keyFile));
...
log.log(Level.INFO, "Encryption key file has been updated with the new key successfully.");
String string = generatedKey;
return string;
}
...
Alright, Getting closer. 'PasswordGenerator.generatePassword(32, 32, true, true, 3, true, true);' Matches the size of the key in the pmp_key.key file above. If you run 'RotateKey.sh' you get the line that says #OLDENCRYPTIONKEY. Now we need to find PasswordGenerator.generatePassword()
It's located in the same directory. After opening 'PasswordGenerator.java'. Look for generatePassword(). There are a couple functions named generatePassword(). Just need to match up with the arguments to the expected arguments to find the correct one. Below is the function it calls. I added my notes below marked with ##comment
## Min and Max Password length is 32.
## Mixed Case is required
## Special Chars are required
## 3 Special chars
## Must start with Letter
## Must have numbers
public static String generatePassword(int minPassLen, int maxPassLen, boolean mixedCaseRqd, boolean splCharRqd, int noOfSplChar, boolean startWithLetter, boolean isNumber) {
## Sets up 4 Arrays for lower case, upper case, digits, and special chars.
## As you can see they aren't using all the special chars.
char[] alpha = new char[]{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
char[] upperAlpha = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};
char[] numeric = new char[]{'0', '1', '2', '3', '3', '4', '5', '6', '7', '8', '9'};
char[] splchar = new char[]{'@', '