Friday, June 21, 2013

How to rapidly build a Burp session handling extension using JavaScript

As a penetration tester you will regularly come across somewhat "odd" applications build by companies or institutions with very high protection requirements. These applications will have some proprietary request signing functionality to ensure that the requests can not be tempered with in transit.  Sometimes this even goes as far as completely encrypting the requests to the server. 
Sure enough, this is very annoying, because it requires the pentester to recreate the client functionality in order to be able to test the application efficiently. So often times you'll find yourself in front of this application investing a lot of time into reverse engineering the e.g signing flow and functionality and wrap it into a Burp extension before actually being able to start the real security assessment. 
Besides messing with the signature implementation and work flow you actually need to be able to conveniently test the application by creating a valid signature for every modified request. Obviously this is something you don't want to do manually. Unfortunately, sometimes translating JavaScript functionality into working Java code, that allows you to automate the signing process using a Burp extension, can be a tedious and error prone task.  Fortunately though, even when doing a black box audit, most of the time all the source code you need is right there in the JavaScript of the e.g. web application or Phonegap Android/iOS app.
Let's take a look at how to create a Burp extension that relies as much on Java as it does on JavaScript to get the obstacle of request signing out of the way to facilitate unimpeded penetration testing.
The basic functionality that the Burp extension requires can be divided in five steps:
  1. Intercept specific requests to the application
  2. Extract the (manipulated) request body
  3. Do the actual request signing (Java/JS combo)
  4. Update the request header with the new signature
  5. Send the updated request to the application
The Java portion of this code will be less than 50 lines of code and that code itself is very generic and will allow you to do future modifications easily.  Let's go through creating that Burp extension step by step:

public class BurpExtender implements IBurpExtender, IHttpListener, ISessionHandlingAction
{
    // domain of our target application
    private static final String HOST_FROM = "www.example.com";    
    private IExtensionHelpers helpers;
    private PrintWriter stdout;
    private PrintWriter stderr;
    
    @Override
    public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks)
    {
        // obtain an extension helpers object
        helpers = callbacks.getHelpers();
        // set our extension name
        callbacks.setExtensionName("Request Signing");
        // obtain our output and error streams
        stdout = new PrintWriter(callbacks.getStdout(), true);
        stderr = new PrintWriter(callbacks.getStderr(), true);
        //register the handler that does that alters the HTTP request
        //this has to be enabled via the Burp Session handling options
        callbacks.registerSessionHandlingAction(this);
    }

    @Override
    public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo){}

    @Override
    public String getActionName() { return "Sign"; }

The core of all Burp extensions is the class BurpExtender that implements IBurpExtender and other required interfaces and defines private member variables that are used throughout the extension. The method registerExtenderCallbacks() is invoked on startup and provides methods that can be used by the specific implementation to perform certain actions. In our case we will use it to register a custom session handling action named "Sign" thus we have to override the method performAction() that is called when our "Sign" session handling action is triggered.
Now after understanding the basic framework of our Burp extension, it is time to look at how the performAction() method is overridden.
@Override
    public void performAction(IHttpRequestResponse currentRequest, IHttpRequestResponse[] macroItems) {
        // get the HTTP service for the request
        IHttpService httpService = currentRequest.getHttpService();
        
        // get the URL of the request
        URL url= helpers.analyzeRequest(currentRequest).getUrl();
        
        // if the target host is the right one and the url is not e.g login
        if (HOST_FROM.equalsIgnoreCase(httpService.getHost()) && !url.getPath().equalsIgnoreCase("/login")){
            // get the request info
            IRequestInfo rqInfo = helpers.analyzeRequest(currentRequest);
            // retrieve all headers
            List headers = rqInfo.getHeaders();
            // get the request
            String request = new String(currentRequest.getRequest());
            // get the request body 
            String messageBody = request.substring(rqInfo.getBodyOffset());
            String signature = null;
            try {
                signature = SignBody(messageBody);
            } catch (Exception e) { stdout.println(e.toString()); }
            
            // go through the header and look for the one that we want to replace
            for (int i = 0; i < headers.size();i++){
                if(headers.get(i).startsWith("Signature-Header:"))
                    headers.set(i, "Signature-Header: " + signature);
            }
            
            // create the new http message with the modified header
            byte[] message = helpers.buildHttpMessage(headers, messageBody.getBytes());
            // print out the debug message if applicable (will be shown in the ui of Burp)
            stdout.println(helpers.bytesToString(message));
            stdout.println("---------------");
            // replace the current request and forward it
            currentRequest.setRequest(message);  
        }  
    }


