rmi反序列化导致rce漏洞修复_记一次Java反序列化漏洞的发现和修复

作者:bit4@勾陈安全

0x00 背景简介

本文是自己对一次反序列化漏洞的发现和修复过程,如有错误请斧正,大神请轻喷。

目标应用系统是典型的CS模式。

客户端是需要安装的windows桌面程序,但是它是大部分内容都是基于Java开发(安装目录有很多Jar包)。

服务端是基于Jboss开发。

客户端和服务端之间的通信流量是序列化了的数据流量。客户端接收和解析数据流量的模式和服务端一样,也是通过http服务,它在本地起了80、81端口,用于接收反序列化对象。

0x01 Java序列化流量特征

特征一

参考特征,反序列化数据看起来就是这个样子: sr 、类名、空白字符

特征二

固有特征,是Java的序列化数据就一定是这样,如果是base64编码的,就是以rO0A开头的。

特征三

参考特征,有些content-type就说明了它是是序列化数据。

0x02 检测工具

当确定是序列化数据后,我使用了2个会主动进行反序列化漏洞扫描的Burp Suite插件:

Java Deserialization Scanner - 能识别流量,也成功报了漏洞,而且使用了多种Payload。

Freddy - 只是识别了流量,并未报漏洞。

当时忘记截图了,这是后续在测试环境的截图:

Freddy的流量识别截图:

0x03 复现方法(攻击服务器端)

使用ysoserial生成一个Payload,这里以Jdk7u21为例,由于是内部系统,我知道服务器上JDK的版本。

java -jar ysoserial-master.jar Jdk7u21 "ping jdk.xxx.ceye.io" > Jdk7u21

将生成的Payload通过Burp suite向服务端进行请求,命令执行成功。

0x04 攻击客户端

晚上回家想了想,返回包也是序列化格式的,是不是可以试试攻击客户端呢?第二天来一试,还真的可以。

对客户端做了一个简单的分析,发现客户端在本地起了80和81端口,也是通过http 服务来接收序列化对象的,反序列化过程和服务端如出一辙。

java -jar ysoserial-master.jar CommonsCollections3 "calc.exe" >CC3-desktop

这里自己想象了一种攻击场景:当攻击者控制了服务器之后,可以干掉这个服务,自己开启一个恶意的服务端,当反序列化请求过来时,都返回一个恶意的响应包,比如反弹shell之类的,凡是使用了该客户端的用户都可能被攻击。危害还是不容小视的。

0x05 防护思路

到了这里就开始考虑修复了,我给开发提供了2种修复思路。

升级

升级服务端所依赖的可能被利用的jar包,当然还包括JDK。不错所料,开发一听就否了。一来升级需要经过各种功能性测试,耗时较长,二来是JDK的升级可能造成稳定性问题(之前一个AMF的反序列化问题就是如此,升了2个月了还没搞定)。

过滤

另一个思路就是过滤了,需要继承Java.io.ObjectInputStream实现一个子类,在子类中重写resolveClass方法,以实现在其中通过判断类名来过滤危险类。然后在JavaSerializer类中使用这个子类来读取序列化数据,从而修复漏洞。

0x06 失败的修复

我将以上的第二种修复思路给到了开发,并提醒参考SerialKiller项目。过了一段时间,开发告诉我修复了,然而我的验证显示漏洞依然存在。

只好硬着头皮去开发电脑上看代码,后来发现开发将过滤方法用在了下图的方法之后,而且是在判断获取到的对象是不是HashMap实例之后(开发不愿意给我截图了...)。到这里我终于发现了点不对劲,在判断对象类型了,岂不是说明已经反序列化完成了?

通过对这个getRequestData()函数的追踪,确定反序列化过程是在一个底层的Jar包中完成的。

0x07 对底层Jar包的分析

然后我拿到这个Jar进行了分析,它是公司自己开发的框架。最后艰难地理出了其中的调用逻辑:该框架基于struts2开发,从下图的调动逻辑可以看出,所有的请求到达服务端后,都会首先经过DataInterceptor这个拦截器,这个拦截器执行的动作就是反序列化数据然后给到Action。上面的getRequestData()方法,已经是在这个流程之后了,属于Action中的逻辑。故而开发的修复方式无效。

DataInterceptor类的实现如下:

public class DataInterceptor

extends BaseInterceptor

{

private static final long serialVersionUID = 1L;

public String intercept(ActionInvocation invocation)

throws Exception

{

HttpServletRequest request = (HttpServletRequest)invocation

.getInvocationContext().get("com.opensymphony.xwork2.dispatcher.HttpServletRequest");

Object action = invocation.getAction();

if ((action instanceof IDataAction))

{

IDataAction richAction = (IDataAction)action;

Serializable model = Toolkit.getSerializer().deserialize(

request.getInputStream());//这里执行了反序列化操作

richAction.setRequestData(model);//将对象传递给了Action,getRequestData()方法才能获取到

}

else if ((action instanceof IDataBundleAction))

{

IDataBundleAction richAction = (IDataBundleAction)action;

Serializable model = Toolkit.getSerializer().deserialize(

request.getInputStream());

richAction.setRequestDataBundle((DataBundle)model);

}

return invocation.invoke();

}

}

