标签:title ota jscon script target 对象传递 save ade rip
这篇博客主要来介绍 WebView 的相关使用方法,常见的几个漏洞,开发中可能遇到的坑和最后解决相应漏洞的源码,以及针对该源码的解析。
由于博客内容长度,这次将分为上下两篇,上篇详解 WebView 的使用,下篇讲述 WebView 的漏洞和坑,以及修复源码的解析。
下篇:android WebView详解,常见漏洞详解和安全源码(下)
转载请注明出处:http://blog.csdn.net/self_study/article/details/54928371。
对技术感兴趣的同鞋加群 544645972 一起交流。
现在市面上的 APP 根据类型大致可以分为 3 类:Native APP、Web APP 和 Hybrid APP,而 Hybrid APP 兼具 “Native APP 良好用户交互体验的优势”和 “Web APP 跨平台开发的优势”,现在很多的主流应用也是使用 Hybrid 模式开发的。
为什么要使用 Hybrid 开发呢,这就要提到 native 开发的限制:
1.客户端发板周期长
众所周知,客户端的发板周期在正常情况下比较长,就算是创业公司的迭代也在一到两个星期一次,大公司的迭代周期一般都在月这个数量级别上,而且 Android 还好,iOS 的审核就算变短了也有几天,而且可能会有审核不通过的意外情况出现,所谓为了应对业务的快速发展,很多业务比如一些活动页面就可以使用 H5 来进行开发。
2.客户端大小体积受限
如果所有的东西都使用 native 开发,比如上面提到的活动页面,就会造成大量的资源文件要加入到 APK 中,这就造成 APK 大小增加,而且有的活动页面更新很快,造成资源文件可能只会使用一个版本,如果不及时清理,就会造成资源文件的残留。
3.web 页面的体验问题
使用纯 Web 开发,比以前迭代快速很多,但是从某种程度上来说,还是不如原生页面的交互体验好;
4.无法跨平台
一般情况下,同一样的页面在 android 和 iOS 上需要写两份不同的代码,但是现在只需要写一份即可,Hybrid 具有跨平台的优势。
所以综上这两种方式单独处理都不是特别好,考虑到发版周期不定,而且体验交互上也不能很差,所以就把两种方式综合起来,让终端和前端共同开发一个 APP,这样一些迭代很稳定的页面就可以使用原生,增加体验性;一些迭代很快速的页面就可以使用 H5,让两种优点结合起来,弥补原来单个开发模式的缺点。
H5 和 Native 的体验差距主要在两个方面:
1.页面渲染瓶颈
第一个是前端页面代码渲染,受限于 JS 的解析效率,以及手机硬件设备的一些性能,所以从这个角度来说,我们应用开发者是很难从根本上解决这个问题的;
2.资源加载缓慢
第二个方面是 H5 页面是从服务器上下发的,客户端的页面在内存里面,在页面加载时间上面,根据网络状况的不同,H5 页面的体验和 Native 在很多情况下相比差距还是不小的,但是这种问题从某种程度上来说也是可以弥补的,比如说我们可以做一些资源预加载的方案,在资源预加载方面,其实也有很多种方式,下面主要列举了一些:
我们来看看 Google 官网关于 WebView 的介绍:
A View that displays web pages. This class is the basis upon which you can roll your own web browser
or simply display some online content within your Activity. It uses the WebKit rendering engine
to display web pages and includes methods to navigate forward and backward through a history,
zoom in and out, perform text searches and more.
可以看到 WebView 是一个显示网页的控件,并且可以简单的显示一些在线的内容,并且基于 WebKit 内核,在 Android4.4(API Level 19) 引入了一个基于 Chromium 的新版本 WebView ,这让我们的 WebView 能支持 HTML5 和 CSS3 以及 Javascript,有一点需要注意的是由于 WebView 的升级,对于我们的程序也带来了一些影响,如果我们的 targetSdkVersion 设置的是 18 或者更低, single and narrow column 和 default zoom levels 不再支持。Android4.4 之后有一个特别方便的地方是可以通过 setWebContentDebuggingEnabled() 方法让我们的程序可以进行远程桌面调试。
WebView 有四个用来加载页面的方法:
使用 WebView 的时候,一般都会对其进行一些设置,我们来看看常见的设置:
WebSettings webSettings = webView.getSettings();
//设置了这个属性后我们才能在 WebView 里与我们的 Js 代码进行交互,对于 WebApp 是非常重要的,默认是 false,
//因此我们需要设置为 true,这个本身会有漏洞,具体的下面我会讲到
webSettings.setJavaScriptEnabled(true);
//设置 JS 是否可以打开 WebView 新窗口
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
//WebView 是否支持多窗口,如果设置为 true,需要重写
//WebChromeClient#onCreateWindow(WebView, boolean, boolean, Message) 函数,默认为 false
webSettings.setSupportMultipleWindows(true);
//这个属性用来设置 WebView 是否能够加载图片资源,需要注意的是,这个方法会控制所有图片,包括那些使用 data URI 协议嵌入
//的图片。使用 setBlockNetworkImage(boolean) 方法来控制仅仅加载使用网络 URI 协议的图片。需要提到的一点是如果这
//个设置从 false 变为 true 之后,所有被内容引用的正在显示的 WebView 图片资源都会自动加载,该标识默认值为 true。
webSettings.setLoadsImagesAutomatically(false);
//标识是否加载网络上的图片(使用 http 或者 https 域名的资源),需要注意的是如果 getLoadsImagesAutomatically()
//不返回 true,这个标识将没有作用。这个标识和上面的标识会互相影响。
webSettings.setBlockNetworkImage(true);
//显示WebView提供的缩放控件
webSettings.setDisplayZoomControls(true);
webSettings.setBuiltInZoomControls(true);
//设置是否启动 WebView API,默认值为 false
webSettings.setDatabaseEnabled(true);
//打开 WebView 的 storage 功能,这样 JS 的 localStorage,sessionStorage 对象才可以使用
webSettings.setDomStorageEnabled(true);
//打开 WebView 的 LBS 功能,这样 JS 的 geolocation 对象才可以使用
webSettings.setGeolocationEnabled(true);
webSettings.setGeolocationDatabasePath("");
//设置是否打开 WebView 表单数据的保存功能
webSettings.setSaveFormData(true);
//设置 WebView 的默认 userAgent 字符串
webSettings.setUserAgentString("");
//设置是否 WebView 支持 “viewport” 的 HTML meta tag,这个标识是用来屏幕自适应的,当这个标识设置为 false 时,
//页面布局的宽度被一直设置为 CSS 中控制的 WebView 的宽度;如果设置为 true 并且页面含有 viewport meta tag,那么
//被这个 tag 声明的宽度将会被使用,如果页面没有这个 tag 或者没有提供一个宽度,那么一个宽型 viewport 将会被使用。
webSettings.setUseWideViewPort(false);
//设置 WebView 的字体,可以通过这个函数,改变 WebView 的字体,默认字体为 "sans-serif"
webSettings.setStandardFontFamily("");
//设置 WebView 字体的大小,默认大小为 16
webSettings.setDefaultFontSize(20);
//设置 WebView 支持的最小字体大小,默认为 8
webSettings.setMinimumFontSize(12);
//设置页面是否支持缩放
webSettings.setSupportZoom(true);
//设置文本的缩放倍数,默认为 100
webSettings.setTextZoom(2);
然后还有最常用的 WebViewClient 和 WebChromeClient,WebViewClient主要辅助WebView执行处理各种响应请求事件的,比如:
mWebView.clearCache(true);
;清空历史记录mWebview.clearHistory();
,这个方法要在 onPageFinished()
的方法之后调用。
使用 Hybrid 开发的 APP 基本都需要 Native 和 web 页面的 JS 进行交互,下面介绍一下交互的方式。
如何让 web 页面调用 native 的代码呢,有三种方式:
第一种方式:通过 addJavascriptInterface 方法进行添加对象映射
这种是使用最多的方式了,首先第一步我们需要设置一个属性:
mWebView.getSettings().setJavaScriptEnabled(true);
这个函数会有一个警告,因为在特定的版本之下会有非常危险的漏洞,我们下面将会着重介绍到,设置完这个属性之后,Native 需要定义一个类:
public class JSObject {
private Context mContext;
public JSObject(Context context) {
mContext = context;
}
@JavascriptInterface
public String showToast(String text) {
Toast.show(mContext, text, Toast.LENGTH_SHORT).show();
return "success";
}
}
...
//特定版本下会存在漏洞
mWebView.addJavascriptInterface(new JSObject(this), "myObj");
需要注意的是在 API17 版本之后,需要在被调用的地方加上 @addJavascriptInterface 约束注解,因为不加上注解的方法是没有办法被调用的,JS 代码也很简单:
function showToast(){
var result = myObj.showToast("我是来自web的Toast");
}
可以看到,这种方式的好处在于使用简单明了,本地和 JS 的约定也很简单,就是对象名称和方法名称约定好即可,缺点就是下面要提到的漏洞问题。
第二种方式:利用 WebViewClient 接口回调方法拦截 url
这种方式其实实现也很简单,使用的频次也很高,上面我们介绍到了 WebViewClient ,其中有个回调接口 shouldOverrideUrlLoading (WebView view, String url) ,我们就是利用这个拦截 url,然后解析这个 url 的协议,如果发现是我们预先约定好的协议就开始解析参数,执行相应的逻辑,我们先来看看这个函数的介绍:
Give the host application a chance to take over the control when a new url is about to be loaded in
the current WebView. If WebViewClient is not provided, by default WebView will ask Activity Manager
to choose the proper handler for the url. If WebViewClient is provided, return true means the host
application handles the url, while return false means the current WebView handles the url. This
method is not called for requests using the POST "method".
注意这个方法在 API24 版本已经废弃了,需要使用 shouldOverrideUrlLoading (WebView view, WebResourceRequest request) 替代,使用方法很类似,我们这里就使用 shouldOverrideUrlLoading (WebView view, String url) 方法来介绍一下:
public boolean shouldOverrideUrlLoading(WebView view, String url) {
//假定传入进来的 url = "js://openActivity?arg1=111&arg2=222",代表需要打开本地页面,并且带入相应的参数
Uri uri = Uri.parse(url);
String scheme = uri.getScheme();
//如果 scheme 为 js,代表为预先约定的 js 协议
if (scheme.equals("js")) {
//如果 authority 为 openActivity,代表 web 需要打开一个本地的页面
if (uri.getAuthority().equals("openActivity")) {
//解析 web 页面带过来的相关参数
HashMap<String, String> params = new HashMap<>();
Set<String> collection = uri.getQueryParameterNames();
for (String name : collection) {
params.put(name, uri.getQueryParameter(name));
}
Intent intent = new Intent(getContext(), MainActivity.class);
intent.putExtra("params", params);
getContext().startActivity(intent);
}
//代表应用内部处理完成
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
代码很简单,这个方法可以拦截 WebView 中加载 url 的过程,得到对应的 url,我们就可以通过这个方法,与网页约定好一个协议,如果匹配,执行相应操作,我们看一下 JS 的代码:
function openActivity(){
document.location = "js://openActivity?arg1=111&arg2=222";
}
这个代码执行之后,就会触发本地的 shouldOverrideUrlLoading 方法,然后进行参数解析,调用指定方法。这个方式不会存在第一种提到的漏洞问题,但是它也有一个很繁琐的地方是,如果 web 端想要得到方法的返回值,只能通过 WebView 的 loadUrl 方法去执行 JS 方法把返回值传递回去,相关的代码如下:
//java
mWebView.loadUrl("javascript:returnResult(" + result + ")");
//javascript
function returnResult(result){
alert("result is" + result);
}
所以说第二种方式在返回值方面还是很繁琐的,但是在不需要返回值的情况下,比如打开 Native 页面,还是很合适的,制定好相应的协议,就能够让 web 端具有打开所有本地页面的能力了。
第三种方式:利用 WebChromeClient 回调接口的三个方法拦截消息
这个方法的原理和第二种方式原理一样,都是拦截相关接口,只是拦截的接口不一样:
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
return super.onJsAlert(view, url, message, result);
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
return super.onJsConfirm(view, url, message, result);
}
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
//假定传入进来的 message = "js://openActivity?arg1=111&arg2=222",代表需要打开本地页面,并且带入相应的参数
Uri uri = Uri.parse(message);
String scheme = uri.getScheme();
if (scheme.equals("js")) {
if (uri.getAuthority().equals("openActivity")) {
HashMap<String, String> params = new HashMap<>();
Set<String> collection = uri.getQueryParameterNames();
for (String name : collection) {
params.put(name, uri.getQueryParameter(name));
}
Intent intent = new Intent(getContext(), MainActivity.class);
intent.putExtra("params", params);
getContext().startActivity(intent);
//代表应用内部处理完成
result.confirm("success");
}
return true;
}
return super.onJsPrompt(view, url, message, defaultValue, result);
}
和 WebViewClient 一样,这次添加的是 WebChromeClient 接口,可以拦截 JS 中的几个提示方法,也就是几种样式的对话框,在 JS 中有三个常用的对话框方法:function clickprompt(){
var result=prompt("js://openActivity?arg1=111&arg2=222");
alert("open activity " + result);
}
这里需要注意的是 prompt 里面的内容是通过 message 传递过来的,并不是第二个参数的 url,返回值是通过 JsPromptResult 对象传递。为什么要拦截 onJsPrompt 方法,而不是拦截其他的两个方法,这个从某种意义上来说都是可行的,但是如果需要返回值给 web 端的话就不行了,因为 onJsAlert 是不能返回值的,而 onJsConfirm 只能够返回确定或者取消两个值,只有 onJsPrompt 方法是可以返回字符串类型的值,操作最全面方便。
以上三种方案的总结和对比
以上三种方案都是可行的,在这里总结一下
第一种方式
native 调用 js 的方法上面已经介绍到了,方法为:
//java
mWebView.loadUrl("javascript:show(" + result + ")");
//javascript
<script type="text/javascript">
function show(result){
alert("result"=result);
return "success";
}
</script>
需要注意的是名字一定要对应上,要不然是调用不成功的,而且还有一点是 JS 的调用一定要在 onPageFinished 函数回调之后才能调用,要不然也是会失败的。
第二种方式
如果现在有需求,我们要得到一个 Native 调用 Web 的回调怎么办,Google 在 Android4.4 为我们新增加了一个新方法,这个方法比 loadUrl 方法更加方便简洁,而且比 loadUrl 效率更高,因为 loadUrl 的执行会造成页面刷新一次,这个方法不会,因为这个方法是在 4.4 版本才引入的,所以我们使用的时候需要添加版本的判断:
final int version = Build.VERSION.SDK_INT;
if (version < 18) {
mWebView.loadUrl(jsStr);
} else {
mWebView.evaluateJavascript(jsStr, new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
//此处为 js 返回的结果
}
});
}
两种方式的对比
一般最常使用的就是第一种方法,但是第一种方法获取返回的值比较麻烦,而第二种方法由于是在 4.4 版本引入的,所以局限性比较大。
常见漏洞和坑请看下篇博客:android WebView详解,常见漏洞详解和安全源码(下)
源码解析请看下篇博客:android WebView详解,常见漏洞详解和安全源码(下)
下载源码:https://github.com/zhaozepeng/SafeWebView;参考自:https://github.com/yushiwo/WebViewBugDemo,在此基础上做了一些优化。
转载请注明出处:http://blog.csdn.net/self_study/article/details/54928371。
http://group.jobbole.com/26417/?utm_source=android.jobbole.com&utm_medium=sidebar-group-topic
http://blog.csdn.net/jiangwei0910410003/article/details/52687530
http://blog.csdn.net/leehong2005/article/details/11808557
https://github.com/yushiwo/WebViewBugDemo/blob/master/src/com/lee/webviewbug/WebViewEx.java
http://blog.csdn.net/sk719887916/article/details/52402470
https://zhuanlan.zhihu.com/p/24202408
https://github.com/lzyzsd/JsBridge
http://www.jianshu.com/p/93cea79a2443#
http://www.codexiu.cn/android/blog/33214/
https://github.com/pedant/safe-java-js-webview-bridge
http://blog.sina.com.cn/s/blog_777f9dbb0102v8by.html
http://www.cnblogs.com/chaoyuehedy/p/5556557.html
http://blogs.360.cn/360mobile/2014/09/22/webview%E8%B7%A8%E6%BA%90%E6%94%BB%E5%87%BB%E5%88%86%E6%9E%90/
https://my.oschina.net/zhibuji/blog/100580
http://www.cnblogs.com/punkisnotdead/p/5062631.html?utm_source=tuicool&utm_medium=referral
标签:title ota jscon script target 对象传递 save ade rip
原文地址:http://blog.csdn.net/self_study/article/details/54928371