前言
忙完考试周,又重新开始搁置许久的学习了 新学期开坑JAVA web 准备从反序列化漏洞入手 先分析一波ysoserial吧
java序列化
java.io.Serializable
jdk提供了java进行序列化与反序列化的方法 一个类若是实现了 java.io.Serializable
接口且其所有的属性都可以被序列化 则它这个类可以被序列化
1 | // 将类序列化为字节码 |
demo程序如下
1 | import java.io.*; |
序列化后的类长这个样子 👇
1 | cat test | xxd |
其中 aced
为魔术头 0005
为版本号 更多可见 这篇文章
除了静态变量不会被序列化之外 java中存在关键字 transient
使得类被序列化时 被该关键字所修饰的属性不会被序列化
java.io.Externalizable
Externalizable 接口继承于 Serializable 接口
1 | // 将类序列化为字节码 |
通过该接口进行序列化时 序列化的操作完全由👆这两个函数决定
1 | import java.io.*; |
恶意类
类被反序列化时会调用 readObject() 函数 所以可以通过重写 readObject() 方法来实现一个恶意的类
1 | import java.io.*; |
1 | import java.io.*; |
ysoserial
1 | Usage: java -jar ysoserial.jar [payload] '[command]' |
用于生成对应利用链的payload
CommonsCollections1
关键在于以下几个类
ConstantTransformer
1
2
3
4
5
6
7
8public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}InvokerTransformer
1
2
3
4
5
6
7
8
9
10
11
12public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
public Object transform(Object input) {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
}ChainedTransformer
1
2
3
4
5
6
7
8
9
10
11public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
顾名思义 第一个类的作用是返回一个 Object 可以作为链的起点 第二个类的作用是反射调用某个类的某个方法 第三个类是迭代的利用第二个类
所以考虑将以下调用转换为链的形式
1 | java.lang.Runtime.getRuntime().exec(cmd); |
最后封装到一个 ChainedTransformer 中 若其 transform 方法被调用 则 POP 链可以得到执行
所以接下来是寻找一个可以利用的类 其需要满足
- 重写了 readObject() 方法
- readObject() 部分可以触发 transform
cc1 利用的链:
- proxy(lazymap).entrySet() -> InvocationHandler(lazymap).invoke() -> lazymap.get() -> transform()
链的关键在于:
其中的 memberValues 为 lazymap 的一个动态代理类 其调用方法时 会触发以下逻辑:
可以看到其中存在 memberValues.get 则会跳转到
触发 transform 执行了payload
另外的可以利用的链:
- TransformedMap.setValue() -> TransformedMap.checkSetValue()
1 | protected Object checkSetValue(Object value) { |
CommonsCollections3
cc3 的 利用环境和 cc1 是一样的 算是对 commons-colllections3.1 的另一种利用方式
1 | new ConstantTransformer(TrAXFilter.class), |
首先理解一下如如何使用 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
类进行 rce 关键部分如下:
即如果能触发该类的 newTransformer() 函数 则该类会从 _bytecodes 中获取字节码 并生成一个对应类的实例 所以可以构造一个恶意类 将其字节码设置进入 _bytecodes 中 当其被实例化时 达到 rce 的目的
其次就是理解 cc3 与 cc1 在 chain 部分的不同 cc3 通过 ConstantTransformer 获取到 TrAXFilter.class 然后利用 InstantiateTransformer 生成一个 TrAXFilter 的实例 其构造方法中调用了成员的 newTransformer() 函数 触发 payload
CommonsCollections2
cc2 同样是利用了 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
类进行 rce 其攻击的版本为 commons-collections4 利用了另外的链进行触发
1 | PriorityQueue.heapify(); |
这条链的入口在于 PriorityQueue 的 readObject() 函数 当比较函数中存在恶意类时 触发了 Transformer 的比较器 而其中存在触发 transform() 的点 之后就很简单了 cc2 是用的 InvokerTransformer 直接反射调用 TemplatesImpl 的 newTransformer() 函数
CommonsCollections4
cc4 是 cc2 的另一个触发链 触发方式和 cc3 是一样的 即通过 InstantiateTransformer
CommonsCollections5
cc5 攻击版本为 Commons-Collections3 不同于cc1触发 AnnotationInvocationHandler 的 readObject() 方法进行反序列化攻击 cc5 利用的类为 BadAttributeValueExpException 类 要求 JVM 不启用 SecurityManager
- TiedMapEntry.toString() -> TiedMapEntry.getValue() -> LazyMap.get()
CommonsCollections6
cc6 攻击的版本为 Commons-Collections3 反序列化入口为 HashSet 类的 readObject() 方法 其中 put() 方法最后会触发 TiedMapEntry 的 hashcode() 方法 其中存在 getValue() 触发 rce
CommonsCollections7
cc7 攻击版本同样为 Commons-Collections3 反序列化入口为 Hashtable 类的 readObject() 方法
其实关键在于 构造两个 hash 一致的 lazymap 触发 equals 比较 然后 紧接着触发 lazymap.get() 唯一的坑点在于构造 payload 时触发过一次 lazymap.get() 所以需要将多出来的键值对 remove 掉
后记
这篇文章断断续续的写了五天 思路可能会显得稍微不连贯 如果文章有什么错误 也请师傅们多多包涵
分析完 cc 链 下一篇文章会接着分析 ysoserial 里用到的其他 POP 链