6. Platform Interaction Requirements

6.1. MSTG-PLATFORM-1

The app only requests the minimum set of permissions necessary.

6.1.1. Testing 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:

Reference

6.1.2. Changes to permissions per API level

6.1.2.1. 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.

Reference

Rulebook

6.1.2.2. 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.

Reference

Rulebook

6.1.2.3. 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).

Reference

Rulebook

6.1.3. Integration with other app components

6.1.3.1. 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.

Reference

6.1.3.2. 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.

Reference

6.1.3.3. 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.

Reference

Rulebook

6.1.3.4. 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.

Reference

6.1.4. 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.

Documentation for URI Permissions

Reference

Rulebook

6.1.5. 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.

Reference

Rulebook

6.1.6. Static Analysis

6.1.6.1. 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

Reference

Rulebook

6.1.6.2. 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");
        }

Reference

Rulebook

6.1.7. 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.

Reference

Rulebook

6.1.8. 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.

Reference

Rulebook

6.1.9. 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.

Reference

Rulebook

6.1.10. 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 AC*TION_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.

Reference

Rulebook

6.1.11. Rulebook

  1. Explicitly request all permissions when needed (Required)

  2. Note that authorization may be required on both the receiving and sending sides of Broadcast (Required)

  3. Applications should only have permissions to specific URIs (Required)

  4. Custom permissions are created according to the “Principle of Least Privilege” (Required)

  5. Register custom permissions with reverse-domain annotation (Recommended)

  6. Check if permissions are really necessary for the app and remove unnecessary permissions (Required)

  7. Programmatic checking of custom permissions (Recommended)

6.1.11.1. Explicitly request all permissions when needed (Required)

Google warns that certain permissions may be moved from one group to another in future versions of the Android SDK. They warn that the app logic should not rely on the structure of these permission groups, as they may move from one group to another. Therefore, the best practice is to explicitly request all permissions when needed.

Below is a sample code for explicitly acquiring write permission to external storage.

Declare permission to write to external storage in AndroidManifest.xml.

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

Sample code to request permissions when needed.

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.");
}

Sample code for permission request response.

@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.
    }
}

If this is violated, the following may occur.

  • App logic needs to be reviewed if certain permissions are moved from one group to another in future versions of the Android SDK.

6.1.11.2. Note that authorization may be required on both the receiving and sending sides of Broadcast (Required)

Permissions can be set to restrict Broadcast to a set of apps with certain permissions. Restrictions can be applied to the sender or receiver of the Broadcast. Note that this requires privileges on both the receiving and sending sides.

Transmission with set permissions

When calling sendBroadcast(Intent, String) or sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle), you can specify permission parameters. Only receivers that have requested permission using tags in the manifest (and receivers that have been granted permission in conjunction for security reasons) may receive Broadcasts.

The sample code below is an example of sending Broadcast with the permission parameter specified.

sendBroadcast

sendBroadcast(Intent("com.example.NOTIFY"), Manifest.permission.SEND_SMS)

sendOrderedBroadcast

To receive Broadcast, the receiving application must request authorization as shown in the sample code below.

sendOrderedBroadcast(Intent("com.example.NOTIFY"), Manifest.permission.SEND_SMS, new BroadcastReceiver() {
            @SuppressLint("NewApi")
			@Override
            public void onReceive(Context context, Intent intent) {
                Bundle results = getResultExtras(true);
            }
        }, null, Activity.RESULT_OK, null, null);

You may specify an existing system authority, such as SEND_SMS, or you may define a custom authority using the <permission > element to define custom permissions.

Receiving with set permissions

If the authorization parameter is specified when registering BroadcastReceiver (registerReceiver(BroadcastReceiver, IntentFilter, String, Handler) or <receiver> tag) in Manifest Broadcast that requested authorization using the <uses-permission> tag. Only the sender (and also the sender that has been granted permission in conjunction for security reasons) can send intent to the Receiver.

Assume that the receiving application has a Receiver declared in Manifest as shown below.

<receiver android:name=".MyBroadcastReceiver"
              android:permission="android.permission.SEND_SMS">
        <intent-filter>
            <action android:name="android.intent.action.AIRPLANE_MODE"/>
        </intent-filter>
    </receiver>
    

Alternatively, assume that there is a Receiver registered with the context in the receiving application.

var filter = IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED)
registerReceiver(receiver, filter, Manifest.permission.SEND_SMS, null )

To be able to send Broadcast to the above Receiver, it is necessary to request authorization from the sending application, as shown in the sample code below.

<uses-permission android:name="android.permission.SEND_SMS"/>

If this is violated, the following may occur.

  • Broadcast targets cannot be restricted.

6.1.11.3. Applications should only have permissions to specific URIs (Required)

A ContentProvider may want to limit permissions to READ permissions to protect itself while using a custom URI to retrieve information. The application should only have permissions to that specific URI.

The solution is per-URI permissions. When starting an activity or returning a result, a method may set both or either Intent.FLAG_GRANT_READ_URI_PERMISSION and Intent.FLAG_GRANT_WRITE_URI_PERMISSION. FLAG_GRANT_WRITE_URI_PERMISSION and Intent. This will grant permission to the activity of a particular URI, regardless of whether or not it has permission to access data from ContentProvider.

The following is an example of specifying and granting permissions for each URI.

The sample code below is a declaration in AndroidManifest.xml for granting permissions.

<provider android:name=".MyProvider" 
          android:authorities="com.example.sampleprovider.myprovider"
          android:grantUriPermissions="true" />

The sample code below is an example of processing for granting permissions and terminating permissions.

// Allowed by grantUriPermission
grantUriPermission("com.example.sampleresolver", 
                   Uri.parse("content://com.example.sampleprovider/myprovider/"), 
                   Intent.FLAG_GRANT_READ_URI_PERMISSION);

// Subsequent accesses only allow reading (query)

// Revoke UriPermission with revokeUriPermission
revokeUriPermission(Uri.parse("content://com.example.sampleprovider/myprovider/"), 
                    Intent.FLAG_GRANT_READ_URI_PERMISSION);

This would allow for a general capability-style model in which fine-grained permissions are granted on an ad hoc basis depending on user interaction. This could be an important feature to reduce the number of permissions needed by an app to only those directly related to the app’s operation. Without this model, a malicious user could access other members’ email attachments or retrieve contact lists for future use via unprotected URIs.

If this is violated, the following may occur.

  • Unable to restrict access to ContentProvider from unintended apps.

6.1.11.4. Custom permissions are created according to the “Principle of Least Privilege” (Required)

It is important that custom permissions be created in accordance with the Principle of Least Privilege. Permissions should be explicitly defined with meaningful and precise labels and descriptions according to their purpose.

The following is an example of explicitly specifying custom permissions and using them in other applications.

The following is an example declaration of a custom permission called “START_MAIN_ACTIVITY”.

<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>

The following is a declaration to use the created “START_MAIN_ACTIVITY” in other applications.

<manifest>
<uses-permission android:name="com.example.myapp.permission.START_MAIN_ACTIVITY" />
        <application>
            <activity>
            </activity>
        </application>
</manifest>

If this is violated, the following may occur.

  • Unintended security breaches may occur in the event of an anomaly.

6.1.11.6. Check if permissions are really necessary for the app and remove unnecessary permissions (Required)

Because users can revoke the rights of applications that use DANGEROUS permissions, developers should check to see that the application has the proper permissions each time an action requiring those permissions is performed.

Review the permissions with the developer, identify the purpose of all permissions, and remove any unnecessary permissions.

Below is the declaration of permissions in AndroidManifest.xml.

<uses-permission android:name="android.permission.INTERNET" />

Analyze permissions.

Also, by analyzing permissions and investigating specific use case scenarios for your app, you should always check to see if there is an API that can replace the DANGEROUS permissions you are using. A good example is the SMS Retriever API, which streamlines the use of SMS permissions when performing SMS-based user authentication. By using this API, applications do not need to declare DANGEROUS permissions, and both the user and the application developer can use the Permissions Declaration Form for both the user and the application developer.

One method of analysis is the Privacy Dashboard (Android 12 (API level 31) and later) to see how apps describe their access to sensitive information.

If this is violated, the following may occur.

  • The presence of unnecessary DANGEROUS permissions requires unnecessary permission request logic for users.

6.2. MSTG-PLATFORM-2

All inputs from external sources and the user are validated and if necessary sanitized. This includes data received via the UI, IPC mechanisms such as intents, custom URLs, and network sources.

6.2.1. Cross-Site Scripting Flaws

Cross-site scripting (XSS) issues allow attackers to inject client-side scripts into web pages viewed by users. This type of vulnerability is prevalent in web applications. When a user views the injected script in a browser, the attacker gains the ability to bypass the same origin policy, enabling a wide variety of exploits (e.g. stealing session cookies, logging key presses, performing arbitrary actions, etc.).

In the context of native apps, XSS risks are far less prevalent for the simple reason these kinds of applications do not rely on a web browser. However, apps using WebView components, such as WKWebView or the deprecated UIWebView on iOS and WebView on Android, are potentially vulnerable to such attacks.

An older but well-known example is the local XSS issue in the Skype app for iOS, first identified by Phil Purviance. The Skype app failed to properly encode the name of the message sender, allowing an attacker to inject malicious JavaScript to be executed when a user views the message. In his proof-of-concept, Phil showed how to exploit the issue and steal a user’s address book.

Reference

6.2.1.1. Static Analysis

Take a close look at any WebViews present and investigate for untrusted input rendered by the app.

XSS issues may exist if the URL opened by WebView is partially determined by user input. The following example is from an XSS issue in the Zoho Web Service, reported by Linus Särud.

Java

webView.loadUrl("javascript:initialize(" + myNumber + ");");

Kotlin

webView.loadUrl("javascript:initialize($myNumber);")

Another example of XSS issues determined by user input is public overridden methods.

Java

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
  if (url.substring(0,6).equalsIgnoreCase("yourscheme:")) {
    // parse the URL object and execute functions
  }
}

Kotlin

    fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
        if (url.substring(0, 6).equals("yourscheme:", ignoreCase = true)) {
            // parse the URL object and execute functions
        }
    }

Sergey Bobrov was able to take advantage of this in the following HackerOne report. Any input to the HTML parameter would be trusted in Quora’s ActionBarContentActivity. Payloads were successful using adb, clipboard data via ModalContentActivity, and Intents from 3rd party applications.

  • ADB

