Twitter
« Insights from the Crypto Trenches | Main | Local Request Forgery »
Wednesday
May252016

AirWatch Vulnerabilities from the GDS Archives

Overview

At GDS, we’ve researched many Mobile Application management (MAM) container platforms over the years. Early last year, we published a whitepaper and security checklist on the subject to assist developers, security teams, and buyers of MAM platforms. Today we’re releasing details on vulnerabilities in the AirWatch MAM platform that we discovered two years ago that have long since been patched. We still regularly run into these types of bugs in similar platforms in 2016, so we thought it’d be helpful to take a look at a few of them in detail.

In May 2014, we were doing independent vulnerability research poking around at the AirWatch Mobile Device Management Secure Content Locker (SCL) application for Android. During our brief analysis, we discovered a number of vulnerabilities in the SCL application that could allow an attacker with access to a user’s device to bypass the application security and gain access to the encrypted content stored in the application’s sandbox. GDS disclosed the vulnerabilities to AirWatch in May 2014. These have all been remediated by the application vendor and are listed below:

1. User Passwords Susceptible to Offline Brute Force Attacks

2. Inadequate Enterprise Wipe

3. Symmetric Key stored using Java String Object

4. Authentication Bypass via Exported Activities

5. Static IV Used in Cryptographic Operation 

These issues, discovered by Ron Gutierrez, were present in AirWatch Android Agent v4.5.3.782 and Secure Content Locker (SCL) v1.9.2, and were fixed in versions 5.1 and 2.1 respectively. GDS would like to commend AirWatch on their responsiveness and rapid deployment of fixes.

These issues will be discussed further in the rest of this blog post.

Issue Descriptions

User Passwords Susceptible to Offline Brute Force Attacks

The routines used to perform offline authentication to the AirWatch Secure Content Locker application used an unsalted SHA-256 hash to validate the user’s passphrase. This made it possible for an attacker, who gains access to a victim’s stolen device, to perform an offline brute force attack on this hash, in order to determine the AirWatch authentication credentials. Depending on how an organization integrates authentication with AirWatch these credentials are likely to be the user’s Active Directory password. However, as was in our case below this password could be an AirWatch specific password. Note, while AirWatch employs device root detection this does not prevent an attacker who has stolen the device from rooting it and then directly accessing the configuration file storing the hash. This configuration file is at the following location:

/data/data/com.airwatch.contentlocker/shared_prefs/com.airwatch.contentlocker_preferences.xml

An example of the contents of this file is shown below:

<string name="seedHash">NEHfC6vCot2lUdfNOfsjW8TgnNHkVWvyYbtJGI9Ug0g=</string>

GDS verified that this was indeed the AirWatch specific password set up for our test device using the following python code:

>>> import base64
>>> import hashlib
>>> output = base64.b64encode(hashlib.sha256("testing1234").digest())
>>> print(output)
'NEHfC6vCot2lUdfNOfsjW8TgnNHkVWvyYbtJGI9Ug0g='

Inadequate Enterprise Wipe

AirWatch provides an Enterprise Wipe option that is available to device administrators. This option performs an application level wipe rather than initiating a full device wipe, i.e. for organizations using AirWatch to remove company resources from an employee owned device while leaving personal data and the underlying phone configuration intact.

Unfortunately, the Enterprise Wipe functionality failed to adequately eliminate company data from the device’s application directory. Therefore, even after this wipe was performed potentially sensitive data was still present in the following directories:

/shared_prefs
/databases
/files
/sdcard/Android/data/com.airwatch.contentlocker/

This can be verified by performing an “Enterprise Wipe” yourself and then checking these directories to see the artifacts left behind, such as the “seedHash” from the finding above.

Symmetric key stored using Java String Object

This vulnerability had two distinct issues. Firstly, storing sensitive values in a Java String object will prevent the value from being properly cleared from memory. This is the same reason why it is not recommended to use String objects to process passwords. Java String objects are immutable and can persist long after they are required by the application. The application has little control over when the garbage collector will recycle the memory and even when it is collected the contents may persist until the memory has been reallocated and written to. This increases the risk of the key being read from device memory by malware or stolen using a privilege escalation vulnerability that does not require a device reboot. This vulnerability was verified by decompiling the source code of the binary application as shown below:

private String e(String paramString1, String paramString2)
{
 char[] arrayOfChar = paramString1.toCharArray();
 String str1 = this.d.getString("Vector", "");
 String str2;
 if (str1.length() == 0)
 {
   str2 = h();
   String str3 = Base64.encodeToString(str2.getBytes(), 0);
   SharedPreferences.Editor localEditor = this.d.edit();
   localEditor.putString("Vector", str3);
   localEditor.commit();
 }
 byte[] arrayOfByte;
 while (true)
 {
   PBEKeySpec localPBEKeySpec = new PBEKeySpec(arrayOfChar, a(paramString2,
                                                 str2, "A1rW4tchR0xin4t0R"),
                                               20000, 256);
   arrayOfByte = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
                                 .generateSecret(localPBEKeySpec)
                                 .getEncoded();
   if (arrayOfByte != null)
     break;
   return null;
   str2 = new String(Base64.decode(str1, 0));
 }
 return new String(arrayOfByte);
} 

The second issue is that using a String object to store a symmetric key value can lead to a decrease in the overall entropy of the key due to String objects attempting to convert the key material to charset encodings. On Android, the default charset is UTF-8 and any byte sequence that is not a valid UTF-8 character will be replaced with the byte sequence EF BF BD (hex), which corresponds to the Unicode codepoint U+FFFD ‘REPLACEMENT CHARACTER’. This introduces a security risk since invalid byte sequences within the key will be converted to the EFBFBD sequence reducing the overall entropy of the key.

