应用开发中经常会有从web端启动App的需求,这里总结了常见的几种方式。
Android
0x0 自定义scheme
Android上常用的是通过scheme协议的方式唤醒本地app客户端。即App在AndroidManifest.xml中为要打开的Activity注册一个intent-filter,添加scheme,并注明host和data等关键字段,前端保持一致后就可以实现在Web页中调起本地App了。
注册scheme的代码如下:1
2
3
4
5
6
7<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<!--BROWSABLE指定该Activity能被浏览器安全调用-->
<data android:host="xyz.com" android:scheme="m4bln"/>
</intent-filter>
这里的action、category、data都必须完全匹配才能获得intent,android.intent.category.DEFAULT是默认的,有实际意义的是android.intent.category.BROWSABLE,表示允许通过浏览器启动该activity(调起App)。data限定了触发条件,当scheme为m4bln且host为xyz.com时才匹配,例如浏览器访问m4bln://xyz.com,能够匹配成功,App就起来了。
0x1 Web页打开App
通过Web页打开App有多种方式,打开方式也因直接在浏览器打开和在webview中打开而异,这里分别进行尝试:
a标签
1 | <a href="m4bln://xyz.com">打开App</a> |
location.href
1 | document.location.href="m4bln://xyz.com" |
iframe
1 | <iframe src="m4bln://xyz.com"></iframe> |
注意:
安卓chrome 浏览器version 25之后版本发生了改变。不能在通过设置iframe标签的src属性来启动app了。取而代之的是通过自定义scheme实现用户手势启动app或者使用Chrome Intent语法(参考下文)
这里我们对比在浏览器中和webview中打开页面的情况:
浏览器中打开
根据官方文档, Chrome浏览器在通过自定义Scheme唤起App的时候,需要绑定用户的点击事件触发,而在浏览器地址栏里直接输入是不可以的。
因此,当我们想要用户打开网页就直接进入App时,在Chrome浏览器地址栏里直接输是无法实现的,但可以通过其他方式实现,例如绑定所有的事件,让用户碰一下屏幕还是可以轻松做到的,参考自动写入剪贴板的实现,而通过点击url的方式则可以自动唤起App。
例如,用户点开短信中的url,此时无需用户点击便可以自动唤起App。(参考之前对于支付宝的克隆攻击Demo)
WebView中自动唤起App实现代码可能如下:
对于a标签
1
2
3
4
5
6<a id="a" href="m4bln://xyz.com">打开App</a>
<script>
window.onload=function(){
document.getElementById("a").click();
}
</script>对于location.href
1
2
3
4
5<script>
window.onload=function(){
document.location.href="m4bln://xyz.com";
}
</script>
WebView中打开
在webview中,直接通过loadurl(“m4bln://xyz.com”)是无法打开App的,需要使用shouldOverrideUrlLoading和shouldInterceptRequest进行拦截和处理。
注:
shouldOverrideUrlLoading 用于拦截页面切换
shouldInterceptRequest 拦截所有的资源请求,例如css,js,iframe
shouldOverrideUrlLoading is called when a new page is about to be opened whereas shouldInterceptRequest is called each time a resource is loaded like a css file, a js file etc
因此,对于a标签或document.location都属于页面切换类型,所以用shouldOverrideUrlLoading来实现处理1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36wv.setWebViewClient(new WebViewClient(){
/*
注: 在Android N中 shouldOverrideUrlLoading(WebView view, String url)被废弃,改为shouldOverrideUrlLoading(WebView view, WebResourceRequest request),通过request.getUrl()来获取url。
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if(你想在当前页面打开新连接){
view.loadUrl(url);
}else if(在一个新的activity中打开新连接){
Intent intent = new Intent(mContext, NewWebViewActivity.class);
intent.putputExtra("URL", url);
startActivity(intent);
}
return true;
}
*/
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
String url="";
try{
url = request.getUrl().toString();
if(url.startsWith("m4bln://")){
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
return true;
}
}catch (Exception e){
return false;
}
view.loadUrl(url);
return true;
}
});
而对于iframe,上文中虽然提到在浏览器中无法达到目的,但在webview中可以通过shouldInterceptRequest来实现。
1 | /* |
webview完成上述设置后,使用webview加载web页面,均可以实现一启动便自动唤起对应的App。
需要注意的是:
不是使用webview.loadurl(“m4bln://xyz.com”)直接加载scheme,loadurl加载的是和上述浏览器部分相同的web页。
如果webview没有作相应的设置,是无法再通过scheme打开其他App的
有人曾经想过利用自定义scheme实现App之间的一个循环启动,实现loop还得取决于App中的webview是如何配置的
App的webview里利用自己的scheme唤起自己确实会导致死循环
0x2 Chrome Intent
为了更有序的打通浏览器页面和本地应用推出了Chrome Intent机制,作为标准协议进行推广,其格式如下:1
2
3
4
5
6
7
8
9intent:
HOST/URI-path // Optional host
#Intent;
package=[string];
action=[string];
category=[string];
component=[string];
scheme=[string];
end;
当打开本地App失败的时候,还可以使用如下代码重定向到一个新页面,例如App下载页等。1
S.browser_fallback_url=[encoded_full_url]
S.
1 | intent://foobar/#Intent;action=myaction;type=text/plain;S.xyz=123;i.abc=678;end |
对于上述例子,我们构造intent为:1
intent://xyz.com#Intent;scheme=m4bln;package=com.m4bln.webviewgps;S.browser_fallback_url=https%3A%2F%2Fwww.baidu.com;end
需要注意的是:
package、action等字段非必须,取决于应用的AndroidMenifest.xml配置,上述例子中使用”intent://xyz.com#Intent;scheme=m4bln;end”也可以成功唤起App.(参照Android源码中对intent://的解析 http://androidxref.com/7.1.2_r36/xref/frameworks/base/core/java/android/content/Intent.java#5044)
除了intent://,intent:,android-app://可以达到同样效果,同样参照源码
仅有Chrome支持这种方式,部分国产浏览器屏蔽了intent://这种scheme
Chrome Intent同样需要用户点击事件, “#Intent”中的“I”必须为大写!!!
Chrome Intent在webview里不支持,同样需要shouldInterceptRequest等来处理
App获取Web传参
1 | @Override |
0x3 App监听本地特定端口
参照WormWhole漏洞(http://www.freebuf.com/vuls/83789.html)
iOS
0x0 自定义Scheme
在Info.plist中配置Scheme
1 | <key>CFBundleURLTypes</key> |
0x1 Universal links
Universal links为iOS9上一个所谓通用链接的深层链接特性,一种能够方便的通过传统HTTP链接来启动app,使用相同的网址打开网站和app;通过唯一的网址,就可以链接一个特定的视图到你的app里面,不需要特别的scheme。
- 通过scheme启动app时,浏览器会弹出确认框提示用户是否打开,Universal links不会提示,体验更好;
- Universal links可在微信浏览器中打开外部app
参考资料: