What is a deep link?

What are deep links? Vulnerabilities, attacks and security best practices

Deep links are predefined URIs (Uniform Resource Identifiers) that allow direct access to an activity in a web or mobile application when clicked.

These links are usually found on pages within a web application or in the webviews of a mobile application. When the user clicks on a deep link, and has the application to open that type of link, a popup suggests opening the link with the corresponding application.

Use case:

The user accesses a link on a web page from the phone’s browser. For example, let’s take the following link: https://deeplink.example/show

If an application on the user’s phone is configured to open links with the domain “deeplink.example” and the “https” protocol, then when the user clicks on that link, he or she will see the following screen indicating that he or she can open the link with multiple applications:

deep link use case

Here, three applications are able to open links of the type: https://deeplink.example/show

Note that if only one application is able to open a specific link, the previous choice screen does not appear and the application able to open the link is launched directly.

If the user selects the “my deeplink app” application, they will be directly redirected to an activity in the application. In our case, it is the “DeepLinkExample1” activity that has been configured to open this type of link. It only indicates that the link in question has been opened:

We will see that misuse of deep links can lead to significant vulnerabilities. This is why we recommend not using them in your applications. But before that, let’s take a closer look at deep links.

How do deep links work?

For an application to open a deep link, it is necessary to create an “Intent filter” associated with an activity. As a reminder, an intention or intent is an element that will allow an activity to perform an action following the reception of data.

In the case of deep links, it is necessary to tell an activity that when a certain type of link is opened on the phone, the activity concerned must be able to respond and potentially perform an action with that link. This will allow the phone to offer an application for opening a certain type of URI as we saw in the previous section.

The definition of an intent filter is done in the “AndroidManifest.xml” file of the application. It is important to note that an activity which can open a deep link must necessarily be exported. The android:exported="true" attribute must therefore be systematically added to the activity.

Below is an example of a deep link definition:

<activity
    android:name=".DeepLinkExample1"
    android:exported="true">
    <intent-filter android:label="my deeplink app">
        <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:host="deeplink.example" />
    </intent-filter>
</activity>

In this example, it is indicated that the activity “DeepLinkExample1” has an intent filter that allows it to open URIs with the domain “deeplink.example” and the scheme “https”. For example, our activity can be used to open the following links:

https://deeplink.example
https://deeplink.example/hello_world

It is the data tag of the intent filter that allows you to configure the type of links that can be opened by the activity.

In this example, we have a classic URL, but the advantage of deep links is that they can open any type of URI. Here is another example:

<activity
    android:name=".DeepLinkActivity"
    android:exported="true"
    android:label="DeepLink">
    <intent-filter android:label="filter_view_example_vaadata">
        <action android:name="android.intent.action.VIEW" />

        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />

        <data android:scheme="vaadata" />
        <data android:host="test" />
        <data android:path="/hello"/>
    </intent-filter>
</activity>

Here, the “DeepLinkActivity” will be able to open links of type vaadata://test/hello, vaadata://test/hello?test=1 or vaadata://test/hello?a=1&test=coucou.

As you can see, it is possible to define any type of URI, which only your application will be able to understand and open.

To detect deep links during a mobile application penetration test, it is therefore necessary to decompile the APK and read the AndroidManifest.xml file to detect the presence of deep links.

What are the common vulnerabilities of deep links and the attacks exploiting them?

As with any mechanism for interacting directly with the mobile application, deep links can be a potential entry point for conducting attacks against users.

Two attack scenarios are possible depending on how the deep links are used:

  • The deep links set up by the application developers are used to transfer sensitive data. The attack therefore consists of trying to steal this data.
  • The parameters passed in the deep links are used insecurely in the application code. The idea here is to understand how deep links are used to exploit more traditional vulnerabilities such as XSS, IDOR, CSRF bypass, etc.

Transfer of sensitive data through deep links

In this case, the risk lies in the fact that sensitive information is transmitted to a mobile application via a deep link. It should be borne in mind that anyone can create a malicious application capable of opening the same deep links as the legitimate application. If the attacker manages to get his victim(s) to install ( through social engineering for example) his malicious application and the users use the malicious application to open the deep links containing the sensitive data, then the attacker will be able to exfiltrate the data.

Although uncommon, the potential impact of this attack scenario can be very high and lead to the theft of user accounts.

Let’s imagine for example an organisation with a web application and users. A password reset feature is present. It allows an email to be sent to the user containing a link to reset the password. This link contains a unique token in the URL to identify the user when changing the password. In the mobile application of this organisation, an activity is able to open the link received in the email. This activity uses a webview so that the user can change their password from their phone.

The attack scenario is very simple: the attacker creates an application that opens the same type of link as those found in the forgotten password email. Through social engineering, he manages to trap one or more users. These users use the malicious application to open the forgotten password link. The malicious application exfiltrates the URL and the attacker can change the password of the targeted users.

The likelihood of exploitation remains fairly low, as social engineering is required. On the other hand, this type of vulnerability has already been detected in applications with thousands or even millions of users. The probability of an attacker managing to trap one of them is therefore much higher.