JavaSerializer的实现如下:

import java.io.BufferedInputStream;

import java.io.IOException;

import java.io.InputStream;

import java.io.ObjectInputStream;

import java.io.Serializable;

public class JavaSerializer

extends AbstractSerializer

{

public Serializable deserialize(InputStream in)

throws IOException

{

ObjectInputStream oo = new ObjectInputStream(new BufferedInputStream(in));

try

{

return (Serializable)oo.readObject();

}

catch (ClassNotFoundException e)

{

throw new IOException("序列化类文件找不到:" + e.getMessage());

}

finally

{

oo.close();

}

}

到这里就清楚了,真正有漏洞的代码就在这里。

0x08 成功修复

要修复这个漏洞,就需要将上面的第二种过滤修复方法用到这个类里,具体的实现方法和SerialKiller一样。

需要继承Java.io.ObjectInputStream实现一个子类(SerialKiller),在子类中重写resolveClass方法,以实现在其中通过判断类名来过滤危险类。然后在JavaSerializer类中使用这个子类来读取序列化数据,从而修复漏洞。

通过如上方法修复了该漏洞,验证也是成功修复。修复后的JavaSerializer类:

import com.xxx.xxx.xxx.core.security.SerialKiller;

import java.io.BufferedInputStream;

import java.io.IOException;

import java.io.InputStream;

import java.io.ObjectInputStream;

import java.io.Serializable;

public class JavaSerializer

extends AbstractSerializer

{

public Serializable deserialize(InputStream in)

throws IOException

{

ObjectInputStream ois = new SerialKiller(new BufferedInputStream(in));//SerialKiller是重写了resolveClass方法的子类。

try

{

return (Serializable)ois.readObject();

}

catch (ClassNotFoundException e)

{

throw new IOException("序列化类文件找不到:" + e.getMessage());

}

finally

{

ois.close();

}

}

0x09 意外之喜 - 另一处XMLDecoder反序列化漏洞

然而,对Java包的分析还发现了另外一处反序列化漏洞。问题出在对XML的反序列化过程中,和weblogic的XMLDecoder RCE如出一辙。

漏洞代码如下:

import java.beans.XMLDecoder;

import java.io.IOException;

import java.io.InputStream;

import java.io.Serializable;

public class XmlSerializer

extends AbstractSerializer

{

public Serializable deserialize(InputStream in)

throws IOException

{

XMLDecoder xd = new XMLDecoder(in);//和weblogic的XMLDecoder RCE如出一辙

try

{

return (Serializable)xd.readObject();

}

finally

{

xd.close();

}

}

它的修复方式也是参考了weblogic的,需要实现一个validate 函数,在执行XML解码(XMLDecoder)前,对InputStream对象进行检查过滤。

private void validate(InputStream is){

WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();

try {

SAXParser parser = factory.newSAXParser();

parser.parse(is, new DefaultHandler() {

private int overallarraylength = 0;

public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {

if(qName.equalsIgnoreCase("object")) {

throw new IllegalStateException("Invalid element qName:object");

} else if(qName.equalsIgnoreCase("new")) {

throw new IllegalStateException("Invalid element qName:new");

} else if(qName.equalsIgnoreCase("method")) {

throw new IllegalStateException("Invalid element qName:method");

} else {

if(qName.equalsIgnoreCase("void")) {

for(int attClass = 0; attClass < attributes.getLength(); ++attClass) {

if(!"index".equalsIgnoreCase(attributes.getQName(attClass))) {

throw new IllegalStateException("Invalid attribute for element void:" + attributes.getQName(attClass));

}

}

}

if(qName.equalsIgnoreCase("array")) {

String var9 = attributes.getValue("class");

if(var9 != null && !var9.equalsIgnoreCase("byte")) {

throw new IllegalStateException("The value of class attribute is not valid for array element.");

}

}

}

}

});

} catch (ParserConfigurationException var5) {

throw new IllegalStateException("Parser Exception", var5);

} catch (SAXException var6) {

throw new IllegalStateException("Parser Exception", var6);

} catch (IOException var7) {

throw new IllegalStateException("Parser Exception", var7);

}

}

0x10 总结

零零散散学习反序列化漏洞已经很长时间了,这是第一次在公司自研框架中发现反序列化漏洞,所以总结记录一下,大神请轻喷!也算是对该漏洞知识的一个梳理。学习还是要实践过、做过了才算是学到了,否则永远不是自己的!

0x11 参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值