CHAPTER 9
Writing Secure Android Applications

You have explored many different ways to find vulnerabilities in applications and exploit them. This chapter looks at ways you can prevent these vulnerabilities in your applications by implementing the right security mechanisms.

Protections against common vulnerabilities such as code injection, logic flaws, insecure storage, application configuration, insecure communication channels, logging, and others will be explored. Some of these mechanisms may be simple configuration changes and others require changes at the code level.

Principle of Least Exposure

The fewer entry points there are into an application, the smaller the attack surface is. To minimize an application's attack surface, the application developer needs to perform the following tasks iteratively:

  1. Consider all entry points into the application. This involves finding every single portion of the application code that is exposed in some way to input from outside sources.
  2. Remove any entry points that can be. An application that has minimal entry points has already reduced its risk exposure.
  3. If an entry point has to be exposed, perform security checks at the entry points before running any other code.

Application Components

An application should reduce its exported application components down to the essentials. The fewer exported components, the better. In the following application only its main activity is exported so that it can be launched. No other components are exposed:

dz> run app.package.attacksurface com.myapp.secure 
Attack Surface: 
  1 activities exported 
  0 broadcast receivers exported 
  0 content providers exported 
  0 services exported 

This exposure level would be considered an ideal case and can be achieved only if the application does not provide any integration opportunities at all to other applications on the device.

Data Storage

If the storage of any application data is not absolutely necessary, simply don't store it. This includes storing data in the application's private data directory or on the SD card.

Interacting with Untrusted Sources

An application that retrieves information from the SD card, the Internet, Wi-Fi, Bluetooth, or any other source that is not directly under the control of the application should be scrutinized for authenticity. Authentication could be in the form of signature checks on the information, some sort of encryption that confirms the identity of the source who sent this information, or some other validation scheme. Be careful of classloading or running executables from untrusted locations. Consider where they have been loaded from and whether they are stored securely. Having a way to cryptographically verify that the code is legitimate before using it is best.

Requesting Minimal Permissions

Request the fewest permissions necessary for your application to function correctly. Performing a task in a way that does not require an extra permission would generally be considered the most secure option. In addition to this, requesting as few permissions as possible helps put more security-minded users at ease. Doing so also reduces the impact of someone exploiting your application. For an example of this theory, refer to Chapter 8 where applications that held the INSTALL_PACKAGES permissions were exploited to devastating effect. This recommendation is also relevant for requesting the use of powerful shared users such as android.uid.system. Shared users should only be used if absolutely necessary.

Bundling Files Inside the APK

Before releasing your app to the world, take the time to unzip the APK and check what is inside because you might find other files unintentionally included inside your APK. You wouldn't want someone to be able to inadvertently obtain a file containing SSH credentials for your testing server that was part of the project during development or other sensitive files.

Essential Security Mechanisms

This section presents a set of essential security mechanisms that you should put in place to ensure that an application is safe for general use.

Reviewing Entry Points into Application Components

You should review each entry point into application code that is accessible over the IPC sandbox to ensure that the maximum possible level of security is provided. The easiest way to review your own code is to trace the functions that handle code from other applications inside each exported component. Table 9.1 details the methods that are relevant for each of the application components.

Table 9.1 Methods per application component that receive data from other applications

COMPONENT METHOD
Activity onCreate()
Broadcast Receiver onReceive()
Content Provider query() insert() update() delete()openFile()
Service onStartCommand()onBind()

When an application component is exported, the functionality that is defined in each method is available to other applications. Ensure that any code paths that exist in these functions are deliberate and cannot lead to unintended consequences.

To maintain a high level of security, your application should make appropriate use of permission protection on all defined application components, including activities, broadcast receivers, services, and content providers that are exported. No components should be available to other applications on the same device that are not protected by a custom-defined permission, unless this component is intended for public use and great care has been taken in its implementation. This also goes for broadcast receivers registered at runtime and broadcasts sent to other trusted applications.

You can enforce permissions by setting the android:permission attribute of a defined component in the manifest. To ensure that all components are protected by the same permission at a top level, set the android:permission attribute in the <application> tag. This applies the stated permission to all application components defined in the manifest.

The most important aspect of securing a custom permission is ensuring that the correct protection level is set on it. The signature protection level ensures that only applications signed with the same certificate are able to request the permission. Setting a protection level of normal or dangerous means that another application can request this permission and the system will grant it. This will allow a malicious application to interact with any components that require this permission to be held by the caller and could inadvertently expose application data or the component to further attack. Here is an example of a custom permission with the signature protection level:

<permission android:name="com.myapp.CUSTOM" 
            android:protectionLevel="signature" /> 

The use of permissions is a general recommendation that goes a long way toward securing an application. The remainder of this section explores additional recommendations that are specific to each of the application components.

Securing Activities

In addition to all standard application component security measures, you should consider the following for activities.

Task Manager Snooping

Two configurations enable you to avoid having the contents of your application's activities from appearing in the recent application list: You can choose to show a blank screen in the Recent list, or remove the entry from the list altogether. To make an activity show as a blank screen, implement the following code inside the onCreate()method of the activity:

getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); 

The FLAG_SECURE parameter ensures that the contents will not appear in screenshots.

To disallow the task from being shown in the Recent Apps list altogether, opt to exclude it by setting the android:excludeFromRecents attribute to true in each activity in the application manifest. You can also perform this action within code when starting a new activity by adding the FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS flag set as follows:

intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 
Tapjacking

To ensure that performing tapjacking attacks on sensitive activities within your application is not possible, you can apply attributes to a View. You can set the following attribute in the layout file of your activity on each item that inherits from a View:

android:filterTouchesWhenObscured="true" 

To prevent touches from being sent through all elements on the activity, apply that attribute to the top-level layout of the activity. You can also accomplish this programmatically by using the setFilterTouchesWhenObscured method as follows:

  view.setFilterTouchesWhenObscured(true); 

This ensures that touches cannot be sent to your activity when another application's View overlays your activity.

Disabling Additions to the Android Dictionary