Unsafe use of parameters transmitted through deep links

In this scenario, the problem is not the type of data transmitted in the deep links, but the way the parameters transmitted in the application are used. Indeed, if the deep link is used to pass values to the application and the application uses these values directly without checking or cleaning them, this can lead to many vulnerabilities. The impact depends on how the parameter is used in the application code. Therefore, the use of deep links should be analysed at the application code level. Here are some examples:

  • If the value of a parameter passed in the deep link is used as is in a webview where JavaScript is allowed (“myWebView.settings.javaScriptEnabled = true”) this can lead to an XSS.
  • If the value of a parameter is used to perform a sensitive action, the consequences may vary depending on the action (e.g. loading a remote page in a webview based on a deep link parameter. This can lead to the creation of a malicious link that will load content controlled by the attacker)
  • If the value of a parameter is passed to the “Runtime.getRuntime().exec()” function, this may lead to an RCE.
  • In some cases, deep links can be used to bypass protections such as CSPs or protections against CSRFs present on a web application, but not present on mobile.

Let’s look at the example of an XSS through deep links. Let’s imagine an Android application with an activity defined as follows in the AdroidManifest.xml file:

<activity
    android:name=".DeepLinkXSS"
    android:exported="true">
    <intent-filter android:label="filter_view_example_xss">
        <action android:name="android.intent.action.VIEW" />

        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />

        <data android:scheme="xss" />
        <data android:host="test" />
    </intent-filter>
</activity>

This activity is therefore designed to open links of type “xss://test/”.

In the Java code of the activity we have the following code:

public class DeepLinkXSS extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_deep_link_xss);
        WebView xssdeeplink = findViewById(R.id.xss_webview);
        xssdeeplink.getSettings().setJavaScriptEnabled(true);
        xssdeeplink.setWebChromeClient(new WebChromeClient());

        if (getIntent() == null){
            finish();
        }

        xssdeeplink.loadData(getIntent().getData().getQueryParameter("name"),"text/html", "UTF-8");
    }
}

We can see that a webView is declared in the code and that JavaScript is active within this webView. When a deep link is opened, we look up the value of the “name” parameter in the deep link and use it in the WebView. The WebView simply displays the value of the “name” parameter.

A normal link looks like this:

xss://test/?name=John

If the user opens this link with the application, he will get the following result:

Now an attacker can forge the following link and pass it on to his victims via social engineering (the payload simply displays an alert):

xss://test/?name=John%3cscript%3ealert%28123%29%3c%2fscript%3e

If the victim clicks, the injected code will be executed:

The example is not very realistic, but it illustrates the principle of the attack. In all cases, the attacker must succeed in making the victim open a malicious deep link.

How to protect yourself from attacks exploiting deep links?

To avoid any exploitation related to the first attack scenario, it is advised not to use deep links but rather another existing mechanism: app links.

The functioning of app links is quite similar to deep links. In fact, app links are a specific type of deep links. They are defined in the same way in the AndroidManifest.xml file using an intent filter with a few differences:

  • An android:autoVerify=”true” field must be added to the intent filter
  • It is mandatory to use HTTP and HTTPS protocols. It is therefore no longer possible to use custom schemas such as vaadata:// or xss://.
  • An assetlinks.json file must be added on the web server corresponding to the app link domain. The domain must therefore exist and belong to you.

This is a valid configuration with app links:

<activity
    android:name=".DeepLinkExample1"
    android:exported="true">
    <intent-filter android:label="my deeplink app"
        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:host="vaadata.com" />
    </intent-filter>
</activity>

Here we indicate that our activity can open URIs of type:

https://vaadata.com/<anyhing>

The “android:autoverify=true” indicates that your application is the only one on the phone that can open this type of link if it is installed. The application will then open directly. This avoids the dialog box for opening another application.

You should also add a /.well-know/assetlinks.json file accessible without authentication in HTTPS at the URL:

https://vaadata.com/.well-known/assetlinks.json

This file is a “Digital Asset Links JSON” file. It should contain the following information:

  • Namespace: the name given to the application
  • Package_name: the mobile application identifier as defined in the “build.gradle” file
  • Sha256_cert_fingerprints : the SHA256 fingerprint of the certificate used to sign your application

Here is an example for an application:

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "DeepLink_app",
    "package_name": "com.vaadata.deeplink.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"]
  }
}]

App links work as follows: each time you open a link on your phone corresponding to an app link of one of your applications, Android will contact your web server and retrieve the assetlinks.json file. It will check that the package name and the hash of the signing certificate match that of the application on your phone. If it does, the link will be opened. As long as the web server is not compromised, only one application on your phone will be able to open a specific app link. For full implementation details, please refer to the official app link documentation.

This recommendation works for the data leakage part and to avoid that several applications can open similar deep links. In other cases, the recommendation is to be applied locally in the code depending on the use of the data transferred in the app links.

In general, deep links should never be used to pass sensitive information. It should also be borne in mind that user-controlled data (and thus URIs) should not be used for sensitive actions without first being checked and cleaned.

Author : Yoan MONTOYA – Pentester @Vaadata