起 因
需要解决的问题
一、动态修改Java依赖
如果两个不同版本的库使用了同一个类,而这两个类可能有一些方法和属性有了变化,此时在序列化通信的时候就可能因为不兼容导致出现隐患。因此,Java在反序列化的时候提供了一个机制,序列化时会根据类的属性和方法,通过固定算法计算出一个当前类的serialVersionUID值,写入数据流中;反序列化时,如果发现对方的环境中这个类计算出的serialVersionUID不同,则反序列化就会异常退出,避免后续的未知隐患。
-
CB版本不同,serialVersionUID不同
客户端commons-beanutils : 1.9.2
服务端commons-beanutils : 1.8.3
Caused by: java.io.InvalidClassException: org.apache.commons.beanutils.BeanComparator; local class incompatible: stream classdesc serialVersionUID = -2044202215314119608, local class serialVersionUID = -3490850999041592962
-
CB版本不同,serialVersionUID相同
客户端commons-beanutils : 1.8.2
服务端commons-beanutils : 1.8.3
二、更灵活的构建Payload
通过自定义ClassLoader动态修改依赖
一、双亲委派模型
二、具体实现
在自定义ClassLoader前,需要先了解ClassLoader的几个重要方法:
关键方法
① loadClass
loadClass(String, boolean)
函数即实现了双亲委派模型
首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用 parent.loadClass(name, false);
).或者是调用bootstrap
类加载器来加载。如果父加载器及 bootstrap
类加载器都没有找到指定的类,那么调用当前类加载器的findClass
方法来完成类加载。
② findClass
抽象类 ClassLoader
的findClass
函数默认是抛出异常的。而loadClass
在父加载器无法加载类的时候,就会调用我们自定义的类加载器中的findeClass
函数,因此我们必须要在loadClass
这个函数里面实现将一个指定类名称转换为Class
对象,Java正好
提供了defineClass
方法,通过这个方法,就可以把一个字节数组转为Class对象。
③ defineClass
defineClass
主要的功能是将一个字节数组转为Class
对象。
④ 依赖存储
⑤ 类加载
⑥ 自定义协议
场面一度陷入了尴尬的境地,笔者当初甚至想过放弃一开始的想法,尝试通过落地文件进行依赖加载。但是在网上冲浪的过程中,发现有前辈提出过一种思路,就是通过URLStreamHandlerFactory自定义协议,伪造一个可以访问内存内容的URL,这样就可以通过URLClassLoader进行加载了。通过查阅Java官方文档关于java.net.URLStreamHandlerFactory的描述,我们可以发现,Java对URL的大致处理流程为:
-
如果已经设置了一个URLStreamHandlerFactory实例,那么Java会优先把protocol字符串作为入参,调用该URLStreamHandlerFactory的createURLStreamHandler方法。 -
如果未设置URLStreamHandlerFactory,或者createURLStreamHandler方法返回null,那么构造器就会去寻找package以及System default package中是否有能处理对应协议的handler,如果都找不到,那么会抛出一个MalformedURLException异常。 -
http,https,file,jar这四种协议的handlers是默认一定存在的。
public static List<URL> init(String[] resourceJars) throws Exception {
List<java.net.URL> urls = new ArrayList<>();
java.net.URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory() {
public URLStreamHandler createURLStreamHandler(String urlProtocol) {
if ("kuro".equalsIgnoreCase(urlProtocol)) {
return new URLStreamHandler() {
@Override
protected URLConnection openConnection(URL url) {;
String key = url.toString().split(":")[1];
return new URLConnection(url) {
public void connect() {
}
public InputStream getInputStream() {
return new ByteArrayInputStream(map.get(key).toByteArray());
}
};
}
};
}
return null;
}
});
for (String resourceJar : resourceJars) {
InputStream in = KuroClassLoader.class.getResourceAsStream(resourceJar);
int len = -1;
byte[] bytes = new byte[1024];
ByteArrayOutputStream jarBytes = new ByteArrayOutputStream();
while ((len = in.read(bytes)) != -1) {
jarBytes.write(bytes, 0, len);
}
map.put(resourceJar, jarBytes);
urls.add(new URL("kuro:" + resourceJar));
}
return urls;
}
代码主要做下面这两件事:
-
遍历传入的Jar包列表,读取字节码并存入Map中,同时为每个Jar生成一个kuro协议的url,返回url列表。 -
通过URL的setURLStreamHandlerFactory方法,扩展一个URL协议,可通过kuro:/lib/javassist-3.26.0-GA.jar直接访问map中的jar包。
以上,我们就解决了动态依赖修改的问题。下面来解决Payload生成的问题。
Payload生成
byte[] exploit = new ExecCommand().getExploit(“open -a Calculator”);
byte[] payloads = new CommonsBeanutils().getPayload(exploit);
Gadget改造
-
TemplatesImpl
-
ChainedTransformer
结 语
参考资料
-
CommonsBeanutils与无commons-collections的Shiro反序列化利用
(https://www.leavesongs.com/PENETRATION/commons-beanutils-without-commons-collections.html)
-
Java自定义类加载器与双亲委派模型
(https://www.cnblogs.com/wxd0108/p/6681618.html)
-
如何实现Java类隔离加载
(https://zhuanlan.zhihu.com/p/351378828)
-
Java 自定义 ClassLoader 实现隔离运行不同版本jar包的方式
(https://blog.csdn.net/t894690230/article/details/73252331)
-
使用resource中的jar包资源作为URLClassloader
(https://www.cnblogs.com/silyvin/articles/12178528.html)
-
URL(Java Plat form SE 8)
(https://docs.oracle.com/javase/8/docs/api/java/net/URL.html#setURLStreamHandlerFactory-java.net.URLStreamHandlerFactory-)
-
ysoserial
(https://github.com/frohoff/ysoserial)
关注我
掌握最新动态
你这么好看,一定要点个在看
本文始发于微信公众号(58安全应急响应中心):技术文章 | 反序列化Payload生成框架(一)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论