In normal input boxes on Android, unknown words are automatically added to the user's dictionary. This is useful for everyday applications. However, sensitive applications may contain input boxes where the text that users type should not be entered into the dictionary for a number of reasons, such as transmission of codes, encryption keys, passwords that do not need masking, and so on. If an attacker gains access to a device through a malicious application or by compromising an installed application, he might be in a position to retrieve the contents of the dictionary.

To stop any unwanted words or numbers from being added to the Android dictionary, set the android:inputType="textVisiblePassword" attribute on an EditText box.

Protecting Against Fragment Attacks

On Android versions 4.3 and lower, explicitly protecting against fragment attacks is not possible. The only available protection is to not expose the vulnerable component. This means that no activity that extends PreferenceActivity should be exported to other applications.

Since Android 4.4, protecting against fragment attacks is possible through the use of a new method in the PreferenceActivity class named isValidFragment. You must explicitly override this method to allow the fragment to be loaded within the activity. The following code provides a whitelist of fragments that can be loaded within this activity:

@Override 
protected boolean isValidFragment(String fragmentName) 
{ 
    String[] validFragments = {"com.myapp.pref.frag1", 
                               "com.myapp.pref.frag2"}; 
    return Arrays.asList(validFragments).contains(fragmentName); 
} 
Ensuring Secure Trust Boundaries

If your application contains a login screen or any other form of trust boundary, then take care as to how it is handled. If your login activity contains a way to start activities that were only intended for trusted users, the authentication model of the application may be defeated.

Thus, making sure that no way exists to open an activity that is intended for authenticated users from an unauthenticated area of the application such as a login activity is important. A more involved solution to this may be to implement an application-wide variable for tracking whether a user is authenticated. Authenticated activities should be available only after the user has passed the authentication check, which should be performed when the activity is first started. If the user has not authenticated, the activity should be closed immediately.

Masking Password Displays

Any passwords that a user has to type in should be masked. You do this using an EditText box with the attribute android:inputType="textPassword". This is sufficient to protect user passwords from prying eyes.

If the default way that Android masks passwords is insufficient for your implementation then you can code your own TransformationMethod that handles the way that the password displays. You can set it as follows:

passwordBox.setTransformationMethod(new CustomTransformationMethod()); 
Scrutinizing Browsable Activities

If you make use of activities that have an intent filter that contain the BROWSABLE category then you should be aware that it is possible to interact with this activity from a web browser. As seen in Chapter 8, making an activity BROWSABLE makes it a high value target for an attacker and exploitation of issues inside the activity are generally trivial.

If your activity does not explicitly require being BROWSABLE then it should be removed. However, if you have legitimate reasons for using it then you must consider all possible intents that could cause actions to take place automatically inside your activity. If an attacker is able to send an intent that abuses some logic flaw or functionality inside your application, then you may be opening up the device owner to an unnecessary level of risk.

Securing Content Providers

This section explores code injection and manifest misconfiguration vulnerabilities that are commonly discovered in content providers.

Default Export Behavior

The default export behavior of content providers prior to API version 17 has been covered in Chapter 7; however, this section serves as a reminder. To ensure that a content provider is consistently not exported across all versions of Android explicitly, set it as android:exported=”false” in its manifest declaration as shown in the following example:

<provider 
    android:name=".ContentProvider" 
    android:authorities="com.myapp.ContentProvider" 
    android:exported="false" > 
</provider> 
SQL Injection

Content providers making use of SQLite in their implementation may be prone to SQL injection attacks if user input is directly used inside a SQL statement. This may be because a developer has used the rawQuery() method from SQLiteDatabase by concatenating SQL queries directly with user input.

To protect against SQL injection attacks on Android you can use prepared statements as you would to protect inputs from web applications. The following example shows the use of a rawQuery() with prepared statements. The database variable is of type SQLiteDatabase.

