美文网首页
Android WebView 学习

Android WebView 学习

作者: BKQ_SYC | 来源:发表于2019-01-05 11:18 被阅读21次
目录

第一部分
常用方法整理
    WebView常用方法
    WebView周边相关类
    WebView缓存机制类型
    Android与JS交互

第二部分
WebView漏洞
    任意代码执行漏洞
        addJavascriptInterface远程代码执行漏洞
        内置对象searchBoxJavaBridge_、accessibility、accessibilityTraversal导出
    密码明文存储漏洞
    域控制不严格漏洞
        setAllowFileAccess()
        setAllowFileAccessFromFileURLs()
        setAllowUniversalAccessFromFileURLs()
        setJavaScriptEnabled()

第一部分:常用方法整理

  • WebView常用方法
  • WebView周边相关类
  • WebView缓存机制类型
  • Android与JS交互

常用方法

// 页面获取焦点,webview正常加载显示网页,仅对当前webview有效
onResume()

// 页面失去焦点时
// 内核暂停所有操作,例如DOM解析、plugin解析、JavaScript执行,仅对当前webview有效
onPause()

// 与onResume、onPause方法结合使用,作用效果基本相同,
// 但,以下方法作用于全局所有webview控件
resumeTimers()
pauseTimers()

// 销毁WebView
// 注:调用destroy时,webview仍绑定在页面上
// 因此,需要先从父容器中移除,再进行销毁
parentView.removeView(webview)
webview.destroy()


// 是否可以后退
Webview.canGoBack() 
// 后退网页
Webview.goBack()

// 是否可以前进                     
Webview.canGoForward()
// 前进网页
Webview.goForward()

// 以当前的index为起始点前进或者后退到历史记录中指定的steps
// 如果steps为负数则为后退,正数则为前进
Webview.goBackOrForward(intsteps) 

public boolean onKeyDown(int keyCode, KeyEvent event) {
      if ((keyCode == KEYCODE_BACK) && webview.canGoBack()) {
            webview.goBack()
            return true;
      }
      return super.onKeyDown(keyCode, event)
}

// 清除网页访问留下的缓存
// 由于内核缓存是全局的,
// 注:因此,该方法清除的是整个应用的网页缓存
clearCache(true)

// 清除当前webview访问的历史记录
// 注:清除访问历史中的记录,不包括当前访问记录
clearHistory()

// 仅仅清除自动完成填充的表单数据,
// 不会清除webview存储到本地的数据
clearFormData()

周边相关类

  1. WebSettings
// 需要js交互时,支持JavaScript
setJavaScriptEnabled(true)

// 支持网页插件
setPluginsEnabled(true)

// 设置自适应屏幕,两者结合使用
setUserWideViewPort(true)      // 将图片调整到适合webview的大小
setLoadWithOverviewMode(true)  // 缩放至屏幕大小

// 缩放操作
setSupportZoom(true)  // 缩放总开关,默认支持
setBuiltInZoomControls(true)  // 设置内资缩放控件,为false,则不可缩放
setDisplayZoomControls(false) // 隐藏原生缩放控件

setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK)  // 关闭缓存
setAllowFileAccess(true) // 设置可以访问文件
setJavaScriptCanOpenWindowsAutomatically(true) // 支持通过js打开新窗口
setLoadsImagesAutomatically(true)  // 支持自动加载图片
setDefaultTextEncodingName("utf-8") // 设置编码格式
补充:
  • 当加载html页面时,WebView会在/data/data/包名目录 下生成database与cache两个文件夹
  • 请求的URL记录保存在WebViewCache.db,而URL的内容是保存在WebViewCache文件夹下
  • 是否启用缓存
webSetting.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK)