$ adb shell
$ am start -n com.quora.android/com.quora.android.ActionBarContentActivity \
-e url 'http://test/test' -e html 'XSS<script>alert(123)</script>'
  • Clipboard Data

$ am start -n com.quora.android/com.quora.android.ModalContentActivity  \
-e url 'http://test/test' -e html \
'<script>alert(QuoraAndroid.getClipboardData());</script>'
  • 3rd party Intent in Java or Kotlin :

Intent i = new Intent();
i.setComponent(new ComponentName("com.quora.android",
"com.quora.android.ActionBarContentActivity"));
i.putExtra("url","http://test/test");
i.putExtra("html","XSS PoC <script>alert(123)</script>");
view.getContext().startActivity(i);
val i = Intent()
i.component = ComponentName("com.quora.android",
"com.quora.android.ActionBarContentActivity")
i.putExtra("url", "http://test/test")
i.putExtra("html", "XSS PoC <script>alert(123)</script>")
view.context.startActivity(i)

If a WebView is used to display a remote website, the burden of escaping HTML shifts to the server side. If an XSS flaw exists on the web server, this can be used to execute script in the context of the WebView. As such, it is important to perform static analysis of the web application source code.

Verify that the following best practices have been followed:

  • No untrusted data is rendered in HTML, JavaScript or other interpreted contexts unless it is absolutely necessary.

  • Appropriate encoding is applied to escape characters, such as HTML entity encoding. Note: escaping rules become complicated when HTML is nested within other code, for example, rendering a URL located inside a JavaScript block.

Consider how data will be rendered in a response. For example, if data is rendered in a HTML context, six control characters that must be escaped:

Table 6.2.1.1.1 List of control characters that must be escaped

Character

Escaped

&

&amp;

<

&lt;

>

&gt;

&quot;

&#x27;

/

&#x2F;

For a comprehensive list of escaping rules and other prevention measures, refer to the OWASP XSS Prevention Cheat Sheet.

Reference

Rulebook

6.2.1.2. Dynamic Analysis

XSS issues can be best detected using manual and/or automated input fuzzing, i.e. injecting HTML tags and special characters into all available input fields to verify the web application denies invalid inputs or escapes the HTML meta-characters in its output.

A reflected XSS attack refers to an exploit where malicious code is injected via a malicious link. To test for these attacks, automated input fuzzing is considered to be an effective method. For example, the BURP Scanner is highly effective in identifying reflected XSS vulnerabilities. As always with automated analysis, ensure all input vectors are covered with a manual review of testing parameters.

Reference

6.2.2. Using Data Storage

6.2.2.1. Using Shared Preferences

When you use the SharedPreferences.Editor to read or write int/boolean/long values, you cannot check whether the data is overridden or not. However: it can hardly be used for actual attacks other than chaining the values (e.g. no additional exploits can be packed which will take over the control flow). In the case of a String or a StringSet you should be careful with how the data is interpreted. Using reflection based persistence? Check the section on “Testing Object Persistence” for Android to see how it should be validated. Using the SharedPreferences.Editor to store and read certificates or keys? Make sure you have patched your security provider given vulnerabilities such as found in Bouncy Castle.

In all cases, having the content HMACed can help to ensure that no additions and/or changes have been applied.

Reference

Rulebook

6.2.2.2. Using Other Storage Mechanisms

In case other public storage mechanisms (than the SharedPreferences.Editor) are used, the data needs to be validated the moment it is read from the storage mechanism.

Reference

Rulebook

6.2.3. Testing for Injection Flaws

Android apps can expose functionality through deep links (which are a part of Intents). They can expose functionality to:

  • other apps (via deep links or other IPC mechanisms, such as Intents or BroadcastReceivers).

  • the user (via the user interface).

None of the input from these sources can be trusted; it must be validated and/or sanitized. Validation ensures processing of data that the app is expecting only. If validation is not enforced, any input can be sent to the app, which may allow an attacker or malicious app to exploit app functionality.

The following portions of the source code should be checked if any app functionality has been exposed:

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.

Reference

Rulebook

6.2.3.1. 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--''"

Reference

6.2.4. Testing for Fragment Injection

Android SDK offers developers a way to present a Preferences activity to users, allowing the developers to extend and adapt this abstract class.

This abstract class parses the extra data fields of an Intent, in particular, the PreferenceActivity.EXTRA_SHOW_FRAGMENT(:android:show_fragment) and Preference Activity.EXTRA_SHOW_FRAGMENT_ARGUMENTS(:android:show_fragment_arguments) fields.

The first field is expected to contain the Fragment class name, and the second one is expected to contain the input bundle passed to the Fragment.

Because the PreferenceActivity uses reflection to load the fragment, an arbitrary class may be loaded inside the package or the Android SDK. The loaded class runs in the context of the application that exports this activity.

With this vulnerability, an attacker can call fragments inside the target application or run the code present in other classes’ constructors. Any class that’s passed in the Intent and does not extend the Fragment class will cause a java.lang.CastException, but the empty constructor will be executed before the exception is thrown, allowing the code present in the class constructor run.

To prevent this vulnerability, a new method called isValidFragment was added in Android 4.4 (API level 19). It allows developers to override this method and define the fragments that may be used in this context.

The default implementation returns true on versions older than Android 4.4 (API level 19); it will throw an exception on later versions.

Reference

6.2.4.1. Static Analysis

Steps:

  • Check if android:targetSdkVersion less than 19.

  • Find exported Activities that extend the PreferenceActivity class.

  • Determine whether the method isValidFragment has been overridden.

  • If the app currently sets its android:targetSdkVersion in the manifest to a value less than 19 and the vulnerable class does not contain any implementation of isValidFragment then, the vulnerability is inherited from the PreferenceActivity.

  • In order to fix, developers should either update the android:targetSdkVersion to 19 or higher. Alternatively, if the android:targetSdkVersion cannot be updated, then developers should implement isValidFragment as described.

The following example shows an Activity that extends this activity:

public class MyPreferences extends PreferenceActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

The following examples show the isValidFragment method being overridden with an implementation that allows the loading of MyPreferenceFragment only:

@Override
protected boolean isValidFragment(String fragmentName)
{
return "com.fullpackage.MyPreferenceFragment".equals(fragmentName);
}

Reference

6.2.4.2. Example of Vulnerable App and Exploitation

MainActivity.class

public class MainActivity extends PreferenceActivity {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

MyFragment.class

public class MyFragment extends Fragment {
    public void onCreate (Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragmentLayout, null);
        WebView myWebView = (WebView) wv.findViewById(R.id.webview);
        myWebView.getSettings().setJavaScriptEnabled(true);
        myWebView.loadUrl(this.getActivity().getIntent().getDataString());
        return v;
    }
}

To exploit this vulnerable Activity, you can create an application with the following code:

Intent i = new Intent();
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
i.setClassName("pt.claudio.insecurefragment","pt.claudio.insecurefragment.MainActivity");
i.putExtra(":android:show_fragment","pt.claudio.insecurefragment.MyFragment");
i.setData(Uri.parse("https://security.claudio.pt"));
startActivity(i);

The Vulnerable App and Exploit PoC App are available for downloading.

Reference

6.2.5. Testing for 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. See the Static Analysis section below for more details.

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.

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

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.

Reference

Rulebook

6.2.5.1. Static Analysis

As we mentioned before, handling page navigation should be analyzed carefully, especially when users might be able to navigate away from a trusted environment. The default and safest behavior on Android is to let the default web browser open any link that the user might click inside the WebView. However, this default logic can be modified by configuring a WebViewClient which allows navigation requests to be handled by the app itself. If this is the case, be sure to search for and inspect the following interception callback functions:

  • shouldOverrideUrlLoading allows your application to either abort loading WebViews with suspicious content by returning true or allow the WebView to load the URL by returning false. Considerations:

    • This method is not called for POST requests.

    • This method is not called for XmlHttpRequests, iFrames, “src” attributes included in HTML or <script> tags. Instead, shouldInterceptRequest should take care of this.

  • shouldInterceptRequest allows the application to return the data from resource requests. If the return value is null, the WebView will continue to load the resource as usual. Otherwise, the data returned by the shouldInterceptRequest method is used. Considerations:

    • This callback is invoked for a variety of URL schemes (e.g., http(s):, data:, file:, etc.), not only those schemes which send requests over the network.

    • This is not called for javascript: or blob: URLs, or for assets accessed via file:///android_asset/ or file:///android_res/ URLs. In the case of redirects, this is only called for the initial resource URL, not any subsequent redirect URLs.

  • When Safe Browsing is enabled, these URLs still undergo Safe Browsing checks but the developer can allow the URL with setSafeBrowsingWhitelist or even ignore the warning via the onSafeBrowsingHit callback.

As you can see there are a lot of points to consider when testing the security of WebViews that have a WebViewClient configured, so be sure to carefully read and understand all of them by checking the WebViewClient Documentation.

While the default value of EnableSafeBrowsing is true, some applications might opt to disable it. To verify that SafeBrowsing is enabled, inspect the AndroidManifest.xml file and make sure that the configuration below is not present:

<manifest>
    <application>
        <meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
                   android:value="false" />
        ...
    </application>
</manifest>

Reference

Rulebook

6.2.5.2. Dynamic Analysis

A convenient way to dynamically test deep linking is to use Frida or frida-trace and hook the shouldOverrideUrlLoading, shouldInterceptRequest methods while using the app and clicking on links within the WebView. Be sure to also hook other related Uri methods such as getHost, getScheme or getPath which are typically used to inspect the requests and match known patterns or deny lists.

Reference

6.2.6. Rulebook

  1. Check WebView and investigate for unreliable input rendered by the app (Required)

  2. Notes on input validation when using SharedPreferences (Required)

  3. Notes on input validation when using public storage mechanisms other than SharedPreferences (Required)

  4. All application functions that process data coming in from the UI should implement input validation (Required)

  5. WebView can only load trusted content (Required)

  6. Ensure that all links that users may click within WebView open in their default web browser (Recommended)

6.2.6.1. Check WebView and investigate for unreliable input rendered by the app (Required)

WebView should be checked and investigated for untrusted input rendered by the app.

If some of the URLs opened by WebView are determined by user input, an XSS problem may exist.

The following sample code shows the XSS problem in Zoho Web Service as reported by Linus Särud as reported by Linus Särud.