Using the Cydia Substrate tool available on rooted Android devices we can write hooks for Java methods to, for example, print out the contents of the parameters during runtime. The following hook was written to print out the contents of the parameters to the com.airwatch.crypto.openssl.e.b(byte[], String) method.

MS.hookClassLoad("com.airwatch.crypto.openssle", new MS.ClassLoadHook() {
 public void classLoaded(Class<?> resources) {
   String methodName = "a";
   Method lmethod;
   try {
     lmethod = resources.getMethod(methodName, Context.class);
   } catch (NoSuchMethodException e) {
     Log.w(_TAG, "No such method: " + methodName);
     lmethod = null;
   }
   final MS.MethodPointer old = new MS.MethodPointer();
   if (lmethod != null) {
   MS.hookMethod(resources, lmethod, new MS.MethodHook() {
       public Object invoked(Object resources, Object... args)
                     throws Throwable {
         Log.i(_TAG, "com.airwatch.a.b.a() is hit");
         Log.i(_TAG, "param1: " + bytesToHex((byte[])args[0]));
         Log.i(_TAG, "param2: " + printString((String)args[1]));
         return old.invoke(resources, args);
       }
     }, old);
   }
 }
});
public static String bytesToHex(byte[] bytes)
{
 char[] hexChars = new char[bytes.length * 2];
 for ( int j = 0; j < bytes.length; j++ ) {
   int v = bytes[j] & 0xFF;
   hexChars[j * 2] = hexArray[v >>> 4];
   hexChars[j * 2 + 1] = hexArray[v & 0x0F];
 }
 return new String(hexChars);
}

The console output below shows the values returned by the application.

param1: 4B46FAF3F28FA6B3F7A44951D4F8330CCD0C82D42438D053209B21473EEA130545605F245D241E1B332ECBF87F263B9E

// Base64 decoded ‘master_encryption_key’ stored in SharedPreferences XML file.

param2: 1B3C495B47EFBFBD3426EFBFBD76EFBFBDEB8794EFBFBDEFBFBD47EFBFBDEFBFBD68EFBFBD0E4F332D5B76EFBFBDEFBFBD285938

// Derived Encrypted Key from AirWatch Password

As you can see in the output above, due to the charset conversion process this issue causes the resulting symmetric key to lose a total of 22 bytes of entropy rather than containing the the expected 32 bytes of entropy.

Authentication Not Enforced when calling Search or Main Activities directly.

The application contains several Activities, which can be found in the AndroidManifest.xml file, that can be abused to bypass the password lock screen. These Activities do not contain the necessary logic to force the user back to the authentication screen. By exploiting this issue an attacker can access metadata of files stored within SCL without knowledge of the user’s login credentials. Therefore, we can deduce that either the file metadata is not encrypted by the application, or that the application can decrypt the content stored in its application directory without requiring the user’s credentials. Either scenario is bad for an application such as this. A walkthrough for exploiting this behavior in the Search Activity is provided below. Explotation of the Main Activity is identical, the only caveat being that exploiting the Main Activity requires the device be to rooted.

Below you can see the intent filter for the SearchActivity from the AndroidManifest.xml file:

<activity android:name="com.airwatch.contentlocker.ui.SearchActivity"
         android:launchMode="singleTop"
         android:windowSoftInputMode="adjustPan">
 <intent-filter>
   <action android:name="android.intent.action.SEARCH" />
 </intent-filter>
 <meta-data android:name="android.app.searchable"
            android:resource="@xml/searchable" />
</activity>

 

With the SCL application closed out on the target device, connect your adb in the usual manner. After which enter the following commands to manually invoke the “SearchActivity” intent:

adb shell am start -a android.intent.action.SEARCH -n com.airwatch.contentlocker/com.airwatch.contentlocker.ui.SearchActivity -e "query" "t"

Upon pressing enter on the above command the SearchActivity intent will fire, and by checking your target devices screen, you’ll see that a search was performed for the input “t”, seen in the screenshots below:

Static IV Used in Cryptographic Operations

The last issue that GDS discovered in the AirWatch SCL application is a fairly pervasive bad practice throughout the cryptography world. As stated in the title, a static IV is used in several of the cryptographic operations performed by the application, including the one responsible for decrypting managed files – awFileDecryptUsingKeyIV. The awFileDecryptUsingKeyIV function accepts several arguments, the path to the encrypted file, the path to the output file, the decryption key, and the IV. The IV in this case consists of the numbers 0 through 15, repeated twice, to create a 32-byte long IV, where there is a static or constant IV, the resulting effect on cryptographic operations is that, it can facilitate practical attacks when comparing multiple ciphertexts. What this means is, the more things you encrypt, the easier it is to guess the decryption key.

A code snippet has been provided below. Since the decompiled code was obfuscated, the filenames and variables below will need to be mapped to the original codebase in order to locate the vulnerable code.

 27:   private static final byte[] j = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
..snip..
 95:   public static void a(File paramFile1, File paramFile2, String paramString)
 96:    {
 97:         i.lock();
 98:         try
 99:    {
100:        e.awFileDecryptUsingKeyIV(c.getFileStreamPath("fipsOpenSSlRSA.enc").getAbsolutePath(), paramFile1.getAbsolutePath(), paramFile2.getAbsolutePath(), paramString.getBytes(), j);
101:        return;
102:   }
103:        finally
104:   {
105:        i.unlock();
106:   }
107:        throw localObject;
108:   }

 

As can be seen in the decompiled code snippet above, our encryption IV value is the 0 through 15 repeated twice in a byte array.

Reader Comments

There are no comments for this journal entry. To create a new comment, use the form below.

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
All HTML will be escaped. Hyperlinks will be created for URLs automatically.