// 不使用缓存
webSetting.setCacheMode(WebSettings.LOAD_NO_CACHE)
类型 说明
LOAD_CACHE_ONLY 只读本地缓存
LOAD_CACHE_ELSE_NETWORK 优先使用本地缓存
LOAD_DEFAULT 根据cache-control决定是否去网络数据
LOAD_NO_CACHE 只读网络数据
  1. WebViewClient
  • onPageStarted():开始载入页面时调用
  • onPageFinished():页面加载结束时调用
  • onLoadResource():加载页面资源时调用,每个资源的加载都会调用一次
  • onReceivedError():加载页面的服务器出现错误时调用
  • onReceivedSslError():处理https请求,webview默认不处理https请求,需如下设置。
webview.setWebViewClient(new WebViewClient() {
      @Override
      public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
           handler.proceed();  // 表示等待证书响应
           // handler.cancel();    // 表示挂起链接,为默认方式
           // handler.handleMessage(null);  
      }
})
  • shouldInterceptRequest(...):当向服务器访问本地已有的静态资源时进行拦截,检测到是相同的资源则用本地资源代替。
webview.setWebViewClient(new WebViewClient() {
      // 重写 webViewClient 的 shouldInterceptRequest()
      // API 21 以下用shouldInterceptRequest(WebView view, String url)
      // API 21 以上用shouldInterceptRequest(WebView view, WebResourceRequest request)

      @Override
      public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
            
            // 1. 判断拦截资源的条件 
            if (url.contains("logo.png")) {
                  // 假设网页里改图片地址为:http://abc.com.image/logo.png
                  // 图片资源名称为 logo.png
          
                  // 创建一个输入流
                  InputStream is = null
                  try {
                       // 3. 获得需要替换的资源
                       is = getApplicationContext().getAssets.open("images/abc.png")
                  } catch (IOException e) {
                      e.printStackTrace()
                  }

                  // 4. 替换资源
                  // 参数: 资源文件Content-Type, 编码类型, 资源输入流
                  WebResourceResponse response = new WebResourceResponse("image/png", "utf-8", is)
                  return response
            }
            return super.shouldInterceptRequest(view, url)
      }

      @Override
      public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
            
            // 1. 判断拦截资源的条件 
            if (request.getUrl().contains("logo.png")) {
                  // 假设网页里改图片地址为:http://abc.com.image/logo.png
                  // 图片资源名称为 logo.png
          
                  // 创建一个输入流
                  InputStream is = null
                  try {
                       // 3. 获得需要替换的资源
                       is = getApplicationContext().getAssets.open("images/abc.png")
                  } catch (IOException e) {
                      e.printStackTrace()
                  }

                  // 4. 替换资源
                  // 参数: 资源文件Content-Type, 编码类型, 资源输入流
                  WebResourceResponse response = new WebResourceResponse("image/png", "utf-8", is)
                  return response
            }
            return super.shouldInterceptRequest(view, request)
      }
})
  • shouldOverrideUrlLoading(WebView view, String url)(API 24废弃)/ shouldOverrideUrlLoading(WebView view, WebResourceRequest request):打开网页时不调用系统浏览器,而是在本webview中显示
方式一:
webview.setWebViewClient(new WebViewClient() {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            mIsRedirect = true;
            view.loadUrl(url)
            return true
        }
        ...
})

方式二:
webview.setWebViewClient(new WebViewClient() {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            mIsRedirect = true;
            return false
        }
        ...
})

公共部分:
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
    mIsRedirect = false;
    super.onPageStarted(view, url, favicon);
}

@Override
public void onPageFinished(WebView view, String url) {
    super.onPageFinished(view, url);
    if (mIsRedirect) {
       return;
    }
    //回调加载成功的函数
}

补充:
方式一方式二均可避免跳转外部浏览器
但,官方说明如下:

Note: Do not call WebView.loadUrl(String) with the same URL and then return true. 
This unnecessarily cancels the current load and starts a new load with the same URL. 
The correct way to continue loading a given URL is to simply return false, 
without calling WebView.loadUrl(String).

官方表示一般情况下没必要使用方式一。当然,方式一写法出现也是有原因的,存在即是合理。