String[] userInput = new String[] {"book", "wiley"}; 
Cursor c = database.rawQuery("SELECT * FROM Products WHERE type=? 
AND brand=?", userInput); 

You can do this in a similar fashion using the query()method where the selection can contain the questions marks and be replaced with content in selectionArgs.

String[] userInput = new String[] {"book", "wiley"}; 
Cursor c = database.query("Products", null, "type=? AND brand=?", 
userInput, null, null, null); 

For actions other than querying, using the SQLiteStatement class to execute a prepared statement is possible, as shown here:

SQLiteStatement statement = database.compileStatement("INSERT INTO 
Products (type, brand) values (?, ?)"); 
statement.bindString(1, "book"); 
statement.bindString(1, "wiley"); 
statement.execute(); 

Making use of prepared statements ensures that user input is properly escaped and does not become part of the SQL query itself.

Directory Traversal

The basis of checking whether another application is attempting a directory traversal attack against a content provider is to test the resulting folder against a known good value. This comes down to checks that a file being requested resides in an “allowed” folder.

You accomplish this by using the getCanonicalPath()method of the File class. This translates a path into one that has the resulting . and .. characters removed and worked into the resultant path. Perform this check and then compare it against a list of allowed files in a certain directory or against the location of the directory itself to prevent against this attack. The following code limits other applications to only reading files within the /files/ directory inside your application's private data directory:

@Override 
public ParcelFileDescriptor openFile (Uri uri, String mode) 
{ 
    try 
    { 
        String baseFolder = getContext().getFilesDir().getPath(); 
        File requestedFile = new File(uri.getPath()); 
 
        //Only allow the retrieval of files from the /files/ 
        //directory in the private data directory 
        if (requestedFile.getCanonicalPath().startsWith(baseFolder)) 
            return ParcelFileDescriptor.open(requestedFile, 
                   ParcelFileDescriptor.MODE_READ_ONLY); 
        else 
            return null; 
    } 
    catch (FileNotFoundException e) 
    { 
        return null; 
    } 
    catch (IOException e) 
    { 
        return null; 
    } 
} 
Pattern Matching

When performing any pattern-matching checks against a requested content URI, always be careful about the implications of using a literal pattern match in the <path-permission> tag in the form of the android:path attribute.

There may be other valid forms of the requested data that are not covered by your logic, so rather use a check that a certain prefix is present, or if possible, create a regular expression for the comparison. Here is an example of using a prefix for the comparison and enforcement of a path-permission:

<provider 
    android:name=".ContentProvider" 
    android:authorities="com.myapp.ContentProvider" 
    android:multiprocess="true" 
    android:exported="true" > 
    <path-permission 
        android:pathPrefix="/Data" 
        android:readPermission="com.myapp.READ_DATA" 
        android:writePermission="com.myapp.WRITE_DATA"/> 
</provider> 

Instead of the android:pathPrefix used in this example, you could use a regular expression as follows:

        android:pathPattern="/Data.*" 

Securing Broadcast Receivers

In addition to all standard application component security measures, the only outlier is the use of secret codes.

Despite their name, these codes can easily be enumerated using a number of tools available on the Play Store. A user or attacker who knows your implemented secret code should not be able to have any control over the application other than that provided when launching the application in the normal way. Secret codes should be used only for convenience or testing purposes. Ideally, if you use them for testing or debugging purposes then remove them before releasing the application into production. Scrutinize the code inside the broadcast receiver to ensure that an unintended action cannot be performed by simply invoking the secret code. On some devices and older versions of Android, invoking these codes from the browser by visiting a crafted website is possible. This means that performing an action automatically upon receipt of the broadcast from the dialer is especially dangerous.

Storing Files Securely

The storage of any information on the device by an application, must be done in a secure manner. The Android sandbox for application data is not enough to create a truly secure application. We've shown multiple times how to defeat this sandbox through misconfiguration and exploitation of the system. Therefore, the assumption that an attacker cannot reach files sitting in a private data directory is somewhat naive.

Creating Files and Folders Securely

When creating a file, explicitly stating the file permissions is better than relying on the umask set by the system. The following is an example of explicitly stating the permissions so that only the application that created it can access and modify the file:

FileOutputStream secretFile = openFileOutput("secret", 
                              Context.MODE_PRIVATE); 

Similarly, you can create a folder within the application's private data directory that is set with secure permissions as follows:

File newdir = getDir("newdir", Context.MODE_PRIVATE); 

Some examples on the Internet show similar code examples, but without the use of the static final integers that represent the permissions. Such an example that actually makes a newly created file world readable is shown here:

FileOutputStream secretFile = openFileOutput("secret", 1); 

Using direct integers that represent the permissions is not advised because it is not clear when reviewing code at a glance what the outcome will be.

When using native code to create a file, you can also explicitly specify permissions. This example shows how to do so in the open function:

FILE * secretFile = open("/data/data/com.myapp/files/secret", 
                         O_CREAT|O_RDWR, S_IRUSR|S_IWUSR); 

This creates the file with permissions that only allow the application owner to read and write to it.

Using Encryption

Previous chapters discussed attacks that can be used to expose the contents of a private data directory. Such attacks highlight the importance of going that extra step and encrypting any sensitive files that reside on disk. When storing sensitive files on the SD card, you absolutely must encrypt it. This applies to data being read from the SD card as well because the ability to manipulate input files could be an entry point into the application for an attacker. You should view the SD card as a public area on the device and take care when using it for storage.

The field of encryption is a heavily technical one that is only lightly explored in the next section. An important point is that creating your own encryption schemes is not an acceptable solution. Widely accepted encryption schemes are mathematically proven and have spent many years in peer review by professional cryptographers. Do not discount the kind of time and effort put into these endeavors; the outcome assures you that widely known encryption algorithms will always provide you with better security than custom ones. The following are a set of safe decisions that are in line with the recommendations from professional cryptographers:

  • Use at minimum 256-bit AES for symmetric key encryption. Avoid using ECB (Electronic Code Book) mode because it will allow an attacker to discover patterns in data between different encrypted blocks.
  • Use 2048-bit RSA for asymmetric encryption.
  • Use SHA-256 or SHA-512 as a hashing algorithm.
  • If it is possible to salt passwords, then do so with a randomly generated string. This method is especially useful when you need to hash a password of some sort. The salt is not a secret and can be stored alongside the encrypted information. Salting prevents the use of pre-computed rainbow tables to recover passwords and is not a secret in itself.

Using Random Numbers, Key Generation, and Key Storage

If at any point in your application you need to generate a random number or obtain a key that is used for cryptographic purposes, then you must watch out for a number of things. The most important of these are as follows:

  • Never seed a pseudo-random number generator (PRNG) using the current date and time. This is a deterministic seed value that is not suitable for key generation. Versions of Android prior to 4.2 would generate the same identical sequence of numbers from SecureRandom when given the same seed, because the seed was not mixed in with the internal source of entropy but rather replaced. This means that on these versions, any generated random numbers could be guessed if the attacker iteratively brute-forced a set of probable seed values.
  • Never seed a PRNG with a constant number. If this seed is recovered from the decompiled code then an attacker could also use it to recover the sequence of numbers generated by the PRNG.
  • Never use device values like an International Mobile Equipment Identity (IMEI) number or Android ID as the encryption key or as input to one. An attacker can easily retrieve these values, especially if he has gained arbitrary code execution on the device.
  • When making use of key derivation functions, never use constant salt values and always use iterations of 10,000 or more. This will make the use of a rainbow tables infeasible and the brute-forcing of passwords expensive.

Now that you have read about some of the things that you should not do, it's time to look at possible solutions. To generate a random number, you use SecureRandom, but you must take care in the way that it is seeded. Seeding with a non-deterministic seed is important and you should use many inputs to create it to guarantee randomness. The Android Developers Blog has excellent code for generating seed values (http://android-developers.blogspot .co.uk/2013/08/some-securerandom-thoughts.html). The technique used mixes: the current time, PID, UID, build fingerprint, and hardware serial number into the Linux PRNG at /dev/urandom.

To generate a 256-bit AES key that is seeded only from default system entropy, you can use the following code:

SecureRandom sr = new SecureRandom(); 
KeyGenerator generator = KeyGenerator.getInstance("AES"); 
generator.init(256, sr); 
SecretKey key = generator.generateKey(); 

If you use this code, then the burning question is where should you store the key? This question is one of the biggest problems faced by developers wanting to encrypt application files. It is a tricky question with many differing opinions about the correct solution. The answer should depend on the type and sensitivity of the application but some possible solutions are discussed here.

A solution that is not acceptable is hard-coding the password in the source code. You have seen how easily an attacker can decompile an application and obtain such keys, which makes the measure completely ineffective.

For high-security applications the answer is simple: The user should hold the key. If the application requires some form of password to access it then the entered password should be used to derive the encryption key via a key derivation function such as PBKDF2. This ensures that the encryption key can be derived only from the correct user password. If an attacker obtains an encrypted file, then he can attempt to brute-force the password and run it through the key derivation function to decrypt the file. However, this attack is largely infeasible when strong passwords are used. A functional implementation of using a user password or pin to generate the encryption key is provided by Google at http://android-developers.blogspot.com/2013/02/using-cryptography-to-store-credentials.html and is shown here:

public static SecretKey generateKey(char[] passphraseOrPin, byte[] salt) 
throws NoSuchAlgorithmException, InvalidKeySpecException { 
    // Number of PBKDF2 hardening rounds to use. Larger values increase 
    // computation time. Select a value that causes 
    // computation to take >100ms. 
    final int iterations = 1000; 
 
    // Generate a 256-bit key 
    final int outputKeyLength = 256; 
 
    SecretKeyFactory secretKeyFactory = 
        SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); 
    KeySpec keySpec = new PBEKeySpec(passphraseOrPin, salt, iterations, 
                                     outputKeyLength); 
    SecretKey secretKey = secretKeyFactory.generateSecret(keySpec); 
    return secretKey; 
} 

The salt in the previous implementation can be any randomly generated value that is stored alongside the encrypted data in the application's private data directory.

For applications where user-derived encryption keys are not possible, you must take a best effort approach. If the encryption key is not making use of something from the user then it must be stored somewhere on the device or retrieved from the linked application web service. Storing the encryption key in the same folder as the encrypted file would probably be of little use because if an attacker is able to retrieve the encrypted file, he might also be able to read other files in the same directory. A location that provides more security is the AccountManager feature in Android. The AccountManager allows an application to store a password that can only be accessed again by the application that added it. A check is performed when calling the getPassword() method that the caller has the AUTHENTICATE_ACCOUNTS permission and that the UID of the caller is same as the one that added the account. This measure is decent for protecting the password from malicious applications but will not protect this password from attackers with privileged access such as root. It is not strictly supposed to be used for this purpose but versions of Android prior to 4.3 did not have a suitable solution for storing symmetric keys securely.

If your application targets Android API level 18 and later then making use of the Android Keystore System may be a better measure. This specific type of KeyStore (see http://developer.android.com/reference/java/security/KeyStore.html) is only available to your application UID. Only asymmetric keys can be added, which means that the stored key would have to be used to encrypt a symmetric key that resided somewhere else on the device.

Exposing Files Securely to Other Applications

Consider the scenario where your application generates PDF documents that the user must view in another application. You do not want to put these documents on the SD card because that is considered a public storage area and these documents might contain sensitive information. You also do not want to mark the document as world readable and place it in your application's private data directory so that the document reader can reach it because then effectively any application can reach it, too.

In this case using a content provider as an access-control mechanism for the document may be wise. Android has this scenario covered by making use of a feature called the granting of URI permissions. Consider the following content provider declaration in a manifest:

<provider 
    android:name=".DocProvider" 
    android:authorities="com.myapp.docs" 
    android:exported="true" 
    android:permission="com.myapp.docs.READWRITE" 
    android:grantUriPermissions="false"> 
        <grant-uri-permission android:pathPrefix="/document/" /> 
</provider> 

An application that wanted to read or write to this content provider directly would have to hold the com.myapp.docs.READWRITE permission. However, the line that sets grantUriPermissions to false and the <grant-uri-permission> tag specifies the paths to which other applications can be granted temporary access. This combination means that only a content URI prefixed with /document/ can be made available using the grant URI permission functionality. This protects the rest of the content provider from being accessed by any external application without holding the specified permission.

The following example of this application uses the grant URI permission functionality to open a generated PDF in an external PDF reader:

Uri uri = Uri.parse("content://com.myapp.docs/document/1"); 
 
Intent intent = new Intent(Intent.ACTION_VIEW); 
intent.setDataAndType(uri, "application/pdf"); 
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 
startActivity(intent); 

Notice that the only difference between this code and normal opening of an exposed content URI is the FLAG_GRANT_READ_URI_PERMISSION flag added to the intent.

The previous code is certainly the easiest method of performing this action but is not the most secure. What if a malicious application on the device registered an intent filter that specified it is able to handle PDF documents? The document might end up being accessible to the malicious application because the intent created was an implicit one! A more secure method is to explicitly grant the URI permission to the application that will be retrieving the document. You can do this by providing a configuration activity or a pop-up containing the list of applications that are suitable to open PDFs prior to launching the intent that actually opens the PDF reader. A list of all applications that can handle PDF documents can be retrieved using the queryIntentActivities method of the PackageManager class. After the user has selected a PDF reader then the name of the package can be provided to the grantUriPermission method as follows:

grantUriPermission("com.thirdparty.pdfviewer", uri, 
Intent.FLAG_GRANT_READ_URI_PERMISSION); 

After performing this code, an explicit intent can be created to open the PDF in the chosen reader. After the application is sure that the user does not require access to the PDF any more, the URI permission can be revoked using the following code:

revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); 

This method maintains the security of the content provider by enforcing a permission and allows the exposure of select files to third-party applications in a flexible way.

Creating Secure Communications

The power of many mobile applications comes from being able to interface with services on the Internet. Unfortunately, this also means that the user's data that is being communicated may be susceptible to compromise when traversing hostile networks. This section explores some ways to ensure that information is transported securely to and from Internet services. It also provides a brief caution against implementing custom IPC mechanisms.

Internet Communications

An application should never use cleartext communications with Internet services because it is a risk for traffic interception attacks. An attacker anywhere along the path between the user's device and the Internet server would be able to intercept and modify content in both directions or simply sniff this traffic to divulge its contents. This is especially not acceptable if an application uses Internet services that require user credentials to be submitted by the application. An attacker may not gain direct value from accessing the service being logged into; however, attackers also know that humans are creatures of habit. Users may make use of the same password on an arbitrary Internet service as they do for their email account or other sensitive services.

In addition to the risk of exposing user data, cleartext channels present a multitude of dangers to the application itself. Chapter 8 covered this topic discussing various ways to exploit a device by manipulating HTTP traffic. Therefore, we recommend that you avoid cleartext channels at all costs.

Android comprises of APIs that you can use to create very secure communication channels. Differing opinions exist in the security world about what constitutes a “secure connection.” However, the general consensus is that the use of SSL with some form of additional protection is acceptable for most use cases. The problem with general-purpose SSL is that it relies on the security of a large number of trusted certificate authorities (CAs) for validation. The compromise of a single trusted CA affects the security of all clients that trust this CA. Compromising the signing certificate of a widely trusted CA means that fraudulent certificates can be issued for your website or other SSL endpoints. An attacker who uses a fraudulent certificate in a traffic interception attack would be able to capture traffic without the user receiving any warnings because the approach of attributing trust through the use of trusted CAs is doing exactly what it says on the tin. Compromising a trusted CA certificate is a known weak point.

The compromise of a CA signing certificate may sound like an unlikely event, but in recent years it has occurred a number of times. To protect against this type of compromise, having applications implement SSL certificate pinning is recommended. This is when certain attributes of the certificate presented by the server are validated against stored values and the connection is allowed only if these values check out. In fact, some well-known cryptographers such as Moxie Marlinspike have recommended not using CAs at all when implementing mobile applications. He discussed this in his blog post at http://thoughtcrime.org/blog/authenticity-is-broken-in-ssl-but-your-app-ha/.

Implementing SSL certificate pinning can be tricky if you are not knowledgeable on the specifics of X.509 certificates and their structure. One way of creating your own SSL certificate pinning implementation is creating a new class that extends X509TrustManager and implementing the certificate checks in the checkServerTrusted method. The technique used by Moxie inside this method was to compare the hash of the SPKI (SubjectPublicKeyInfo) of the certificate against a stored value. Using this technique means that only the issuer's key information will be checked, and so you are basically providing assurance that the certificate is signed by the correct CA. This check is relatively lightweight and does not come with the hassles of pushing application updates every time your website's certificate expires. Moxie has also written an Android Library that provides an easy way for developers to add SSL certificate pinning to their connections. The documentation in his project provides an example that shows how to retrieve data from https://www.google.com using a pinned connection:

String[] pins = new String[] {"f30012bbc18c231ac1a44b788e410ce754182513"}; 
URL url = new URL("https://www.google.com"); 
HttpsURLConnection connection = 
PinningHelper.getPinnedHttpsURLConnection(context, pins, url); 

You can find further examples and the source code that implements the checks at https://github.com/moxie0/AndroidPinning. If you decide not to make use of SSL certificate pinning then at least mandate the use of SSL. Before releasing an application, perform thorough checks on the sections of code handling the SSL connection to ensure that no certificate-bypassing hacks have been left in use. Validation of the certificate should be done by the system or carefully implemented by someone who fully understands SSL using a custom HostnameVerifier and TrustManager.

Some applications may require exceptionally secure communication channels that do not rely solely on the security of SSL. In this case, you could add an additional encryption layer that makes use of a symmetric key that is generated upon first use of the application. This decreases the likelihood that if an attacker is able to break the SSL layer of the encryption, that he will be able to gain access to the actual contents of the communication. This is because he would first need to gain access to the device to extract the key.

Local Communications

Android has a rich set of APIs for communication between applications. This diminishes the need to come up with a unique way of transferring data from one application to another using network sockets, the clipboard, or some other arbitrary mechanism. In fact, doing so decreases the security of the application because implementing the same level of security the built-in APIs have is hard. If an arbitrary IPC mechanism must be implemented for some reason then it should always include checks for verifying which application is connecting to it. You need to think through all the ways that a malicious application could spoof a legitimate application's identity.

Securing WebViews

WebViews have a lot of functionality under the hood that an attacker can use to his advantage. Therefore, limiting the attack surface as much as possible if you use WebViews in your application is important. If you are only using a WebView to load a simple informational website then rather open the site in the Android browser by sending an intent containing the link. This method is more secure than having an embedded WebView because the Android browser loads content within the context of its own sandbox. If the browser were to get compromised by this content, it would have no implications for the data being held by your application. However, sometimes legitimate use cases exist for implementing an embedded WebView.

The single biggest mistake made when implementing a WebView is loading cleartext HTTP content inside it because of the numerous attack methods that are available to an attacker who is able to load his own content inside the WebView. For this reason, only HTTPS links should be loaded inside a WebView, and code paths that allow another application on the same device to load arbitrary content in the WebView should be removed.

The following sections list recommendations for what you can do to limit what attackers can do if they are able to load their own content inside the WebView. David Hartley of MWR InfoSecurity documents these considerations at https://labs .mwrinfosecurity.com/blog/2012/04/23/adventures-with-android-webviews/.

JavaScript

If support for JavaScript is not required in the WebView, then you should disable it because it is usually the launching point for further attacks against the WebView. Being able to load dynamic code like JavaScript inside the WebView gives the attacker the platform needed to exfiltrate data, redirect the page, create attack payloads, and perform any other arbitrary action required for exploitation. You can disable JavaScript by implementing the following code:

webview.getSettings().setJavaScriptEnabled(false); 

JavaScriptInterface

The effects of exploiting a vulnerable WebView with an implemented JavaScriptInterface was shown in Chapter 8. You can completely avoid this by simply not using a JavaScriptInterface if the functionality can be provided in another way. If no other option exists, set the following attributes in the application manifest to ensure that gaining arbitrary code execution using the JavaScriptInterface and CVE-2012-6636 is not possible:

<uses-sdk android:minSdkVersion="17" 
          android:targetSdkVersion="17"/> 

You can then annotate methods exposed over the bridge with @JavascriptInterface. Note that this limits the versions of Android that can run this application.

Plug-Ins

WebView plug-ins can provide third-party application vendors the ability to provide additional functionality. For example, Flash from Adobe is a plug-in that can be used inside a WebView. The plug-ins functionality has been deprecated from API version 18 (Jelly Bean 4.3) and higher but you should explicitly disable it in case older versions of Android are being used by your userbase. You do that using the following code:

webview.getSettings().setPluginState(PluginState.OFF); 

Setting this value helps protect against the exploitation of vulnerable WebView plug-ins and the “Fake ID” vulnerability that was briefly discussed in Chapter 8.

Access to Information

WebViews by default are allowed to load files from the filesystem. This poses a problem when a vulnerability exists that allows a malicious application to open local files inside another application's WebView. This opens the exposed WebView to all the available exploitation techniques. You can disable filesystem access from a WebView as follows:

webview.getSettings().setAllowFileAccess(false); 

This will not stop the WebView from being able to load from its own application's resources or assets folder using file:///android_res and file:///android_asset. To lock down the WebView even further, you should not allow loaded pages from the filesystem to access other files on the filesystem. This will stop these loaded pages from exfiltrating other files out to the Internet. The following setting helps protect against this:

webview.getSettings().setAllowFileAccessFromFileURLs(false); 

Furthermore, you can protect a WebView from being able to access content providers on the device by using the following setting:

webview.getSettings().setAllowContentAccess(false); 

Web Content Validation

If a WebView is connecting to a pre-defined set of pages that are known to the developer before the release of the application, then performing additional checks to ensure that no other page is attempting to load inside the WebView is best. You can do so by overriding the WebViewClient's shouldInterceptRequest method as follows:

@Override 
public WebResourceResponse shouldInterceptRequest (final WebView view, 
String url) 
{ 
    Uri uri = Uri.parse(url); 
    if (!uri.getHost.equals("www.mysite.com") && 
        !uri.getScheme.equals("https")) 
    { 
        return new WebResourceResponse("text/html", "UTF-8", 
        new StringBufferInputStream("alert('Not happening')") 
    } 
    else 
    { 
        return super.shouldInterceptRequest(view, url); 
    } 
} 

The previous example will load pages from www.mysite.com only when they are being loaded over HTTPS.

Configuring the Android Manifest

The exploitation of some issues on Android do not arise from insecure code, but rather a lack of understanding of each configuration available in the Android manifest. This section contains some configurations to be aware of in the manifest file.

Application Backups

To ensure that an attacker with physical access to a device is not able to download the contents of an application's private data directory using "adb backup," you can implement a single fix. In the application's AndroidManifest.xml file, set the android:allowBackup attribute to false. By default, this attribute is set to true and backups are allowed.

Setting the Debuggable Flag

To ensure that your application cannot be exploited by an attacker with physical access to the device, or on older devices by another application, the application should not be searching for a debugger. The android:debuggable attribute in the AndroidManifest.xml should explicitly be set to false prior to building the release version of the application. Having the application built automatically with the debuggable flag set to false is possible in common Android IDEs, and if you are comfortable with your configuration then by all means make use of it. However, explicitly setting this flag in conjunction with having manual pre-release checks performed on the APK will always ensure that the application does not go into production with this flag set.

API Version Targeting

Developers have the ability to create Android applications that are largely backward compatible and have a single code base that works on a range of old and new devices. However, Google trusts that the developer is informed about what features and modifications have been made in each API version to make sure that an application remains backward compatible.

Two important attributes regarding API version targeting in an application's manifest are minSdkVersion and targetSdkVersion in the <uses-sdk> tag. minSdkVersion states the minimum API level that the application can work on. targetSdkVersion states the API version that ensures the set of features that the application is intended to run on is available. Having differing versions between minSdkVersion and targetSdkVersion means that your code should be detecting what platform features are not available on older devices and providing alternatives.

These values also have implications for security. When security fixes that change certain features in existing components are performed, they are activated only if you are targeting an API version equal to or greater than the version where the security fix was implemented. For example, content providers on older versions of Android were exported by default. However, if you set your minSdkVersion or targetSdkVersion to 17 or greater, the content provider is no longer exported by default.

The latest versions of Android have security fixes included but sometimes they need to keep these fixes unimplemented for older API versions so that backward compatibility is maintained. Therefore, targeting the largest targetSdkVersion value possible is important so that users of new devices get the benefits of security fixes made to the platform. This may require extra effort in keeping up with changes, but it benefits the security of your application. A great example of where this is important is when using a WebView with a JavaScriptInterface. If your version is targeting an API level smaller than 17, your application will still be vulnerable to code execution regardless of which Android version the application is running on.

Correctly targeting API versions also applies for native code that is bundled with your application. The targeted API versions can be set in the Android.mk file as follows:

APP_PLATFORM := android-16 

The bigger the value, the more security features are enabled but the fewer devices are supported. A defining point for security in the Android NDK took place at API 16 where PIE (Position Independent Executable) was enabled in order to ensure full ASLR on devices. However, PIE binaries were not enforced until Android 5.0 Lollipop and targeting API versions smaller than 16 will cause binaries not to run on this version and upward. The only solution is to provide two versions of the same binary bundled with your application and use the correct one for the version of Android your application is running on.

Logging

Logging is essential during development, but can inadvertently expose information if it's left on in release builds. Keeping track of whether these logging functions are commented out when going into production is difficult for a developer. Instead of waiting until production release time to check and disable logging functions, you can use a centralized logging class. This class should contain a flag that can be turned on and off depending on whether you want logging enabled during development or have it all turned off for production releases. You can even link this logging function to a check for BuildConfig .DEBUG, but this approach may also be prone to errors, and using your own defined constant is safer. Defining a central logging function can apply to native code as well and the on/off flag can be implemented by using define. Using a custom logging class eliminates all potential failure points in terms of logging sensitive information.

Additionally, by making use of a tool like ProGuard (see http://developer .android.com/tools/help/proguard.html), you can also remove the logging functions from code. The following solution was provided by David Caunt on StackOverflow to remove logging; you specify the following inside proguard .cfg:

-assumenosideeffects class android.util.Log { 
    public static *** d(...); 
    public static *** v(...); 
    public static *** i(...); 
} 

Reducing the Risk of Native Code

Native code is notoriously hard to secure but sometimes is required within an application. You can reduce the risk of using native code by limiting its exposure to the outside world. Scrutinize any entry points into native code and treat them as high risk factors of the application. Any native code that can be replaced with its Java equivalent without affecting the goals of the application should be replaced. If you are using any third-party libraries, these should also be kept up to date to ensure that the latest security fixes are included.

Another way of contributing to the mitigating factors of using native code is by making sure that all exploit mitigations are enabled when compiling the code. This was made quite simple by the Android NDK and the secret is to always use the latest version of the NDK and target the highest possible API version. The NDK enables as many exploit mitigations as possible by default. In fact, you need to explicitly turn them off if you do not want them enabled for some reason. These exploit mitigations should not be an excuse for coding insecurely, though, and you should make every effort to check the code for possible bugs. A minimum effort of making sure that some common native coding mistakes are not present is a prerequisite.

Tobias Klein created an excellent script named checksec to show which exploit mitigations are enabled on a library or executable. You can download it from his site at http://www.trapkit.de/tools/checksec.html. You can use this script to verify that all expected exploit mitigations have been enabled on your native components. Here is an example of running this against a demo shared library created using the NDK:

$ ./checksec.sh --file libtest.so 
RELRO       STACK CANARY  NX         PIE  RPATH     RUNPATH     FILE 
Full RELRO  Canary found  NX enabled DSO  No RPATH  No RUNPATH  libtest.so 

The previous output shows that all important exploit mitigations have been enabled on this library. Performing the same test of an example busybox binary downloaded from an unofficial source on the Internet reveals the following:

$ ./checksec.sh --file busybox 
RELRO     STACK CANARY    NX          PIE     RPATH     RUNPATH     FILE 
No RELRO  No canary found NX enabled  No PIE  No RPATH  No RUNPATH  busybox 

The exploit mitigations have not been enabled for this binary, which will make exploitation of any bugs inside it easier. This script is very useful for doing a quick verification that suitable exploit mitigations are enabled before going live with your application. The output is self-explanatory if you are familiar with the available exploit mitigations offered on Android. However, even as a beginner the output of checksec makes spotting disabled mitigations easy because it highlights them in red.

Advanced Security Mechanisms

This section explores security mechanisms that are generally not implemented in everyday applications. These are reserved for developers looking to go above and beyond the call of duty to secure their applications.

Protection Level Downgrade Detection

Chapter 7 explored how it was possible to downgrade application protection levels by installing a malicious application that defined a permission first with an insecure protection level. Therefore, having applications that hold sensitive data perform an additional check to ensure that the security of the custom permissions defined have not been downgraded to a less secure protection level is important. You do this by running a check at each entry point protected by a custom permission that ensures that all the custom permissions defined still have the correct protection levels set. The following code shows a functional implementation of this check:

public void definedPermissionsSecurityOk(Context con) 
{ 
    PackageManager pm = con.getPackageManager(); 
    try 
    { 
        PackageInfo myPackageInfo = pm.getPackageInfo(con.getPackageName(), 
                                    PackageManager.GET_PERMISSIONS); 
        PermissionInfo[] definedPermissions = myPackageInfo.permissions; 
        for (int i = 0; i < definedPermissions.length; i++) 
        { 
            int protLevelReportedBySystem = pm.getPermissionInfo( 
                                            definedPermissions[i].name, 
                                            0).protectionLevel; 
 
            if (definedPermissions[i].protectionLevel != 
                protLevelReportedBySystem) 
            { 
                throw new SecurityException("protectionLevel mismatch for " 
                                            + definedPermissions[i].name); 
            } 
        } 
    } 
    catch (NameNotFoundException e) 
    { 
        e.printStackTrace(); 
    } 
} 

This code snippet checks all the custom permissions defined by the application and compares the protection level specified in the manifest to the one that the system reports. If a discrepancy exists between these values, the function throws a SecurityException, meaning that one of the permissions has been altered and may no longer provide protection for exported components.

Using this function will stop downgrade attacks from taking place and could be used to alert the user and developer of the situation.

Protecting Non-Exported Components

If you recall from Chapter 7, privileged users such as root are able to invoke and interact with application components even when they are not exported. If you as an application developer decide that this is not acceptable for your application then ways exist to protect against it. Note that regardless of any permissions (even with signature protection levels) set on an application component, stopping root from being able to invoke it is not possible.

One way to prevent the invocation of components that are not meant to be accessible to any user except the local application is by implementing a request token system. When the application is started, a random token can be generated and stored in a static variable inside the code. Then when the application itself issues an intent to other non-exported components, this token must be provided as an extra. When the component is started by any application including itself, the provided token should be checked by the application against the stored value and if it does not match, the component should immediately exit and not process any other data further. This check should be done before any other actions are performed. This technique is very useful for activities but is not restricted to only being used by them. You can apply the concept in a similar way to other application components that are not exported.

Slowing Down a Reverse Engineer

Application developers who want to do so can put the following checks and measures in place, but these items are not a replacement for good application security practices. Defeating these checks will always be possible by patching them out of the application either statically or at runtime by a privileged user context. Therefore, performing such checks may be a requirement but will only serve to slow down a skilled reverse engineer from being able to properly analyze an application's behavior.

Obfuscation

As discussed in previous chapters, compiled Android applications can easily be decompiled into readable source code that resembles the original. To make a reverse engineer's life a tad more difficult, developers can use obfuscators to make the decompiled code less readable and harder to follow. Depending on how rigorous the obfuscation technique performed is, it could add significant time expenses for a reverse engineer. This fact may deter the casual reverse engineer but will not stop someone who is determined to understand the code.

You should view this countermeasure as an in-depth defense measure that makes researching and planning attacks more difficult, rather than as a replacement for ensuring that any source code is as secure as possible. Obfuscating source code does not prevent any inherent vulnerability from being exploited.

Various code obfuscators exist, ranging from free tools such as ProGuard (see http://developer.android.com/tools/help/proguard.html) to many paid options. The paid version of ProGuard is called DexGuard (see https://www .saikoa.com/dexguard) and provides excellent features that can make reverse-engineering applications tough.

Other products that provide obfuscation are as follows:

Jon Sawyer at Defcon 22 made an excellent comparison of some of these obfuscators and their features at https://www.defcon.org/images/defcon-22/dc-22-presentations/Strazzere-Sawyer/DEFCON-22-Strazzere-and-Sawyer-Android-Hacker-Protection-Level-UPDATED.pdf. Some commonly found features in these products are:

  • String encryption
  • Class encryption
  • Native library encryption
  • Asset encryption
  • Reflection to hide sensitive calls to APIs
  • Tamper detection
  • Removal of logging code
  • Class and variable renaming
  • Control flow mangling
  • Watermarking

Many of these products support native code obfuscation as well. However, the University of Applied Sciences and Arts Western Switzerland of Yverdon-les-Bains started an interesting open-source project called O-LLVM, and it is a fork of the LLVM (Low Level Virtual Machine) project that provides obfuscation and tamper proofing for many languages and platforms. You can make use of it with the Android NDK, and it produces compiled code that is very difficult to reverse engineer. The project page is available at https://github.com/obfuscator-llvm/obfuscator/wiki and is worth investigating if you require rigorous obfuscation of native code.

Root Detection

Some applications may have legitimate reasons for needing to know whether the device they are running on is rooted. In practice, often very shallow checks are performed to determine this status. This section presents some more in-depth methods to check whether the user of the device or other applications are able to obtain root access. The most commonly implemented technique is to check for the existence of the su binary on the path. This is commonly done by executing which su and parsing the output, which provides the full path to su if it is available on the device. The which tool is not a standard binary that is provided on Android and you should not rely on its being present. Instead you should create a function that operates in the same manner as which. This would involve decomposing the PATH environmental variable into its separate directories and searching them for the provided binary.

Although searching for the su binary certainly is valid, it is not sufficient on its own to determine whether the owner of the device can obtain root. You could also perform the following additional checks:

  • Read the default.prop file located on the root of the Android filesystem. An attribute in this file called ro.secure indicates what privileges are associated with an ADB shell when the connection is made from a computer. If this value equals 0, then ADB starts with root privileges and this is an indication that the user can obtain a root shell when connecting to the device using adb shell.
  • Check whether the adbd program has been started by the root user. You can see this by invoking the standard ps binary and parsing the output.
  • Check for common emulator build properties through the use of the android.os.Build class. The following system properties can be checked against the provided regular expression to see whether the application is running inside an emulator:

         Build.TAGS = "test-keys" 
         Build.HARDWARE = "goldfish" 
         Build.PRODUCT = "generic" or "sdk" 
         Build.FINGERPRINT = "generic.*test-keys" 
         Build.display = ".*test-keys" 

    The existence of one or more of these values would indicate that the application is running on an emulator.

  • Iterate through the labels of installed applications using the PackageManager class and look whether they contain the words 'SuperSU', 'Superuser', and other common applications used to control root access. This is a much better way to check for the existence of an application on a device rather than checking for the existence of its APK file in a certain directory. The APK may be renamed by developers of the application or be installed in a different place to the commonly checked /system/app/ directory. The installed package names of these applications could also be searched; for example, 'com.noshufou.android.su'and 'eu.chainfire.supersu'. This check is the least reliable because the user could have just installed a root manager application from the Play Store without actually having root access. However, if the user managed to install the root manager's APK somewhere inside the /system folder, then this indicates that he had privileged access to the device at some point.

Debugger Detection

A reverse engineer who needs to manipulate code inside your application can do so by using a debugger attached to the device. However, this technique can only be used if your application is marked as debuggable. A reverse engineer may have modified the application's manifest to include android:debuggable=”true” or used a runtime manipulation tool that makes the process debuggable in order to achieve this.

You can perform a check to make sure that the application is not set as debuggable by implementing the following code:

boolean debuggable = (getApplicationInfo().flags & 
ApplicationInfo.FLAG_DEBUGGABLE) != 0; 

Another measure that you could implement is to periodically check whether an application has a debugger attached to it by using the isDebuggerConnected() method provided in the android.os.Debug class.

These approaches do not provide an infallible way of preventing application debugging but will certainly slow down a reverse engineer who has not taken the time to defeat these checks.

Tamper Detection

An application can be designed to fail to run if it detects signs of modification of its APK file. This technique is commonly known as tamper detection. The following code snippet shows how an application can check whether its APK has been changed and resigned. Specifically, it checks the signature of the signing certificate used against a known good value.

public boolean applicationTampered(Context con) 
{ 
    PackageManager pm = con.getPackageManager(); 
    try 
    { 
        PackageInfo myPackageInfo = pm.getPackageInfo(con.getPackageName(), 
                                    PackageManager.GET_SIGNATURES); 
        String mySig = myPackageInfo.signatures[0].toCharsString(); 
 
        //Compare against known value 
        return !mySig.equals("3082..."); 
    } 
    catch (NameNotFoundException e) 
    { 
        e.printStackTrace(); 
    } 
    return false; 
} 

A reverse engineer could certainly patch these checks or defeat them in some other way; however, it is an annoyance. Upon failing the tamper detection check, the app could also transmit information about the device to the application developer so that he is aware that someone is attempting to modify the application, possibly in an attempt to crack it and make it available on the Internet. Paid products that provide code obfuscation also often provide tamper detection. If paying for tamper detection code is a better option, refer to the “Obfuscation” section earlier in this chapter for some options.

Summary

When creating an Android application, you must consider many security aspects. However, the security functionality provided by the Android platform is rich and strong security mechanisms can be created using built-in features. The following is a list of security checks provided in this chapter that you can use as input to a security assessment of your application. The items on this checklist are most of the time not fully attainable but should be seen as an ideal to strive toward.