Example in Java

webView.loadUrl("javascript:initialize(" + myNumber + ");");

Example in Kotlin

webView.loadUrl("javascript:initialize($myNumber);")

To avoid XSS problems, perform an input check on “myNumber” before using it in webView.loadUrl.

Also, the sample code below is a public overridden method of another example of an XSS problem determined by user input.

Example in Java

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
  if (url.substring(0,6).equalsIgnoreCase("yourscheme:")) {
    // parse the URL object and execute functions
  }
}

Example in Kotlin

    fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
        if (url.substring(0, 6).equals("yourscheme:", ignoreCase = true)) {
            // parse the URL object and execute functions
        }
    }

To avoid XSS problems, perform a “url” input check before using “url”.

Verify that the following best practices have been followed:

  • No untrusted data is rendered in HTML, JavaScript or other interpreted contexts unless it is absolutely necessary.

  • Appropriate encoding is applied to escape characters, such as HTML entity encoding. Note: escaping rules become complicated when HTML is nested within other code, for example, rendering a URL located inside a JavaScript block.

Consider how data will be rendered in a response. For example, if data is rendered in a HTML context, six control characters that must be escaped:

Table 6.2.6.1.1 List of control characters that must be escaped

Character

Escaped

&

&amp;

<

&lt;

>

&gt;

&quot;

&#x27;

/

&#x2F;

For a comprehensive list of escaping rules and other prevention measures, refer to the OWASP XSS Prevention Cheat Sheet.

If this is violated, the following may occur.

  • A possible XSS problem exists.

6.2.6.2. Notes on input validation when using SharedPreferences (Required)