The code itself is quite straight forward. First of all you retrieve the HTTP service for the current request, and then extract the URL from it. This can be used to further define which requests to the target application shall be actually processed by our extension and which requests should be ignored e.g the login functionality
Once the desired request is identified, we need to extract the HTTP message body. Keep in mind that the request body that is processed here has already been modified by either tool of the Burp suite such as the proxy, repeater, scanner, etc. The requested body is then handed to a method we shall generically call SignBody() that actually does the signing for us and returns the correct value. Then we just iterate through all the HTTP headers and replace the signature header with the correct updated value to make the request legit. Finally, what is left to do is to create an updated HTTP message and replace it in the current request.

So far all the functionality is rather generic and can be quickly adapted to any specific needs. The last piece of the puzzle is the integration of JavaScript file that does the actual signing and integrate it in our burp extension. 
//File: sign.js
/*
CryptoJS v3.1.2
code.google.com/p/crypto-js
(c) 2009-2013 by Jeff Mott. All rights reserved.
code.google.com/p/crypto-js/wiki/License
*/
var CryptoJS=CryptoJS||function(a,m){
// ...
// required JS libraries are copied into the beginning of the file
// ...

// specific proprietary JS code that follows the required signing process
var passbytes = CryptoJS.enc.Utf16LE.parse('password');
var passhash = CryptoJS.MD5(passbytes);
var res = CryptoJS.enc.Utf8.parse('sessiontoken');
res.concat(passhash);
// ...

// the requestBody variable will be set by Java code
var data = CryptoJS.SHA512(CryptoJS.enc.Utf8.parse(requestBody));
var encrypted = CryptoJS.AES.encrypt(...);
var enc = CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
// ...
// the result variable will be read by the Java code as well
var result =  "--howeverthisisconcatinated--";


The first thing to note is that ScriptEngine does not by default resolve and download any  "<script src='xxx'>" includes which means that the required external code has to be downloaded before and added to the beginning of the JavaScript file. Besides that it is very straight forward JavaScript. In the above code snippet there is an example of how a signing routine could look like, but the beauty of this approach is that recycling any specific existing JavaScript code is as simple as copy-pasting. Two additional things to note are the variable requestBody and result which are set and retrieved by the burp extension.

How this is done is shown in the last source code part right below:
public String SignBody(String requestbody) throws GeneralSecurityException, FileNotFoundException, IOException, ScriptException{
        ScriptEngineManager factory = new ScriptEngineManager();
        // escape all the single quotes or do other required modifications to the body
        String bodyRequest = requestbody.replace("'","\\'");
    
        ScriptEngine engine = factory.getEngineByName("JavaScript");
        // set the RequestBody for the javascript functionality
        engine.put("requestBody",bodyRequest);
        // execute the js
        engine.eval(new java.io.FileReader("/path/to/sign.js"));
        // read the result variable from the js 
        Object res = engine.get("result");
        // and return it
        return res.toString();
}
All that is done here is setting the variable requestBody in the JavaScript with our updated HTTP request body and finally reading the result variable and returning it so it can be placed in the HTTP header.

Well and that's it folks!

The complete BurpExtender.java can be downloaded here and a working Netbeans project here.