注:希望重定向时继续显示loading效果时,必须使用方式一,即:loadUrl(url) return true。

虽然方式一、二呈现效果一样,但方式一可以保证非重定向的url不会回调shouldOverrideUrlLoading方法,从而可以通过一个开关来过滤重定向链接

举个例子:
目标url是http://www.jianshu.com,会重定向到https://www.jianshu.com

  • 执行流程对比

方式一:

-> onPageStarted(http://www.jianshu.com)
-> onPageStarted(https://www.jianshu.com)
-> shouldOverrideUrlLoading(https://www.jianshu.com)
-> onPageFinished(https://www.jianshu.com)
-> onPageStarted(https://www.jianshu.com)
-> onPageFinished(https://www.jianshu.com)

方式二

-> onPageStarted(http://www.jianshu.com)
-> onPageStarted(https://www.jianshu.com)
-> shouldOverrideUrlLoading(https://www.jianshu.com)
-> onPageFinished(https://www.jianshu.com)

结论: 重定向需继续显示loading时,方式二无法准确判断loading消失时机,此时应使用方式一;除此之外选择方式二比较合适

  1. WebChromeClient
  • onProgressChanged(WebView view, int newProgress):获得网页的加载进度并显示,newProgress最大值为100。
  • onReceivedTitle(WebView view, String title):获取Web页中的标题

缓存机制类型

webview自带5种缓存机制

  1. 浏览器 缓存
    • 根据Http协议头中的Cache-Control(或Expires)和Lase-Modified(或Etag)等字段来控制文件的有效时长和文件最后更改时间决定是否需要请求网络
    • 浏览器缓存是浏览器内核的机制 一般都是标准的实现
    • 特点:缓存文件需首次加载才会产生;且存储空间有限、可能会被清除;缓存的文件没有校验;可用来缓存静态资源文件,存储在app的data目录中;内置自动实现。
  2. Application Cache 缓存
    • 以文件单位进行缓存,且文件有一定的更新机制(类似于浏览器缓存机制)
    • 存储静态文件,是对浏览器缓存机制的补充
// 1.设置缓存路径
String cacheDirPath = context.getFilesDir().getAbsolutePath() + "cache/"
webSetting.setAppCachePath(cacheDirPath)

// 2.设置缓存大小
webSetting.setAppCacheMaxSize(20 * 1024 * 1024)

// 3.开启Application Cache存储机制
webSetting.setAppCacheEnable(true)

注:
每个Application 只调用一次 setAppCachePath()setAppCacheMaxSize()

  1. DOM Storage 缓存
  • 通过存储字符串的Key - Value对来提供

DOM Storage分为sessionStoragelocalStorage,两者使用方法基本相同,区别在于作用范围不同
1. sessionStorage:具备临时性,存储与页面相关的数据,在页面关闭后无法使用
2. localStorage:具备持久性,保存的数据在页面关闭后也可以使用

  • 特点:存储空间大(5MB),Cookies才4KB;存储安全便捷,无需经常和服务器交互,而Cookies每次请求页面都会向服务器发送网络请求;存储机制类似于SharePreference机制。
// 开启DOM Storage
setDomStorageEnabled(true)
  1. Web SQL Database 缓存(不再维护)
  • 基于SQL的数据存储机制
  • 可方便对数据进行增删改查
// 1. 设置本地缓存地址
String cacheDirPath = context.getFileDir().getAbsolutePath() + "cache/"
webSetting.setDatabasePath(cacheDirPath)

// 2. 开启数据库存储机制
webSetting.setDatabaseEnabled(true)
  1. Indexed Database 缓存
  • 通过存储字符串的Key - Value对来提供
// 只需设置支持JS就自动打开IndexDB存储机制
// Android 在4.4开始加入对IndexedDB的支持
webSetting.setJavaScriptEnabled(true)

Android与JS交互

  • Android调用JS代码
  1. loadUrl()
html代码:
<!DOCTYPE html>
<html>
      <head>
            <meta charset="utf-8">

             // JS代码
             <script>
                   // Android需要调用的方法
                   function callJS() {
                          alert("Android调用了JS的callJS方法");
                   }
             </script>
      </head>
</html>

Android代码:
// 调用JS方法名需一致 callJS
webview.loadUrl("javascript:callJS()")

注:JS代码必须在onPageFinished()回调之后调用

  1. evaluateJavascript()
webview.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
       @Override
       public void onReceiverValue(String value) {
           // js返回的结果
       }
})

注:高效,页面不会刷新,可轻松获得返回值;Android 4.4后可用

  • JS调用Android代码
  1. 通过addJavascriptInterface()进行对象映射
html代码
<!DOCTYPE html>
<html>
      <head>
            <meta charset="utf-8">

             // JS代码
             <script>
                   // Android需要调用的方法
                   function callAndroid() {
                          test.callWeb("JS调用了Android的callAndroid方法");
                   }
             </script>
      </head>
</html>

Android代码
public class AndroidtoJs extrends Object {
       // 定义JS需要调用的方法,且方法名与html中一致
       // 被JS调用的方法必须加入@JavascriptInterface注解
       @JavascriptInterface
       public void callWeb(String msg) {
              // msg 值为 “JS调用了Android的callAndroid方法”
       }
}

调用:
// “test” 需与html约定一致
webView.addJavascriptInterface(new AndroidtoJs(), "test")

注:该方式存在严重漏洞问题,详情请看后续WebView漏洞一段。

  1. 通过WebViewClientshouldOverrideUrlLoading()方式拦截url
html代码
<!DOCTYPE html>
<html>
      <head>
            <meta charset="utf-8">

             // JS代码
             <script>
                   // Android需要调用的方法
                   function callAndroid() {
                      // 约定的url协议
                      document.location = "js://webview?arg1=111&arg2=222";
                   }
             </script>
      </head>

      <!-- 点击按钮则调用callAndroid()方法  -->
      <body>
           <button type="button" id="button1" onclick="callAndroid()">点击调用Android代码</button>
      </body>
</html>

Android代码
public class AndroidtoJs extrends WebViewClient {

       @Override
       public boolean shouldOverrideUrlLoading(WebView view, String url) {
              // 根据协议的参数,判断是否是所需的url
              // 一般根据scheme、authority判断前两个参数
              Uri uri = Uri.parse(url);
              if (uri.getScheme().equals("js") && uri.getAuthority().equals("webview")) {
                    // 执行拦截操作
                    // 也可以通过uri.getQueryParameterNames()获取参数
                    return true;
              }
              return super.shouldOverrideUrlLoading(view, url);
       }
}

另:该方法JS获取Android方法的返回值复杂,如需获取返回值,具体操作如下:
Android代码
webview.loadUrl("javascript:returnResult(" + result + ")");

html代码
function returnResult(result) {
       alert("result is " + result);
}
  1. 通过WebChromeClientonJsAlert(),onJsConfirm(),onJsPrompt()方法回调拦截JS对话框alert(),confirm(),prompt()消息
方法 作用 返回值 备注
alert() 弹出警告框 没有 在文本加入“\n”可换行
confirm() 弹出确认框 两个返回值 返回布尔值,表示点击确定/取消
prompt() 弹出输入框 任意设置返回值 点击确定返回输入框中的值,点击取消返回null
html代码
<!DOCTYPE html>
<html>
      <head>
            <meta charset="utf-8">

             // JS代码
             <script>
                   // Android需要调用的方法
                   function test() {
                      // 约定的url协议
                      var result=prompt("js://webview?arg1=111&arg2=222");
                      alert("demo " + result);
                   }
             </script>
      </head>

      <!-- 点击按钮则调用callAndroid()方法  -->
      <body>
           <button type="button" id="button1" onclick="test()">调用prompt</button>
      </body>
</html>

Android代码
public class AndroidtoJs extrends WebChromeClient {

       @Override
       public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JSPromptResult result) {
              // 根据协议的参数,判断是否是所需的url
              // 一般根据scheme、authority判断前两个参数
              Uri uri = Uri.parse(url);
              if (uri.getScheme().equals("js") && uri.getAuthority().equals("webview")) {
                    // 执行拦截操作
                    // 也可以通过uri.getQueryParameterNames()获取参数
                    result.confirm("js调用了Android的方法成功啦")
                    return true;
              }
              return super.onJsPrompt(view, url, message, defaultValue, result);
       }

       @Override
       public boolean onJsAlert(WebView view, String url, String message, JSResult result) {
              // 根据协议的参数,判断是否是所需的url
              // 一般根据scheme、authority判断前两个参数
              Uri uri = Uri.parse(url);
              if (uri.getScheme().equals("js") && uri.getAuthority().equals("webview")) {
                    // 执行拦截操作
                    // 也可以通过uri.getQueryParameterNames()获取参数
                    result.confirm()
                    return true;
              }
              return super.onJsAlert(view, url, message, result);
       }

       @Override
       public boolean onJsConfirm(WebView view, String url, String message, JSResult result) {
              // 根据协议的参数,判断是否是所需的url
              // 一般根据scheme、authority判断前两个参数
              Uri uri = Uri.parse(url);
              if (uri.getScheme().equals("js") && uri.getAuthority().equals("webview")) {
                    // 执行拦截操作
                    // 也可以通过uri.getQueryParameterNames()获取参数
                    result.confirm()
                    return true;
              }
              return super.onJsConfirm(view, url, message, result);
       }
}

三种JS调用Android方法对比

调用方式 优点 缺点 使用场景
方式一 方便简洁 4.2以下存在漏洞 4.2以上相对简单互调
方式二 无漏洞 需协议约束 不需返回值互调(ios主用)
方式三 无漏洞 需协议约束 满足大多数情况下的互调场景

注:更多复杂交互场景可参考JsBridge


第二部分:WebView漏洞

webview中,漏洞主要分为以下三类

  • 任意代码执行漏洞
  • 密码明文存储漏洞
  • 域控制不严格漏洞

任意代码执行漏洞

出现该漏洞的原因有两类:

A: WebView中的addJavascriptInterface()接口:接口引起远程代码执行漏洞
  1. 产生原因:
    JS调用Android的其中一个方式是通过addJavascriptInterface接口对象映射,如此 webview.addJavascriptInterface(new JSObject, "jsInterface")。因此,当JS拿到Android这个对象后,就可以调用这个Android对象中所有的方法,包括系统类(java.lang.Runtime类),从而进行任意代码执行。

    例如 可以执行命令获取本地设备中的文件等信息,从而造成信息泄露。
    漏洞利用过程(结合Java反射机制):可调用getClass()方法,从而通过getClass().forName加载(java.lang.Runtime类),最终通过该类执行本地命令。流程伪码大致如下:

function execute(cmdArgs)
{
      // 1. 遍历window对象,找到包含getClass()的对象,
      // 也就是上文中的《jsInterface》
      for (var obj in window) {
            if ("getClass" in window[obj]) {
                   // 2.利用反射调用forName()得到Runtime类对象
                   window[obj].getClass.forName("java.lang.Runtime")
                   // 3.调用静态方法执行一些命令,比如访问文件的命令
                   getMethod("getRuntime", null).invoke(null, null).exec(cmdArgs)
                   // 最终得到想要的数据流,如访问的文件
            }
      }
}

注:当一些APP通过扫描二维码打开一个外部网页时,攻击者就可以执行这段js代码进行漏洞攻击。

  1. 解决方案:Android 4.2之后,对被调用的函数以@JavascriptInterface进行注解
B: WebView内置导出的searchBoxJavaBridge_accessibilityaccessibilityTraversal对象
  1. 产生原因:Android 3.0以下,Android 系统会默认通过searchBoxJavaBridge_的Js接口给WebView添加一个JS映射对象,该接口可能被利用,实现远程任意代码。
  2. 解决方案:删除searchBoxJavaBridge_接口
// 通过调用改方法删除接口
removeJavascriptInterface()

密码明文存储漏洞

  1. 产生原因:WebView默认开启密码保存功能,当用户允许保存时会被明文保存到本地.../databases/webview.db中,这样就有被盗取秘密的危险。
  2. 解决方案:关闭密码保存提醒
webSettings.setSavePassword(false)

域控制不严格漏洞

  1. 产生原因:由于某应用(A)可通过设置exported属性被另一个应用(B)启动。即B应用可以通过A应用导出的Activity让B应用加载一个恶意file协议的url,从而通过一系列手段可以获取B应用的内部私有文件,从而可能导致数据泄露.

解释:当其他应用启动此Activity时,intent中的data直接被当做url来加载,其他APP通过使用显式ComponentName或者其他类似当时就可以很轻松启动该WebViewActivity并加载恶意url。

注:危险来源在于,当应用A设置可被外部应用B启动时,B可传递任意url(包含危险恶意url)让应用A去加载。
下面着重分析WebView中的getSettings类的方法对WebView安全性的影响。

  1. setAllowFileAccess()

方法解释:
// 设置是否允许WenView使用File协议
webview.getSettings().setAllowFileAccess()
// 默认设置为true,即允许在File域下执行任意JavaScript代码

注:使用File域加载的js代码能够使用进行同源策略跨域访问,从而导致隐私信息泄漏

同源策略跨域访问:对私有目录文件进行访问
针对IM类产品,泄漏的是聊天消息、联系人等等
针对浏览器类软件,泄漏的是cookie信息

a. 解决方案:
不允许使用file协议,则不会存在上述威胁,但,同时也我要发加载本地html文件。因此,需要区分对待。

1. 不需要使用file协议的应用,禁用file协议
setAllowFileAccess(false)
2. 需要使用file协议的应用,禁止file协议加载JavaScript.
setAllowFileAccess(true)
// 禁止file协议加载JavaScript
if (url.startWith("file://")) {
    setJavaScriptEnabled(false)
} else {
    setJaveScriptEnabled(true)
}
  1. setAllowFileAccessFromFileURLs()

方法解释:
// 设置是否允许通过file url加载的Js代码读取其他的本地文件
webSettings.setAllowFileAccessFromFileURLs(true)
// Android 4.1前默认允许
// Android 4.1后默认禁止

AllowFileAccessFromFileURLs()设置为true时,攻击者的JS代码如下:

// 通过以下代码可成功读取/etc/hosts 内容数据的
function loadXMLDoc()
{
    var arm = "file:///etc/hosts";
    var xmlhttp;
    if (window.XMLHttpRequest) {
        xmlhttp = new XMLHttpRequest()
    }
    xmlhttp.onreadystatechange = function() {
        if (xmlhttp.readyState == 4) {
            console.log(xmlhttp.responseText)
        }
    }
    xmlhttp.open("GET", arm)
    xmlhttp.send(null)
}
loadXMLDoc()

a. 解决方案:
禁止通过file url加载的js代码读取其他本地文件,setAllowFileAccessFromFileURLs(false),表示浏览器禁止从file url中的javascript读取其他本地文件。

  1. setAllowUniversalAccessFromFileURLs()

方法解释
// 设置是否允许通过file url加载的Javascript可以访问其他源(包括http、https等)
webSettings.setAllowUniversalAccessFromFileURLs(true)
// Android 4.1前默认允许
// Android 4.1后默认禁止

AllowUniversalAccessFromFileURLs()设置为true时,攻击者的JS代码如下:

// 通过以下代码可成功读取/etc/hosts 内容数据的
function loadXMLDoc()
{
    var arm = "http://www.so.com";
    var xmlhttp;
    if (window.XMLHttpRequest) {
        xmlhttp = new XMLHttpRequest()
    }
    xmlhttp.onreadystatechange = function() {
        if (xmlhttp.readyState == 4) {
            console.log(xmlhttp.responseText)
        }
    }
    xmlhttp.open("GET", arm)
    xmlhttp.send(null)
}
loadXMLDoc()

a. 解决方案:
禁止通过file url加载的js代码访问其他源,setAllowUniversalAccessFromFileURLs(false),表示浏览器禁止从file url中的javascript访问其他源。

  1. setJavaScriptEnabled()

方法解释:
// 设置是否允许WebView使用JavaScript, 默认不允许
webSettings.setJavaScriptEnabled(true)

注:即使把setAllowFileAccessFromFileURLs()setAllowUniversalAccessFromFileURLs()都设置成false,通过file url加载的javascript还是有办法访问其他的本地文件:通过符号链接跨域攻击, 前提是设置setJavaScriptEnabled(true)

这一攻击能奏效的原因是:通过javascript的延时执行和将当前文件换成指向其他文件的软链接就可以读取到呗符号链接所指的文件。具体攻击步骤如下:

  1. 把恶意js代码输入到攻击应用的目录下,随机命名,并修改该目录的读写权限
  2. 修改后休眠1s,让文件操作完成
  3. 通过系统的Chrome应用去打开该文件
  4. 等待4s让Chrome加载完成该html,最后将该html删除,并使用In -s命令为Chrome的Cookie文件创建软连接

注:在命令执行前该文件是不存在的,执行完这条命令之后,就生成了这个文件,并将Coolie文件链接到了该文件上。

于是就可以通过链接来访问Chrome的Cookie
Google没有进行修复,只是让Chrome最新版本默认禁用file协议,所以这一漏洞在最新版的Chrome中并不存在。但是,在日常大量使用WebView的App和浏览器,都有可能因为此漏洞而导致数据泄露。

如果是file协议, 禁用JavaScript可以很大程度上减少跨源漏洞对WebView的威胁。但,并不能完全杜绝文件的泄露,例如:

应用实现了下载功能,对于无法加载的页面,会自动下载到sd卡中,由于sd卡中的文件所有应用都可以访问,于是可以通过构造一个file url指向被攻击应用的私有文件,然后用此url启动被攻击应用的WebActivity,这样由于该WebActivity无法加载该文件,就会将该文件下载到sd卡下面,然后就可以从sd卡上读取这个文件了。

a. 解决方案

1. 对于不需要使用file协议的应用,禁止file协议
// 禁用file协议
setAllowFileAccess(false)
setAllowFileAccessFromFileURLs(false)
setAllowUniversalAccessFromFileURLs(false)

2. 对于需要使用file协议的应用,禁止file协议加载JavaScript
// 需要使用file协议
setAllowFileAccess(true)
setAllowFileAccessFromFileURLs(true)
setAllowUniversalAccessFromFileURLs(true)

// 禁止file协议加载JavaScript
if (url.startWith("file://")) {
    setJavaScriptEnabled(false)
} else {
    setJavaScriptEnabled(true)
}

第三部分:进阶:网页加速、加载优化

首先,了解WebView加载html流程,客户端加载html步骤如下:

  • 创建并初始化WebView

  • 下载html/js/css/image 等文件

  • 渲染展示html

    • 解析html,构建DOM树

    • DOM树与css样式文件附着呈现

    • 布局、绘制、展示


      html加载流程.png

html加载流程大致分为三个阶段,html主要耗时点为图中三个阶段,以下按三个阶段介绍优化思路。
Q:

  1. 创建初始化WebView耗时
  2. 网络请求网页资源耗时

A:

  1. 优化创建初始化WebView耗时
    (1)、预加载WebView
    (2)、WebView复用 ,例如:腾讯X5
  2. 减少网络请求网页资源耗时,使网页“秒出”
    (1)、静态直出:服务端生成首屏html,提前填充html第一屏数据。大部分主流页面在服务器端拉取首屏数据后通过NodeJs渲染,然后生成一个包含首屏数据的html,发布到CDN上。
    前提:html数据固定,即每个用户看到的页面相同。
    (2)、离线预推:网络环境不佳时,为保证预览效果,增加离线包。通过离线预推的方式,把页面的资源提前拉取到本地,当用户加载资源时,直接加载本地离线包数据
    Q:解决离线包包体过大下载时间过长
    A:离线包与历史离线包,先行对比生成差分包,最后差分包与历史离线包合并。最终需要下载的只有对比之后的差异包。

以下为VasSonic框架实现思路

为了更好的为用户推荐喜欢的内容,根据算法动态向用户推荐内容,因此,不同用户看到的内容可能是不同的,并且同一个用户在不同时间看到的内容可能也不同。为了满足业务,腾讯提出动态直出的方案。

问题:但是动态直出方案需要解决几个明显的问题

  1. 根据需求,生成首屏html时需提前获取用户信息,根据用户渲染直出,总时间不可控。
  2. 首屏无法使用离线预推等缓存策略,因为每个用户看到的内容不一样,无法将html全部发布到cdn。

思路:优化加载时长,核心是提升资源加载速度。
方案:
并行加载
加载时,WebVeiw初始化与请求资源为串行进行,因此,可考虑将html加载流程的第一阶段与第二阶段并行执行,对比图如下:

第一阶段为:创建初始化WebView(Launch WebView)
第二阶段为:请求网页资源(Native(Sonic) Request)

串行模式.png
并行模式.png
问题:并行模式缩短了整体加载时间,但第一阶段与第一阶段谁先谁后并无法得知
方案:流式拦截,加入中间层来桥接内核和数据
  1. 启动子线程请求页面主资源,子线程中不断将网络数据读取到内存中。
  2. WebView初始化完成的时候,提供中间层BridgeStream来连接WebView和数据流;
  3. WebView读取数据时,中间层BridgeStream先把内存的数据读取返回后,再继续读取网络的数据。
中间件.png

通过桥接流的方式,整个内核无需等待,边加载边解析

动态缓存

为解决弱网场景下加载缓慢,引入动态缓存概念。将用户已经加载的页面内容缓存下来,等用户下次点击时,优先展示页面缓存,同事去请求新页面的数据,等新的页面数据拉取下来之后,再重新加载一遍。流程如下:

动态缓存.png
Q:优先展示缓存,获取新数据后重新加载一遍,这样会导致页面闪烁严重。
思路:局部刷新。只刷新新数据与缓存数据不一致的节点。
方案:同一个用户的页面,大部分数据都是不变的,只有少量数据经常变化,由此引入两个概念模板(template)数据块(data):页面中经常变化的数据称之为数据块,除数据块之外的数据成为模板。
页面分离
将整个页面html通过VasSonic标签划分,包裹在标签中的内容为data,标签外的内容为模板。
页面分离.png
通过在请求头带上支持页面更新节点、页面更新时间等关键字段标记是否需要更新本地缓存,原理与svn、git代码管理库类似。

Q:自定义WebView 无法弹出输入法
A:重写构造方法时,不能修改参数,正确写法如下。

    public CustomWebView(Context context) {
        super(context);
        init(context);
    }

    public CustomWebView(Context context, AttributeSet attributeSet) {
        super(context, attributeSet);
        init(context);
    }

    public CustomWebView(Context context, AttributeSet attributeSet, int i) {
        super(context, attributeSet, i);
        init(context);
    }

相关文章

网友评论

      本文标题:Android WebView 学习

      本文链接:https://www.haomeiwen.com/subject/iqtslqtx.html