When using SharedPreferences as data storage, the following should be noted.

  • In the case of String or StringSet, be careful how the data is interpreted. Sample code for using String in SharedPreferences

    // Save setting value String
    public static void saveString(Context ctx, String key, String val) {
        SharedPreferences prefs = ctx.getSharedPreferences(APP_NAME, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = prefs.edit();
        editor.putString(key, val);
        editor.apply();
    }
    
    // Get setting value String
    public static String loadString(Context ctx, String key) {
        SharedPreferences prefs = ctx.getSharedPreferences(APP_NAME, Context.MODE_PRIVATE);
        return prefs.getString(key, "");
    }
    

    Sample code when using StringSet in SharedPreferences:.

    // Save setting value Set<String>
    public static void saveStringSet(Context ctx, String key, Set<String> vals) {
        SharedPreferences prefs = ctx.getSharedPreferences(APP_NAME, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = prefs.edit();
        editor.putStringSet(key, vals);
        editor.apply();
    }
    
    // Get setting value Set<String>
    public static Set<String> loadStringSet(Context ctx, String key) {
        SharedPreferences prefs = ctx.getSharedPreferences(APP_NAME, Context.MODE_PRIVATE);
        return prefs.getStringSet(key, new HashSet<String>());
    }
    
  • Check Android’s “Testing object persistence” to see how it should be verified.

  • Use SharedPreferences.Editor to verify that you are storing and reading certificates and keys, considering vulnerabilities such as those found in Bouncy Castle and applying patches from security providers.

If this is violated, the following may occur.

  • Strings are interpreted with the wrong data type.

6.2.6.3. Notes on input validation when using public storage mechanisms other than SharedPreferences (Required)

When using a public storage mechanism other than SharedPreferences, input must be validated at the time data is read from the storage mechanism.

The sample code below is an example of the process of reading a file from data storage.

private String readFile(String file){
    String text = null;
    try {
        FileInputStream fileInputStream = openFileInput(file);
        BufferedReader reader = new BufferedReader(new InputStreamReader(fileInputStream, "UTF-8"));
        String lineBuffer;
        while (true){
            lineBuffer = reader.readLine();
            if (lineBuffer != null){
                text += lineBuffer;
            }
            else {
                break;
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    return text;
}

When reading data safely, as in the sample code above, “text” should be verified before “text” is returned.

If this is violated, the following may occur.

  • It may be read as unintended input.

6.2.6.4. All application functions that process data coming in from the UI should implement input validation (Required)

All application functions that process data coming in from the UI must implement input validation.

  • Android Saripaar v2 can be used for user interface input.

    • Sample code omitted due to last release on 9/18/2015.

  • For input from IPC or URL scheme, a validation function should be created.

    The sample code below is an example of a process to determine if a string is alphanumeric or not.

    public boolean isAlphaNumeric(String s){
        String pattern= "^[a-zA-Z0-9]*$";
        return s.matches(pattern);
    }
    

If this is violated, the following may occur.

  • It may be read as unintended input.

6.2.6.5. WebView can only load trusted content (Required)

WebView is a built-in component of Android that allows web pages to be opened within an application. In addition to threats associated with mobile apps, WebView can also expose apps to common web threats (e.g., XSS, Open Redirect, etc.). Therefore, it is necessary to ensure that only trusted content can be loaded.

Safe Web Browsing.

For safer web browsing, use the SafeBrowsing API introduced in Android 8.1 (API level 27). This allows applications to detect URLs that Google has classified as known threats. By default, WebView displays an interstitial that alerts the user to known threats.

The sample code below is an example of how to instruct the WebView instance of an app to always return to a safe page when a known threat is detected.

The following is a declaration in AndroidManifest.

<manifest>
    <application>
        ...
        <meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
                    android:value="true" />
    </application>
</manifest>

The following is the callback process for the WebView calling class.

private var superSafeWebView: WebView? = null
private var safeBrowsingIsInitialized: Boolean = false

// ...

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    superSafeWebView = WebView(this).apply {
        webViewClient = MyWebViewClient()
        safeBrowsingIsInitialized = false
        startSafeBrowsing(this@SafeBrowsingActivity, { success ->
            safeBrowsingIsInitialized = true
            if (!success) {
                Log.e("MY_APP_TAG", "Unable to initialize Safe Browsing!")
            }
        })
    }
}

The following is the processing in the callback onSafeBrowsingHit, which is called when an accessed URL is determined to be unsafe.

class MyWebViewClient : WebViewClient() {
    // Automatically go "back to safety" when attempting to load a website that
    // Safe Browsing has identified as a known threat. An instance of WebView
    // calls this method only after Safe Browsing is initialized, so there's no
    // conditional logic needed here.
    override fun onSafeBrowsingHit(
            view: WebView,
            request: WebResourceRequest,
            threatType: Int,
            callback: SafeBrowsingResponse
    ) {
        // The "true" argument indicates that your app reports incidents like
        // this one to Safe Browsing.
        callback.backToSafety(true)
        Toast.makeText(view.context, "Unsafe web page blocked.", Toast.LENGTH_LONG).show()
    }
}

In addition, the SafetyNet library, which implements a client for the SafeBrowsing Network Protocol v4 SafeBrowsing API independently of WebViews. SafetyNet can analyze all URLs that an app plans to load. Since SafeBrowsing is URL scheme agnostic, it can check URLs with different schemes (http, file, etc.) to counter the TYPE_POTENTIALLY_HARMFUL_APPLICATION and TYPE_SOCIAL_ENGINEERING threat types. The following sample code is from the SafetyNet library.

The sample code below is an example of how to request a URL check using the SafetyNet library.

SafetyNet.getClient(this).lookupUri(
        url,
        SAFE_BROWSING_API_KEY,
        SafeBrowsingThreat.TYPE_POTENTIALLY_HARMFUL_APPLICATION,
        SafeBrowsingThreat.TYPE_SOCIAL_ENGINEERING
)
        .addOnSuccessListener(this) { sbResponse ->
            // Successful communication with service
            if (sbResponse.detectedThreats.isEmpty()) {
                // No threat found
            } else {
                // If a threat is found
            }
        }
        .addOnFailureListener(this) { e: Exception ->
            // Communication failure with service
            if (e is ApiException) {
                // If you get an error with the Google Play Services API
            } else {
                // If you get another error
            }
        }

Otherwise, Virus Total can be used to analyze URLs and local files for known threats.

Also, unless you are developing a browser app, you would normally limit the pages that are loaded to the app’s domain. A good way to do this is to avoid giving users the opportunity to enter URLs within WebView (which is the default in Android) or even navigate outside of trusted domains. Even when navigating within a trusted domain, there is still a risk that users will encounter links to untrusted content and click on them (for example, on pages where other users can post comments).

If this is violated, the following may occur.

  • Vulnerable to web threats (e.g. XSS, Open Redirect, etc.).

6.3. MSTG-PLATFORM-3

The app does not export sensitive functionality via custom URL schemes, unless these mechanisms are properly protected.

6.3.5. Static Analysis

6.3.5.2. Check for Correct Website Association

Even if deep links contain the android:autoVerify=”true” attribute, they must be actually verified in order to be considered App Links. You should test for any possible misconfigurations that might prevent full verification.

Reference

Rulebook

Automatic Verification

Use the Android “App Link Verification” Tester script to get the verification status for all app links (verify-applinks). See an example here.

Reference

Only on Android 12 (API level 31) or higher:

You can use adb to test the verification logic regardless of whether the app targets Android 12 (API level 31) or not. This feature allows you to:

You can also review the verification results. For example:

adb shell pm get-app-links com.example.package

com.example.package:
    ID: 01234567-89ab-cdef-0123-456789abcdef
    Signatures: [***]
    Domain verification state:
      example.com: verified
      sub.example.com: legacy_failure
      example.net: verified
      example.org: 1026

The same information can be found by running adb shell dumpsys package com.example.package (only on Android 12 (API level 31) or higher).

Reference

Manual Verification

This section details a few, of potentially many, reasons why the verification process failed or was not actually triggered. See more information in the Android Developers Documentation and in the white paper “Measuring the Insecurity of Mobile Deep Links of Android”.

Check the Digital Asset Links file:

  • Check for missing Digital Asset Links file:

    • try to find it in the domain’s /.well-known/ path. Example: https://www.example.com/.well-known/assetlinks.json

    • or try https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=www.example.com

  • Check for valid Digital Asset Links file served via HTTP.

  • Check for invalid Digital Asset Links files served via HTTPS. For example:

    • the file contains invalid JSON.

    • the file doesn’t include the target app’s package.

Check for Redirects:

To enhance the app security, the system doesn’t verify any Android App Links for an app if the server sets a redirect such as http://example.com to https://example.com or example.com to www.example.com.

Check for Subdomains:

If an intent filter lists multiple hosts with different subdomains, there must be a valid Digital Asset Links file on each domain. For example, the following intent filter includes www.example.com and mobile.example.com as accepted intent URL hosts.

<application>
  <activity android:name="MainActivity">
    <intent-filter android:autoVerify="true">
      <action android:name="android.intent.action.VIEW" />
      <category android:name="android.intent.category.DEFAULT" />
      <category android:name="android.intent.category.BROWSABLE" />
      <data android:scheme="https" />
      <data android:scheme="https" />
      <data android:host="www.example.com" />
      <data android:host="mobile.example.com" />
    </intent-filter>
  </activity>
</application>

In order for the deep links to correctly register, a valid Digital Asset Links file must be published at both https://www.example.com/.well-known/assetlinks.json and https://mobile.example.com/.well-known/assetlinks.json.

Check for Wildcards:

If the hostname includes a wildcard (such as *.example.com), you should be able to find a valid Digital Asset Links file at the root hostname: https://example.com/.well-known/assetlinks.json.

Reference

6.3.5.3. Check the Handler Method

Even if the deep link is correctly verified, the logic of the handler method should be carefully analyzed. Pay special attention to deep links being used to transmit data (which is controlled externally by the user or any other app).

First, obtain the name of the Activity from the Android Manifest <activity> element which defines the target <intent-filter> and search for usage of getIntent and getData. This general approach of locating these methods can be used across most applications when performing reverse engineering and is key when trying to understand how the application uses deep links and handles any externally provided input data and if it could be subject to any kind of abuse.

The following example is a snippet from an exemplary Kotlin app decompiled with jadx. From the static analysis we know that it supports the deep link deeplinkdemo://load.html/ as part of com.mstg.deeplinkdemo.WebViewActivity.

// snippet edited for simplicity
public final class WebViewActivity extends AppCompatActivity {
    private ActivityWebViewBinding binding;

    public void onCreate(Bundle savedInstanceState) {
        Uri data = getIntent().getData();
        String html = data == null ? null : data.getQueryParameter("html");
        Uri data2 = getIntent().getData();
        String deeplink_url = data2 == null ? null : data2.getQueryParameter("url");
        View findViewById = findViewById(R.id.webView);
        if (findViewById != null) {
            WebView wv = (WebView) findViewById;
            wv.getSettings().setJavaScriptEnabled(true);
            if (deeplink_url != null) {
                wv.loadUrl(deeplink_url);
            ...

You can simply follow the deeplink_url String variable and see the result from the wv.loadUrl call. This means the attacker has full control of the URL being loaded to the WebView (as shown above has JavaScript enabled.

The same WebView might be also rendering an attacker controlled parameter. In that case, the following deep link payload would trigger Reflected Cross-Site Scripting (XSS) within the context of the WebView:

deeplinkdemo://load.html?attacker_controlled=<svg onload=alert(1)>

But there are many other possibilities. Be sure to check the following sections to learn more about what to expect and how to test different scenarios:

In addition, we recommend to search and read public reports (search term: “deep link*”|”deeplink*” site:https://hackerone.com/reports/). For example:

Reference

Rulebook

6.3.6. Dynamic Analysis

Here you will use the list of deep links from the static analysis to iterate and determine each handler method and the processed data, if any. You will first start a Frida hook and then begin invoking the deep links.

The following example assumes a target app that accepts this deep link: deeplinkdemo://load.html. However, we don’t know the corresponding handler method yet, nor the parameters it potentially accepts.

[Step 1] Frida Hooking:

You can use the script “Android Deep Link Observer” from Frida CodeShare to monitor all invoked deep links triggering a call to Intent.getData. You can also use the script as a base to include your own modifications depending on the use case at hand. In this case we included the stack trace in the script since we are interested in the method which calls Intent.getData.

[Step 2] Invoking Deep Links:

Now you can invoke any of the deep links using adb and the Activity Manager (am) which will send intents within the Android device. For example:

adb shell am start -W -a android.intent.action.VIEW -d "deeplinkdemo://load.html/?message=ok#part1"

Starting: Intent { act=android.intent.action.VIEW dat=deeplinkdemo://load.html/?message=ok }
Status: ok
LaunchState: WARM
Activity: com.mstg.deeplinkdemo/.WebViewActivity
TotalTime: 210
WaitTime: 217
Complete

This might trigger the disambiguation dialog when using the “http/https” schema or if other installed apps support the same custom URL schema. You can include the package name to make it an explicit intent.

This invocation will log the following:

[*] Intent.getData() was called
[*] Activity: com.mstg.deeplinkdemo.WebViewActivity
[*] Action: android.intent.action.VIEW

[*] Data
- Scheme: deeplinkdemo://
- Host: /load.html
- Params: message=ok
- Fragment: part1

[*] Stacktrace:

android.content.Intent.getData(Intent.java)
com.mstg.deeplinkdemo.WebViewActivity.onCreate(WebViewActivity.kt)
android.app.Activity.performCreate(Activity.java)
...
com.android.internal.os.ZygoteInit.main(ZygoteInit.java)

In this case we’ve crafted the deep link including arbitrary parameters (?message=ok) and fragment (#part1). We still don’t know if they are being used. The information above reveals useful information that you can use now to reverse engineer the app. See the section “Check the Handler Method” to learn about things you should consider.

  • File: WebViewActivity.kt

  • Class: com.mstg.deeplinkdemo.WebViewActivity

  • Method: onCreate

Sometimes you can even take advantage of other applications that you know interact with your target app. You can reverse engineer the app, (e.g. to extract all strings and filter those which include the target deep links, deeplinkdemo:///load.html in the previous case), or use them as triggers, while hooking the app as previously discussed.

Reference

6.3.7. Rulebook

  1. Check deep links and verify correct relevance of web site (Required)

  2. Carefully analyze the logic of the Handler Method even if deep links are correctly validated (Required)

6.4. MSTG-PLATFORM-4

The app does not export sensitive functionality through IPC facilities, unless these mechanisms are properly protected.

6.4.1. Disclosure of confidential functions by 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:

Reference

Rulebook

6.4.1.1. Activities

Inspect the AndroidManifest

In the “Sieve” app, we find three exported activities, identified by <activity>:

<activity android:excludeFromRecents="true" android:label="@string/app_name" android:launchMode="singleTask" android:name=".MainLoginActivity" android:windowSoftInputMode="adjustResize|stateVisible">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
<activity android:clearTaskOnLaunch="true" android:excludeFromRecents="true" android:exported="true" android:finishOnTaskLaunch="true" android:label="@string/title_activity_file_select" android:name=".FileSelectActivity" />
<activity android:clearTaskOnLaunch="true" android:excludeFromRecents="true" android:exported="true" android:finishOnTaskLaunch="true" android:label="@string/title_activity_pwlist" android:name=".PWList" />

Inspect the Source Code

By inspecting the PWList.java activity, we see that it offers options to list all keys, add, delete, etc. If we invoke it directly, we will be able to bypass the LoginActivity. More on this can be found in the dynamic analysis below.

Reference

Rulebook

6.4.1.2. Services

Inspect the AndroidManifest

In the “Sieve” app, we find two exported services, identified by <service>:

<service android:exported="true" android:name=".AuthService" android:process=":remote" />
<service android:exported="true" android:name=".CryptoService" android:process=":remote" />

Inspect the Source Code

Check the source code for the class android.app.Service:

By reversing the target application, we can see that the service AuthService provides functionality for changing the password and PIN-protecting the target app.

   public void handleMessage(Message msg) {
            AuthService.this.responseHandler = msg.replyTo;
            Bundle returnBundle = msg.obj;
            int responseCode;
            int returnVal;
            switch (msg.what) {
                ...
                case AuthService.MSG_SET /*6345*/:
                    if (msg.arg1 == AuthService.TYPE_KEY) /*7452*/ {
                        responseCode = 42;
                        if (AuthService.this.setKey(returnBundle.getString("com.mwr.example.sieve.PASSWORD"))) {
                            returnVal = 0;
                        } else {
                            returnVal = 1;
                        }
                    } else if (msg.arg1 == AuthService.TYPE_PIN) {
                        responseCode = 41;
                        if (AuthService.this.setPin(returnBundle.getString("com.mwr.example.sieve.PIN"))) {
                            returnVal = 0;
                        } else {
                            returnVal = 1;
                        }
                    } else {
                        sendUnrecognisedMessage();
                        return;
                    }
           }
   }

Reference

Rulebook

6.4.1.3. Broadcast Receivers

Inspect the AndroidManifest

In the “Android Insecure Bank” app, we find a broadcast receiver in the manifest, identified by <receiver>:

<receiver android:exported="true" android:name="com.android.insecurebankv2.MyBroadCastReceiver">
    <intent-filter>
        <action android:name="theBroadcast" />
    </intent-filter>
</receiver>

Inspect the Source Code

Search the source code for strings like sendBroadcast, sendOrderedBroadcast, and sendStickyBroadcast. Make sure that the application doesn’t send any sensitive data.

If an Intent is broadcasted and received within the application only, LocalBroadcastManager can be used to prevent other apps from receiving the broadcast message. This reduces the risk of leaking sensitive information.

To understand more about what the receiver is intended to do, we have to go deeper in our static analysis and search for usage of the class android.content.BroadcastReceiver and the Context.registerReceiver method, which is used to dynamically create receivers.

The following extract of the target application’s source code shows that the broadcast receiver triggers transmission of an SMS message containing the user’s decrypted password.

public class MyBroadCastReceiver extends BroadcastReceiver {
  String usernameBase64ByteString;
  public static final String MYPREFS = "mySharedPreferences";

  @Override
  public void onReceive(Context context, Intent intent) {
    // TODO Auto-generated method stub

        String phn = intent.getStringExtra("phonenumber");
        String newpass = intent.getStringExtra("newpass");

    if (phn != null) {
      try {
                SharedPreferences settings = context.getSharedPreferences(MYPREFS, Context.MODE_WORLD_READABLE);
                final String username = settings.getString("EncryptedUsername", null);
                byte[] usernameBase64Byte = Base64.decode(username, Base64.DEFAULT);
                usernameBase64ByteString = new String(usernameBase64Byte, "UTF-8");
                final String password = settings.getString("superSecurePassword", null);
                CryptoClass crypt = new CryptoClass();
                String decryptedPassword = crypt.aesDeccryptedString(password);
                String textPhoneno = phn.toString();
                String textMessage = "Updated Password from: "+decryptedPassword+" to: "+newpass;
                SmsManager smsManager = SmsManager.getDefault();
                System.out.println("For the changepassword - phonenumber: "+textPhoneno+" password is: "+textMessage);
smsManager.sendTextMessage(textPhoneno, null, textMessage, null, null);
          }
     }
  }
}

BroadcastReceivers should use the android:permission attribute; otherwise, other applications can invoke them. You can use Context.sendBroadcast(intent, receiverPermission); to specify permissions a receiver must have to read the broadcast. You can also set an explicit application package name that limits the components this Intent will resolve to. If left as the default value (null), all components in all applications will be considered. If non-null, the Intent can match only the components in the given application package.

Reference

Rulebook

6.4.2. Static Analysis

We start by looking at the AndroidManifest.xml, where all activities, services, and content providers included in the source code must be declared (otherwise the system won’t recognize them and they won’t run). Broadcast receivers can be declared in the manifest or created dynamically. You will want to identify elements such as

An “exported” activity, service, or content can be accessed by other apps. There are two common ways to designate a component as exported. The obvious one is setting the export tag to true android:exported=”true”. The second way involves defining an <intent-filter> within the component element (<activity>, <service>, <receiver>). When this is done, the export tag is automatically set to “true”. To prevent all other Android apps from interacting with the IPC component element, be sure that the android:exported=”true” value and an <intent-filter> aren’t in their AndroidManifest.xml files unless this is necessary.

Remember that using the permission tag (android:permission) will also limit other applications’ access to a component. If your IPC is intended to be accessible to other applications, you can apply a security policy with the <permission> element and set a proper android:protectionLevel. When android:permission is used in a service declaration, other applications must declare a corresponding <uses-permission> element in their own manifest to start, stop, or bind to the service.

For more information about the content providers, please refer to the test case “Testing Whether Stored Sensitive Data Is Exposed via IPC Mechanisms” in chapter “Testing Data Storage”.

Once you identify a list of IPC mechanisms, review the source code to see whether sensitive data is leaked when the mechanisms are used. For example, content providers can be used to access database information, and services can be probed to see if they return data. Broadcast receivers can leak sensitive information if probed or sniffed.

In the following, we use two example apps and give examples of identifying vulnerable IPC components:

Reference

Rulebook

6.4.3. Dynamic Analysis

You can enumerate IPC components with MobSF. To list all exported IPC components, upload the APK file and the components collection will be displayed in the following screen:

../_images/MobSF_Show_Components.jpg

Fig 6.4.3.1 Component Collection

Reference

6.4.3.1. Content Providers

The “Sieve” application implements a vulnerable content provider. To list the content providers exported by the Sieve app, execute the following command:

$ adb shell dumpsys package com.mwr.example.sieve | grep -Po "Provider{[\w\d\s\./]+}" | sort -u
Provider{34a20d5 com.mwr.example.sieve/.FileBackupProvider}
Provider{64f10ea com.mwr.example.sieve/.DBContentProvider}

Once identified, you can use jadx to reverse engineer the app and analyze the source code of the exported content providers to identify potential vulnerabilities.

To identify the corresponding class of a content provider, use the following information:

  • Package Name: com.mwr.example.sieve.

  • Content Provider Class Name: DBContentProvider.

When analyzing the class com.mwr.example.sieve.DBContentProvider, you’ll see that it contains several URIs:

package com.mwr.example.sieve;
...
public class DBContentProvider extends ContentProvider {
    public static final Uri KEYS_URI = Uri.parse("content://com.mwr.example.sieve.DBContentProvider/Keys");
    public static final Uri PASSWORDS_URI = Uri.parse("content://com.mwr.example.sieve.DBContentProvider/Passwords");
...
}

Use the following commands to call the content provider using the identified URIs:

$ adb shell content query --uri content://com.mwr.example.sieve.DBContentProvider/Keys/
Row: 0 Password=1234567890AZERTYUIOPazertyuiop, pin=1234

$ adb shell content query --uri content://com.mwr.example.sieve.DBContentProvider/Passwords/
Row: 0 _id=1, service=test, username=test, password=BLOB, email=t@tedt.com
Row: 1 _id=2, service=bank, username=owasp, password=BLOB, email=user@tedt.com

$ adb shell content query --uri content://com.mwr.example.sieve.DBContentProvider/Passwords/ --projection email:username:password --where 'service=\"bank\"'
Row: 0 email=user@tedt.com, username=owasp, password=BLOB

You are able now to retrieve all database entries (see all lines starting with “Row:” in the output).

Reference

6.4.3.2. Activity

To list activities exported by an application, you can use the following command and focus on activity elements:

$ aapt d xmltree sieve.apk AndroidManifest.xml
...
E: activity (line=32)
  A: android:label(0x01010001)=@0x7f05000f
  A: android:name(0x01010003)=".FileSelectActivity" (Raw: ".FileSelectActivity")
  A: android:exported(0x01010010)=(type 0x12)0xffffffff
  A: android:finishOnTaskLaunch(0x01010014)=(type 0x12)0xffffffff
  A: android:clearTaskOnLaunch(0x01010015)=(type 0x12)0xffffffff
  A: android:excludeFromRecents(0x01010017)=(type 0x12)0xffffffff
E: activity (line=40)
  A: android:label(0x01010001)=@0x7f050000
  A: android:name(0x01010003)=".MainLoginActivity" (Raw: ".MainLoginActivity")
  A: android:excludeFromRecents(0x01010017)=(type 0x12)0xffffffff
  A: android:launchMode(0x0101001d)=(type 0x10)0x2
  A: android:windowSoftInputMode(0x0101022b)=(type 0x11)0x14
  E: intent-filter (line=46)
    E: action (line=47)
      A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN")
    E: category (line=49)
      A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")
E: activity (line=52)
  A: android:label(0x01010001)=@0x7f050009
  A: android:name(0x01010003)=".PWList" (Raw: ".PWList")
  A: android:exported(0x01010010)=(type 0x12)0xffffffff
  A: android:finishOnTaskLaunch(0x01010014)=(type 0x12)0xffffffff
  A: android:clearTaskOnLaunch(0x01010015)=(type 0x12)0xffffffff
  A: android:excludeFromRecents(0x01010017)=(type 0x12)0xffffffff
E: activity (line=60)
  A: android:label(0x01010001)=@0x7f05000a
  A: android:name(0x01010003)=".SettingsActivity" (Raw: ".SettingsActivity")
  A: android:finishOnTaskLaunch(0x01010014)=(type 0x12)0xffffffff
  A: android:clearTaskOnLaunch(0x01010015)=(type 0x12)0xffffffff
  A: android:excludeFromRecents(0x01010017)=(type 0x12)0xffffffff
...

You can identify an exported activity using one of the following properties:

  • It have an intent-filter sub declaration.

  • It have the attribute android:exported to 0xffffffff.

You can also use jadx to identify exported activities in the file AndroidManifest.xml using the criteria described above:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.mwr.example.sieve">
...
  <!-- This activity is exported via the attribute "exported" -->
  <activity android:name=".FileSelectActivity" android:exported="true" />
   <!-- This activity is exported via the "intent-filter" declaration  -->
  <activity android:name=".MainLoginActivity">
    <intent-filter>
      <action android:name="android.intent.action.MAIN"/>
      <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
  </activity>
  <!-- This activity is exported via the attribute "exported" -->
  <activity android:name=".PWList" android:exported="true" />
  <!-- Activities below are not exported -->
  <activity android:name=".SettingsActivity" />
  <activity android:name=".AddEntryActivity"/>
  <activity android:name=".ShortLoginActivity" />
  <activity android:name=".WelcomeActivity" />
  <activity android:name=".PINActivity" />
...
</manifest>

Enumerating activities in the vulnerable password manager “Sieve” shows that the following activities are exported:

  • .MainLoginActivity

  • .PWList

  • .FileSelectActivity

Use the command below to launch an activity:

# Start the activity without specifying an action or an category
$ adb shell am start -n com.mwr.example.sieve/.PWList
Starting: Intent { cmp=com.mwr.example.sieve/.PWList }

# Start the activity indicating an action (-a) and an category (-c)
$ adb shell am start -n "com.mwr.example.sieve/.MainLoginActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.mwr.example.sieve/.MainLoginActivity }

Since the activity .PWList is called directly in this example, you can use it to bypass the login form protecting the password manager, and access the data contained within the password manager.

Reference

6.4.3.3. Service

Services can be enumerated with the Drozer module app.service.info:

dz> run app.service.info -a com.mwr.example.sieve
Package: com.mwr.example.sieve
  com.mwr.example.sieve.AuthService
    Permission: null
  com.mwr.example.sieve.CryptoService
    Permission: null

To communicate with a service, you must first use static analysis to identify the required inputs.

Because this service is exported, you can use the module app.service.send to communicate with the service and change the password stored in the target application:

dz> run app.service.send com.mwr.example.sieve com.mwr.example.sieve.AuthService --msg 6345 7452 1 --extra string com.mwr.example.sieve.PASSWORD "abcdabcdabcdabcd" --bundle-as-obj
Got a reply from com.mwr.example.sieve/com.mwr.example.sieve.AuthService:
  what: 4
  arg1: 42
  arg2: 0
  Empty

Reference

6.4.3.4. BroadcastReceivers

To list broadcast receivers exported by an application, you can use the following command and focus on receiver elements:

$ aapt d xmltree InsecureBankv2.apk AndroidManifest.xml
...
E: receiver (line=88)
  A: android:name(0x01010003)="com.android.insecurebankv2.MyBroadCastReceiver" (Raw: "com.android.insecurebankv2.MyBroadCastReceiver")
  A: android:exported(0x01010010)=(type 0x12)0xffffffff
  E: intent-filter (line=91)
    E: action (line=92)
      A: android:name(0x01010003)="theBroadcast" (Raw: "theBroadcast")
E: receiver (line=119)
  A: android:name(0x01010003)="com.google.android.gms.wallet.EnableWalletOptimizationReceiver" (Raw: "com.google.android.gms.wallet.EnableWalletOptimizationReceiver")
  A: android:exported(0x01010010)=(type 0x12)0x0
  E: intent-filter (line=122)
    E: action (line=123)
      A: android:name(0x01010003)="com.google.android.gms.wallet.ENABLE_WALLET_OPTIMIZATION" (Raw: "com.google.android.gms.wallet.ENABLE_WALLET_OPTIMIZATION")
...

You can identify an exported broadcast receiver using one of the following properties:

  • It has an intent-filter sub declaration.

  • It has the attribute android:exported set to 0xffffffff.

You can also use jadx to identify exported broadcast receivers in the file AndroidManifest.xml using the criteria described above:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.insecurebankv2">
...
  <!-- This broadcast receiver is exported via the attribute "exported" as well as the "intent-filter" declaration -->
  <receiver android:name="com.android.insecurebankv2.MyBroadCastReceiver" android:exported="true">
    <intent-filter>
      <action android:name="theBroadcast"/>
    </intent-filter>
  </receiver>
  <!-- This broadcast receiver is NOT exported because the attribute "exported" is explicitly set to false -->
  <receiver android:name="com.google.android.gms.wallet.EnableWalletOptimizationReceiver" android:exported="false">
    <intent-filter>
      <action android:name="com.google.android.gms.wallet.ENABLE_WALLET_OPTIMIZATION"/>
    </intent-filter>
  </receiver>
...
</manifest>

The above example from the vulnerable banking application InsecureBankv2 shows that only the broadcast receiver named com.android.insecurebankv2.MyBroadCastReceiver is exported.

Now that you know that there is an exported broadcast receiver, you can dive deeper and reverse engineer the app using jadx. This will allow you to analyze the source code searching for potential vulnerabilities that you could later try to exploit. The source code of the exported broadcast receiver is the following:

package com.android.insecurebankv2;
...
public class MyBroadCastReceiver extends BroadcastReceiver {
    public static final String MYPREFS = "mySharedPreferences";
    String usernameBase64ByteString;

    public void onReceive(Context context, Intent intent) {
        String phn = intent.getStringExtra("phonenumber");
        String newpass = intent.getStringExtra("newpass");
        if (phn != null) {
            try {
                SharedPreferences settings = context.getSharedPreferences("mySharedPreferences", 1);
                this.usernameBase64ByteString = new String(Base64.decode(settings.getString("EncryptedUsername", (String) null), 0), "UTF-8");
                String decryptedPassword = new CryptoClass().aesDeccryptedString(settings.getString("superSecurePassword", (String) null));
                String textPhoneno = phn.toString();
                String textMessage = "Updated Password from: " + decryptedPassword + " to: " + newpass;
                SmsManager smsManager = SmsManager.getDefault();
                System.out.println("For the changepassword - phonenumber: " + textPhoneno + " password is: " + textMessage);
                smsManager.sendTextMessage(textPhoneno, (String) null, textMessage, (PendingIntent) null, (PendingIntent) null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("Phone number is null");
        }
    }
}

As you can see in the source code, this broadcast receiver expects two parameters named phonenumber and newpass. With this information you can now try to exploit this broadcast receiver by sending events to it using custom values:

# Send an event with the following properties:
# Action is set to "theBroadcast"
# Parameter "phonenumber" is set to the string "07123456789"
# Parameter "newpass" is set to the string "12345"
$ adb shell am broadcast -a theBroadcast --es phonenumber "07123456789" --es newpass "12345"
Broadcasting: Intent { act=theBroadcast flg=0x400000 (has extras) }
Broadcast completed: result=0

This generates the following SMS:

Updated Password from: SecretPassword@ to: 12345

Sniffing Intents
If an Android application broadcasts intents without setting a required permission or specifying the destination package, the intents can be monitored by any application that runs on the device.

To register a broadcast receiver to sniff intents, use the Drozer module app.broadcast.sniff and specify the action to monitor with the –action parameter:

dz> run app.broadcast.sniff  --action theBroadcast
[*] Broadcast receiver registered to sniff matching intents
[*] Output is updated once a second. Press Control+C to exit.

Action: theBroadcast
Raw: Intent { act=theBroadcast flg=0x10 (has extras) }
Extra: phonenumber=07123456789 (java.lang.String)
Extra: newpass=12345 (java.lang.String)`

You can also use the following command to sniff the intents. However, the content of the extras passed will not be displayed:

$ adb shell dumpsys activity broadcasts | grep "theBroadcast"
BroadcastRecord{fc2f46f u0 theBroadcast} to user 0
Intent { act=theBroadcast flg=0x400010 (has extras) }
BroadcastRecord{7d4f24d u0 theBroadcast} to user 0
Intent { act=theBroadcast flg=0x400010 (has extras) }
45: act=theBroadcast flg=0x400010 (has extras)
46: act=theBroadcast flg=0x400010 (has extras)
121: act=theBroadcast flg=0x400010 (has extras)
144: act=theBroadcast flg=0x400010 (has extras)

Reference

6.4.4. Rulebook

  1. Use IPC mechanism for security considerations (Required)

6.4.4.1. Use IPC mechanism for security considerations (Required)

Security considerations and the use of IPC mechanisms prevent the leakage or disclosure of sensitive data from apps.

There are two general methods of disclosing components to other apps Do not publish components that do not need to be published to other (external) apps.

  • Set android:exported=”true” in the component definition in AndroidManifest.xml. This exposes the component to other apps.

  • Add the definition of <intent-filter> to the component definition in AndroidManifest.xml.
    If the API level is less than 31, android:exported is automatically set to “true” by defining it.
    If API level 31 or higher, android:exported must be explicitly set when defining <intent-filter>. If not, the installation will fail.

The following is an example of defining <intent-filter> in AndroidManifest.xml and setting android:exported=”true”.

<activity android:name="MyActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
</activity>

<service android:name="MyService"
         android:exported="true">
    <intent-filter>
        <action android:name="com.example.app.START_BACKGROUND" />
    </intent-filter>
</service>

<receiver android:name="MyReceiver"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.LOCALE_CHANGED"/>
    </intent-filter>
</receiver>

The permission tag (android:permission) can also be used to restrict access to other application components. If IPC is intended to be accessed by other applications, a security policy can be applied with the <permission> element and the appropriate android:protectionLevel can be set.

Below is an example of defining custom permissions in AndroidManifest.xml.

<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>

The following is an example of defining custom permissions defined above in AndroidManifest.xml for another app.

<manifest>
<uses-permission android:name="com.example.myapp.permission.START_MAIN_ACTIVITY" />
        <application>
            <activity>
            </activity>
        </application>
</manifest>

If the IPC mechanism needs to be disclosed, make sure the source code does not leak sensitive data when the mechanism is used.

The sample code below shows the process of setting a string to an Intent and sending BroadCast. In these cases, the string should not contain sensitive data.

Intent().also { intent ->
    intent.setAction("com.example.broadcast.MY_NOTIFICATION")
    intent.putExtra("data", "Notice me senpai!")
    sendBroadcast(intent)
}

If the Intent is broadcast and received only within the application, the LocalBroadcastManager can be used to prevent other apps from receiving the broadcast message. This reduces the risk of sensitive information being compromised.

If this is violated, the following may occur.

  • It leads to leakage or exposure of confidential data through IPC mechanisms.

6.5. MSTG-PLATFORM-5

JavaScript is disabled in WebViews unless explicitly required.

6.5.1. Using JavaScript in WebView

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.

Reference

6.5.1.1. Static Analysis

The source code must be checked for usage and implementations of the WebView class. To create and use a WebView, you must create an instance of the WebView class.

WebView webview = new WebView(this);
setContentView(webview);
webview.loadUrl("https://www.owasp.org/");

Various settings can be applied to the WebView (activating/deactivating JavaScript is one example). JavaScript is disabled by default for WebViews and must be explicitly enabled. Look for the method setJavaScriptEnabled to check for JavaScript activation.

webview.getSettings().setJavaScriptEnabled(true);

This allows the WebView to interpret JavaScript. It should be enabled only if necessary to reduce the attack surface to the app. If JavaScript is necessary, you should make sure that

  • The communication to the endpoints consistently relies on HTTPS (or other protocols that allow encryption) to protect HTML and JavaScript from tampering during transmission.

  • JavaScript and HTML are loaded locally, from within the app data directory or from trusted web servers only.

  • The user cannot define which sources to load by means of loading different resources based on a user provided input.

To remove all JavaScript source code and locally stored data, clear the WebView’s cache with clearCache when the app closes.

Devices running platforms older than Android 4.4 (API level 19) use a version of WebKit that has several security issues. As a workaround, the app must confirm that WebView objects display only trusted content if the app runs on these devices.

Reference

Rulebook

6.5.1.2. Dynamic Analysis

Dynamic Analysis depends on operating conditions. There are several ways to inject JavaScript into an app’s WebView:

  • Stored Cross-Site Scripting vulnerabilities in an endpoint; the exploit will be sent to the mobile app’s WebView when the user navigates to the vulnerable function.

  • Attacker takes a man-in-the-middle (MITM) position and tampers with the response by injecting JavaScript.

  • Malware tampering with local files that are loaded by the WebView.

To address these attack vectors, check the following:

  • All functions offered by the endpoint should be free of stored XSS.

  • Only files that are in the app data directory should be rendered in a WebView (see test case “Testing for Local File Inclusion in WebViews”).

  • The HTTPS communication must be implemented according to best practices to avoid MITM attacks. This means:

    • all communication is encrypted via TLS (see test case “Testing for Unencrypted Sensitive Data on the Network”),

    • the certificate is checked properly (see test case “Testing Endpoint Identify Verification”), and/or

    • the certificate should be pinned (see “Testing Custom Certificate Stores and Certificate Pinning”).

Reference

Rulebook

6.5.2. Rulebook

  1. Restrict use of JavaScript in WebView (Required)

  2. Address attack vectors when using JavaScript in WebView (Required)

6.5.2.1. Restrict use of JavaScript in WebView (Required)

Every app has its own WebView cache, which is not shared with native browsers or other apps. On Android, WebView uses the WebKit rendering engine to display web pages, but the page has minimal functionality, including no address bar. If WebView is poorly implemented and JavaScript is allowed, it is possible to use JavaScript to attack the application and access its data.

Therefore, to use WebView more safely, the use of JavaScript must be restricted.

JavaScript is disabled by default in WebView and must be explicitly enabled in order to use it. To enable it, use the setJavaScriptEnabled method and set the argument to true.

WebView webview = new WebView(this);
webview.getSettings().setJavaScriptEnabled(true);
setContentView(webview);
webview.loadUrl("https://www.owasp.org/");

Therefore, if you do not need to enable JavaScript, do not set true in the setJavaScriptEnabled method.

If JavaScript is required, the following must be verified.

  • Communication to the endpoint consistently relies on HTTPS (or other protocol capable of encryption) to protect HTML and JavaScript from tampering in transit.

  • JavaScript and HTML are loaded locally only from within the app’s data directory or from a trusted web server.

  • A means of loading different resources based on user-provided input, but not allowing the user to define which sources to load.

If you want to delete all JavaScript source code and locally stored data, use clearCache to clear WebView’s cache when the app exits.

Below is a sample code for clearing WebView’s cache with clearCache.

webView.clearCache(true);

Devices running platforms older than Android 4.4 (API level 19) are using a version of WebKit that has several security issues. As a workaround, when running your app on these devices, you need to make sure that your app checks that the WebView object shows only trusted content, you need to make sure that your app does so.

If this is violated, the following may occur.

  • An app can be attacked using JavaScript to access data within the app.

6.5.2.2. Address attack vectors when using JavaScript in WebView (Required)

To deal with attack vectors that inject JavaScript into an app’s WebView, take the following actions.

  • All functions offered by the endpoint should be free of stored XSS.

  • Only files that are in the app data directory should be rendered in a WebView.The sample code below shows how to load a file in the application’s data directory in WebView.

    WebView = new WebView(this);
    webView.loadUrl("file:///android_asset/filename.html");
    
  • The HTTPS communication must be implemented according to best practices to avoid MITM attacks. This means that.

    • all communication is encrypted via TLS. The sample code below is a TLS communication process.

          // Load CAs from an InputStream
          // (could be from a resource or ByteArrayInputStream or ...)
          CertificateFactory cf = CertificateFactory.getInstance("X.509");
          // From https://www.washington.edu/itconnect/security/ca/load-der.crt
          InputStream caInput = new BufferedInputStream(new FileInputStream("load-der.crt"));
          Certificate ca;
          try {
              ca = cf.generateCertificate(caInput);
              System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
          } finally {
              caInput.close();
          }
      
          // Create a KeyStore containing our trusted CAs
          String keyStoreType = KeyStore.getDefaultType();
          KeyStore keyStore = KeyStore.getInstance(keyStoreType);
          keyStore.load(null, null);
          keyStore.setCertificateEntry("ca", ca);
      
          // Create a TrustManager that trusts the CAs in our KeyStore
          String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
          TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
          tmf.init(keyStore);
      
          // Create an SSLContext that uses our TrustManager
          SSLContext context = SSLContext.getInstance("TLS");
          context.init(null, tmf.getTrustManagers(), null);
      
          // Tell the URLConnection to use a SocketFactory from our SSLContext
          URL url = new URL("https://certs.cac.washington.edu/CAtest/");
          HttpsURLConnection urlConnection =
              (HttpsURLConnection)url.openConnection();
          urlConnection.setSSLSocketFactory(context.getSocketFactory());
          InputStream in = urlConnection.getInputStream();
          copyInputStreamToOutputStream(in, System.out);
          
      
    • the certificate is checked properly. See “Verification by TrustManager (Required)” for sample code for proper checking of certificates.

    • the certificate should be pinned.

If this is violated, the following may occur.

  • Stored Cross-Site Scripting vulnerabilities in an endpoint; the exploit will be sent to the mobile app’s WebView when the user navigates to the vulnerable function.

  • Attacker takes a man-in-the-middle (MITM) position and tampers with the response by injecting JavaScript.

  • Malware tampering with local files that are loaded by the WebView.

6.6. MSTG-PLATFORM-6

WebViews are configured to allow only the minimum set of protocol handlers required (ideally, only https is supported). Potentially dangerous handlers, such as file, tel and app-id, are disabled.

6.6.1. Protocol handlers allowed in WebView

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.

Reference

Rulebook

6.6.2. Static Analysis

Check the source code for WebView usage. The following WebView settings control resource access:

  • setAllowContentAccess: Content URL access allows WebViews to load content from a content provider installed on the system, which is enabled by default .

  • setAllowFileAccess: Enables and disables file access within a WebView. The default value is true when targeting Android 10 (API level 29) and below and false for Android 11 (API level 30) and above. Note that this enables and disables file system access only. Asset and resource access is unaffected and accessible via file:///android_asset and file:///android_res.

  • setAllowFileAccessFromFileURLs: Does or does not allow JavaScript running in the context of a file scheme URL to access content from other file scheme URLs. The default value is true for Android 4.0.3 - 4.0.4 (API level 15) and below and false for Android 4.1 (API level 16) and above.

  • setAllowUniversalAccessFromFileURLs: Does or does not allow JavaScript running in the context of a file scheme URL to access content from any origin. The default value is true for Android 4.0.3 - 4.0.4 (API level 15) and below and false for Android 4.1 (API level 16) and above.

If one or more of the above methods is/are activated, you should determine whether the method(s) is/are really necessary for the app to work properly.

If a WebView instance can be identified, find out whether local files are loaded with the loadURL method.

WebView = new WebView(this);
webView.loadUrl("file:///android_asset/filename.html");

The location from which the HTML file is loaded must be verified. If the file is loaded from external storage, for example, the file is readable and writable by everyone. This is considered a bad practice. Instead, the file should be placed in the app’s assets directory.

webview.loadUrl("file:///" +
Environment.getExternalStorageDirectory().getPath() +
"filename.html");

The URL specified in loadURL should be checked for dynamic parameters that can be manipulated; their manipulation may lead to local file inclusion.

Use the following code snippet and best practices to deactivate protocol handlers, if applicable:

//If attackers can inject script into a WebView, they could access local resources. This can be prevented by disabling local file system access, which is enabled by default. You can use the Android WebSettings class to disable local file system access via the public method `setAllowFileAccess`.
webView.getSettings().setAllowFileAccess(false);

webView.getSettings().setAllowFileAccessFromFileURLs(false);

webView.getSettings().setAllowUniversalAccessFromFileURLs(false);

webView.getSettings().setAllowContentAccess(false);
  • Create a list that defines local and remote web pages and protocols that are allowed to be loaded.

  • Create checksums of the local HTML/JavaScript files and check them while the app is starting up. Minify JavaScript files to make them harder to read.

Reference

Rulebook

6.6.3. Dynamic Analysis

To identify the usage of protocol handlers, look for ways to trigger phone calls and ways to access files from the file system while you’re using the app.

Reference

6.6.4. Rulebook

  1. WebView does not use potentially dangerous protocol handlers (Recommended)

6.7. MSTG-PLATFORM-7

If native methods of the app are exposed to a WebView, verify that the WebView only renders JavaScript contained within the app package.

6.7.1. Bridge between JavaScript and native in WebView

Android offers a way for JavaScript executed 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).

Reference

Rulebook

6.7.1.1. Static Analysis

You need to determine whether the method addJavascriptInterface is used, how it is used, and whether an attacker can inject malicious JavaScript.

The following example shows how addJavascriptInterface is used to bridge a Java Object and JavaScript in a WebView:

WebView webview = new WebView(this);
WebSettings webSettings = webview.getSettings();
webSettings.setJavaScriptEnabled(true);

MSTG_ENV_008_JS_Interface jsInterface = new MSTG_ENV_008_JS_Interface(this);

myWebView.addJavascriptInterface(jsInterface, "Android");
myWebView.loadURL("http://example.com/file.html");
setContentView(myWebView);

In Android 4.2 (API level 17) and above, an annotation @JavascriptInterface explicitly allows JavaScript to access a Java method.

public class MSTG_ENV_008_JS_Interface {

        Context mContext;

        /** Instantiate the interface and set the context */
        MSTG_ENV_005_JS_Interface(Context c) {
            mContext = c;
        }

        @JavascriptInterface
        public String returnString () {
            return "Secret String";
        }

        /** Show a toast from the web page */
        @JavascriptInterface
        public void showToast(String toast) {
            Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
        }
}

This is how you can call the method returnString from JavaScript, the string “Secret String” will be stored in the variable result:

var result = window.Android.returnString();

With access to the JavaScript code, via, for example, stored XSS or a MITM attack, an attacker can directly call the exposed Java methods.

If addJavascriptInterface is necessary, take the following considerations:

  • Only JavaScript provided with the APK should be allowed to use the bridges, e.g. by verifying the URL on each bridged Java method (via WebView.getUrl).

  • No JavaScript should be loaded from remote endpoints, e.g. by keeping page navigation within the app’s domains and opening all other domains on the default browser (e.g. Chrome, Firefox).

  • If necessary for legacy reasons (e.g. having to support older devices), at least set the minimal API level to 17 in the manifest file of the app (<uses-sdk android:minSdkVersion=”17” />).

Reference

Rulebook

6.7.1.2. Dynamic Analysis

Dynamic analysis of the app can show you which HTML or JavaScript files are loaded and which vulnerabilities are present. The procedure for exploiting the vulnerability starts with producing a JavaScript payload and injecting it into the file that the app is requesting. The injection can be accomplished via a MITM attack or direct modification of the file if it is stored in external storage. The whole process can be accomplished via Drozer and weasel (MWR’s advanced exploitation payload), which can install a full agent, injecting a limited agent into a running process or connecting a reverse shell as a Remote Access Tool (RAT).

A full description of the attack is included in the blog article by MWR.

Reference

6.7.2. Rulebook

  1. Be aware of the use of WebView JavaScript Bridge (Required)

6.7.2.1. Be aware of the use of WebView JavaScript Bridge (Required)

A method is provided for JavaScript executed in a WebView to call and use native Android app functions (annotated with @JavascriptInterface) using the addJavascriptInterface method.

Note that using @addJavascriptInterface will explicitly grant access to the registered JavaScript Interface object to all pages loaded within that WebView.

Apps targeting Android versions below Android 4.2 (API level 17) have a flaw in the implementation of addJavascriptInterface that could lead to remote code execution if malicious JavaScript is injected into the WebView through a reflection exploit. This can lead to remote code execution if malicious JavaScript is injected into WebView through a reflection attack.

The sample code below is an example of WebView JavaScript Bridge using the addJavascriptInterface method under Android 4.2 (API level 17).

WebView webview = new WebView(this);
WebSettings webSettings = webview.getSettings();
webSettings.setJavaScriptEnabled(true);

MSTG_ENV_008_JS_Interface jsInterface = new MSTG_ENV_008_JS_Interface(this);

myWebView.addJavascriptInterface(jsInterface, "Android");
myWebView.loadURL("http://example.com/file.html");
setContentView(myWebView);

In Android 4.2 (API level 17) and later, the @JavascriptInterface annotation can be used to explicitly allow JavaScript to access Java methods. Sample code is shown below.

public class MSTG_ENV_008_JS_Interface {

        Context mContext;

        /** Instantiate the interface and set the context */
        MSTG_ENV_005_JS_Interface(Context c) {
            mContext = c;
        }

        @JavascriptInterface
        public String returnString () {
            return "Secret String";
        }

        /** Show a toast from the web page */
        @JavascriptInterface
        public void showToast(String toast) {
            Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
        }
}

As shown above, when the returnString method is called from JavaScript, the string “Secret String” is stored in the variable “result.

var result = window.Android.returnString();

If addJavascriptInterface is required, it should be used with the following points in mind

  • Only JavaScript provided with the APK should be allowed to use the bridge. For example, validate the URL of each bridged Java method (using WebView.getUrl).

  • Do not load JavaScript from remote endpoints. For example, keep page navigation within the app’s domain and open other domains in the default browser (Chrome, Firefox, etc.).

  • If required for legacy reasons (e.g. need to support older devices), at least set the minimum API level to 17 in the app’s manifest file (<uses-sdk android:minSdkVersion=”17” />).

If this is violated, the following may occur.

  • May be accessed by JavaScript that does not expect a registered JavaScript Interface object.

6.8. MSTG-PLATFORM-8

Object deserialization, if any, is implemented using safe serialization APIs.

6.8.1. Object Serialization in Android

There are several ways to persist an object on Android:

Reference

6.8.1.1. 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.

Reference

6.8.1.2. 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"}

Reference

Rulebook

6.8.1.3. 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.

Reference

Rulebook

6.8.1.4. 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.

Reference

6.8.1.5. 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.

Reference

Rulebook

6.8.1.6. 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.

Reference

Rulebook

6.8.2. Static Analysis

If object persistence is used for storing sensitive information on the device, first make sure that the information is encrypted and signed/HMACed. See the chapters “Data Storage on Android” and “Android Cryptographic APIs” for more details. Next, make sure that the decryption and verification keys are obtainable only after the user has been authenticated. Security checks should be carried out at the correct positions, as defined in best practices.

There are a few generic remediation steps that you can always take:

  1. Make sure that sensitive data has been encrypted and HMACed/signed after serialization/persistence. Evaluate the signature or HMAC before you use the data. See the chapter “Android Cryptographic APIs” for more details.

  2. Make sure that the keys used in step 1 can’t be extracted easily. The user and/or application instance should be properly authenticated/authorized to obtain the keys. See the chapter “Data Storage on Android” for more details.

  3. Make sure that the data within the de-serialized object is carefully validated before it is actively used (e.g., no exploit of business/application logic).

For high-risk applications that focus on availability, we recommend that you use Serializable only when the serialized classes are stable. Second, we recommend not using reflection-based persistence because

  • the attacker could find the method’s signature via the String-based argument

  • the attacker might be able to manipulate the reflection-based steps to execute business logic.

See the chapter “Android Anti-Reversing Defenses” for more details.

Reference

Rulebook

6.8.2.1. Object Serialization

Search the source code for the following keywords:

  • import java.io.Serializable

  • implements Serializable

Reference

6.8.2.2. JSON

If you need to counter memory-dumping, make sure that very sensitive information is not stored in the JSON format because you can’t guarantee prevention of anti-memory dumping techniques with the standard libraries. You can check for the following keywords in the corresponding libraries:

JSONObject Search the source code for the following keywords:

  • import org.json.JSONObject;

  • import org.json.JSONArray;

GSON Search the source code for the following keywords:

  • import com.google.gson

  • import com.google.gson.annotations

  • import com.google.gson.reflect

  • import com.google.gson.stream

  • new Gson();

  • Annotations such as @Expose, @JsonAdapter, @SerializedName,@Since, and @Until

Jackson Search the source code for the following keywords:

  • import com.fasterxml.jackson.core

  • import org.codehaus.jackson for the older version.

Reference

Rulebook

6.8.2.3. ORM

When you use an ORM library, make sure that the data is stored in an encrypted database and the class representations are individually encrypted before storing it. See the chapters “Data Storage on Android” and “Android Cryptographic APIs” for more details. You can check for the following keywords in the corresponding libraries:

OrmLite Search the source code for the following keywords:

  • import com.j256.*

  • import com.j256.dao

  • import com.j256.db

  • import com.j256.stmt

  • import com.j256.table\

Please make sure that logging is disabled.

SugarORM Search the source code for the following keywords:

  • import com.github.satyan

  • extends SugarRecord<Type>

  • In the AndroidManifest, there will be meta-data entries with values such as DATABASE, VERSION, QUERY_LOG and DOMAIN_PACKAGE_NAME.

Make sure that QUERY_LOG is set to false.

GreenDAO Search the source code for the following keywords:

  • import org.greenrobot.greendao.annotation.Convert

  • import org.greenrobot.greendao.annotation.Entity

  • import org.greenrobot.greendao.annotation.Generated

  • import org.greenrobot.greendao.annotation.Id

  • import org.greenrobot.greendao.annotation.Index

  • import org.greenrobot.greendao.annotation.NotNull

  • import org.greenrobot.greendao.annotation.*

  • import org.greenrobot.greendao.database.Database

  • import org.greenrobot.greendao.query.Query

ActiveAndroid Search the source code for the following keywords:

  • ActiveAndroid.initialize (<contextReference>);

  • import com.activeandroid.Configuration

  • import com.activeandroid.query.*

Realm Search the source code for the following keywords:

  • import io.realm.RealmObject;

  • import io.realm.annotations.PrimaryKey;

Reference

Rulebook

6.8.2.4. Parcelable

Make sure that appropriate security measures are taken when sensitive information is stored in an Intent via a Bundle that contains a Parcelable. Use explicit Intents and verify proper additional security controls when using application-level IPC (e.g., signature verification, intent-permissions, crypto).

Reference

Rulebook

6.8.3. Dynamic Analysis

There are several ways to perform dynamic analysis:

  1. For the actual persistence: Use the techniques described in the data storage chapter.

  2. For reflection-based approaches: Use Xposed to hook into the deserialization methods or add unprocessable information to the serialized objects to see how they are handled (e.g., whether the application crashes or extra information can be extracted by enriching the objects).

Reference

6.8.4. Rulebook

  1. When serializing in JSON/XML format, implement and store additional protections for information that should not be confidential or altered (Required)

  2. Do not store data on disk via Parcelable (Recommended)

  3. Avoid serialization in Protocol Buffers (Recommended)

  4. If object persistence is used to store sensitive information on the device, the information is encrypted and signed/HMAC (Required)

  5. For high-risk applications that care about availability, use Serializable only if the serialized classes are stable (Recommended)

  6. Do not store very important information in JSON format (Required)

  7. Notes on serialization in ORM (Required)

  8. Take appropriate security measures when sensitive information is stored in an Intent via a Bundle containing a Parcelable (Required)

6.8.4.1. When serializing in JSON/XML format, implement and store additional protections for information that should not be confidential or altered (Required)

JSON/XML data can be stored anywhere. Therefore, due to the risk of access to files stored in external storage, it is necessary to consider the storage location and encryption when saving information that should not be secret or changed.

See the rule below for how to save data to internal storage and a sample code to save data with encryption.

Rulebook

If this is violated, the following may occur.

  • Files stored in external storage may be accessed and leaked.

6.8.4.4. If object persistence is used to store sensitive information on the device, the information is encrypted and signed/HMAC (Required)

If object persistence is used for storing sensitive information on the device, first make sure that the information is encrypted and signed/HMACed. If not, perform the following general remediation steps.

  1. Make sure that sensitive data has been encrypted and HMACed/signed after serialization/persistence. Evaluate the signature or HMAC before you use the data. See the chapter “Android Cryptographic APIs” for more details.

  2. Make sure that the keys used in step 1 can’t be extracted easily. The user and/or application instance should be properly authenticated/authorized to obtain the keys. See the chapter “Data Storage on Android” for more details.

  3. Make sure that the data within the de-serialized object is carefully validated before it is actively used (e.g., no exploit of business/application logic).

If this is violated, the following may occur.

  • Sensitive data stored by object serialization can be read by a third party.

6.8.4.6. Do not store very important information in JSON format (Required)

When memory dump countermeasures are required, very important information is not stored in JSON format because the standard library cannot guarantee the prevention of memory dump countermeasure methods. The following keywords can be checked in the corresponding library.

JSONObject Search the source code for the following keywords:

  • import org.json.JSONObject;

  • import org.json.JSONArray;

GSON Search the source code for the following keywords:

  • import com.google.gson

  • import com.google.gson.annotations

  • import com.google.gson.reflect

  • import com.google.gson.stream

  • new Gson();

  • Annotations such as @Expose, @JsonAdapter, @SerializedName,@Since, and @Until

Jackson Search the source code for the following keywords:

  • import com.fasterxml.jackson.core

  • import org.codehaus.jackson for the older version.

* No sample code due to deprecated rules.

If this is violated, the following may occur.

  • A memory dump can leak information.

6.8.4.7. Notes on serialization in ORM (Required)

Note the following when serializing in ORM.

  • When you use an ORM library, make sure that the data is stored in an encrypted database and the class representations are individually encrypted before storing it. See “SQLite” for sample code for encrypted databases.

  • Please make sure that logging is disabled.

If this is violated, the following may occur.

  • A third party may read the stored confidential data.

  • A third party may be able to read confidential data from the contents of the log.

6.8.4.8. Take appropriate security measures when sensitive information is stored in an Intent via a Bundle containing a Parcelable (Required)

Make sure that appropriate security measures are taken when sensitive information is stored in an Intent via a Bundle that contains a Parcelable. Use explicit Intents and verify proper additional security controls when using application-level IPC (e.g., signature verification, intent-permissions, crypto).

For security measures and sample code when using IPC mechanisms and Intents, see “Use IPC mechanism for security considerations (Required)”.

If this is violated, the following may occur.

  • Sensitive data can be read by other apps through PC mechanisms.