Android WebView 學(xué)習(xí)

目錄

第一部分
常用方法整理
    WebView常用方法
    WebView周邊相關(guān)類
    WebView緩存機(jī)制類型
    Android與JS交互

第二部分
WebView漏洞
    任意代碼執(zhí)行漏洞
        addJavascriptInterface遠(yuǎn)程代碼執(zhí)行漏洞
        內(nèi)置對(duì)象searchBoxJavaBridge_巡莹、accessibility嫌吠、accessibilityTraversal導(dǎo)出
    密碼明文存儲(chǔ)漏洞
    域控制不嚴(yán)格漏洞
        setAllowFileAccess()
        setAllowFileAccessFromFileURLs()
        setAllowUniversalAccessFromFileURLs()
        setJavaScriptEnabled()

第一部分:常用方法整理

  • WebView常用方法
  • WebView周邊相關(guān)類
  • WebView緩存機(jī)制類型
  • Android與JS交互

常用方法

// 頁(yè)面獲取焦點(diǎn)贾惦,webview正常加載顯示網(wǎng)頁(yè)这揣,僅對(duì)當(dāng)前webview有效
onResume()

// 頁(yè)面失去焦點(diǎn)時(shí)
// 內(nèi)核暫停所有操作,例如DOM解析州邢、plugin解析杉武、JavaScript執(zhí)行辙诞,僅對(duì)當(dāng)前webview有效
onPause()

// 與onResume、onPause方法結(jié)合使用轻抱,作用效果基本相同飞涂,
// 但,以下方法作用于全局所有webview控件
resumeTimers()
pauseTimers()

// 銷毀WebView
// 注:調(diào)用destroy時(shí),webview仍綁定在頁(yè)面上
// 因此较店,需要先從父容器中移除士八,再進(jìn)行銷毀
parentView.removeView(webview)
webview.destroy()


// 是否可以后退
Webview.canGoBack() 
// 后退網(wǎng)頁(yè)
Webview.goBack()

// 是否可以前進(jìn)                     
Webview.canGoForward()
// 前進(jìn)網(wǎng)頁(yè)
Webview.goForward()

// 以當(dāng)前的index為起始點(diǎn)前進(jìn)或者后退到歷史記錄中指定的steps
// 如果steps為負(fù)數(shù)則為后退,正數(shù)則為前進(jìn)
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)
}

// 清除網(wǎng)頁(yè)訪問(wèn)留下的緩存
// 由于內(nèi)核緩存是全局的梁呈,
// 注:因此婚度,該方法清除的是整個(gè)應(yīng)用的網(wǎng)頁(yè)緩存
clearCache(true)

// 清除當(dāng)前webview訪問(wèn)的歷史記錄
// 注:清除訪問(wèn)歷史中的記錄,不包括當(dāng)前訪問(wèn)記錄
clearHistory()

// 僅僅清除自動(dòng)完成填充的表單數(shù)據(jù)官卡,
// 不會(huì)清除webview存儲(chǔ)到本地的數(shù)據(jù)
clearFormData()

周邊相關(guān)類

  1. WebSettings
// 需要js交互時(shí)蝗茁,支持JavaScript
setJavaScriptEnabled(true)

// 支持網(wǎng)頁(yè)插件
setPluginsEnabled(true)

// 設(shè)置自適應(yīng)屏幕,兩者結(jié)合使用
setUserWideViewPort(true)      // 將圖片調(diào)整到適合webview的大小
setLoadWithOverviewMode(true)  // 縮放至屏幕大小

// 縮放操作
setSupportZoom(true)  // 縮放總開(kāi)關(guān)寻咒,默認(rèn)支持
setBuiltInZoomControls(true)  // 設(shè)置內(nèi)資縮放控件哮翘,為false,則不可縮放
setDisplayZoomControls(false) // 隱藏原生縮放控件

setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK)  // 關(guān)閉緩存
setAllowFileAccess(true) // 設(shè)置可以訪問(wèn)文件
setJavaScriptCanOpenWindowsAutomatically(true) // 支持通過(guò)js打開(kāi)新窗口
setLoadsImagesAutomatically(true)  // 支持自動(dòng)加載圖片
setDefaultTextEncodingName("utf-8") // 設(shè)置編碼格式
補(bǔ)充:
  • 當(dāng)加載html頁(yè)面時(shí)毛秘,WebView會(huì)在/data/data/包名目錄 下生成database與cache兩個(gè)文件夾
  • 請(qǐng)求的URL記錄保存在WebViewCache.db饭寺,而URL的內(nèi)容是保存在WebViewCache文件夾下
  • 是否啟用緩存
webSetting.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK)

// 不使用緩存
webSetting.setCacheMode(WebSettings.LOAD_NO_CACHE)
類型 說(shuō)明
LOAD_CACHE_ONLY 只讀本地緩存
LOAD_CACHE_ELSE_NETWORK 優(yōu)先使用本地緩存
LOAD_DEFAULT 根據(jù)cache-control決定是否去網(wǎng)絡(luò)數(shù)據(jù)
LOAD_NO_CACHE 只讀網(wǎng)絡(luò)數(shù)據(jù)
  1. WebViewClient
  • onPageStarted():開(kāi)始載入頁(yè)面時(shí)調(diào)用
  • onPageFinished():頁(yè)面加載結(jié)束時(shí)調(diào)用
  • onLoadResource():加載頁(yè)面資源時(shí)調(diào)用,每個(gè)資源的加載都會(huì)調(diào)用一次
  • onReceivedError():加載頁(yè)面的服務(wù)器出現(xiàn)錯(cuò)誤時(shí)調(diào)用
  • onReceivedSslError():處理https請(qǐng)求叫挟,webview默認(rèn)不處理https請(qǐng)求艰匙,需如下設(shè)置。
webview.setWebViewClient(new WebViewClient() {
      @Override
      public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
           handler.proceed();  // 表示等待證書響應(yīng)
           // handler.cancel();    // 表示掛起鏈接抹恳,為默認(rèn)方式
           // handler.handleMessage(null);  
      }
})
  • shouldInterceptRequest(...):當(dāng)向服務(wù)器訪問(wèn)本地已有的靜態(tài)資源時(shí)進(jìn)行攔截员凝,檢測(cè)到是相同的資源則用本地資源代替。
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")) {
                  // 假設(shè)網(wǎng)頁(yè)里改圖片地址為:http://abc.com.image/logo.png
                  // 圖片資源名稱為 logo.png
          
                  // 創(chuàng)建一個(gè)輸入流
                  InputStream is = null
                  try {
                       // 3. 獲得需要替換的資源
                       is = getApplicationContext().getAssets.open("images/abc.png")
                  } catch (IOException e) {
                      e.printStackTrace()
                  }

                  // 4. 替換資源
                  // 參數(shù): 資源文件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")) {
                  // 假設(shè)網(wǎng)頁(yè)里改圖片地址為:http://abc.com.image/logo.png
                  // 圖片資源名稱為 logo.png
          
                  // 創(chuàng)建一個(gè)輸入流
                  InputStream is = null
                  try {
                       // 3. 獲得需要替換的資源
                       is = getApplicationContext().getAssets.open("images/abc.png")
                  } catch (IOException e) {
                      e.printStackTrace()
                  }

                  // 4. 替換資源
                  // 參數(shù): 資源文件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):打開(kāi)網(wǎng)頁(yè)時(shí)不調(diào)用系統(tǒng)瀏覽器骤公,而是在本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;
    }
    //回調(diào)加載成功的函數(shù)
}

補(bǔ)充:
方式一方式二均可避免跳轉(zhuǎn)外部瀏覽器
但,官方說(shuō)明如下:

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).

官方表示一般情況下沒(méi)必要使用方式一扬跋。當(dāng)然阶捆,方式一寫法出現(xiàn)也是有原因的,存在即是合理钦听。

注:希望重定向時(shí)繼續(xù)顯示loading效果時(shí)洒试,必須使用方式一,即:loadUrl(url) return true朴上。

雖然方式一垒棋、二呈現(xiàn)效果一樣,但方式一可以保證非重定向的url不會(huì)回調(diào)shouldOverrideUrlLoading方法痪宰,從而可以通過(guò)一個(gè)開(kāi)關(guān)來(lái)過(guò)濾重定向鏈接

舉個(gè)例子:
目標(biāo)url是http://www.reibang.com叼架,會(huì)重定向到http://www.reibang.com

  • 執(zhí)行流程對(duì)比

方式一:

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

方式二

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

結(jié)論: 重定向需繼續(xù)顯示loading時(shí)畔裕,方式二無(wú)法準(zhǔn)確判斷l(xiāng)oading消失時(shí)機(jī),此時(shí)應(yīng)使用方式一乖订;除此之外選擇方式二比較合適

  1. WebChromeClient
  • onProgressChanged(WebView view, int newProgress):獲得網(wǎng)頁(yè)的加載進(jìn)度并顯示扮饶,newProgress最大值為100。
  • onReceivedTitle(WebView view, String title):獲取Web頁(yè)中的標(biāo)題

緩存機(jī)制類型

webview自帶5種緩存機(jī)制

  1. 瀏覽器 緩存
    • 根據(jù)Http協(xié)議頭中的Cache-Control(或Expires)和Lase-Modified(或Etag)等字段來(lái)控制文件的有效時(shí)長(zhǎng)和文件最后更改時(shí)間決定是否需要請(qǐng)求網(wǎng)絡(luò)
    • 瀏覽器緩存是瀏覽器內(nèi)核的機(jī)制 一般都是標(biāo)準(zhǔn)的實(shí)現(xiàn)
    • 特點(diǎn):緩存文件需首次加載才會(huì)產(chǎn)生乍构;且存儲(chǔ)空間有限甜无、可能會(huì)被清除;緩存的文件沒(méi)有校驗(yàn)哥遮;可用來(lái)緩存靜態(tài)資源文件岂丘,存儲(chǔ)在app的data目錄中;內(nèi)置自動(dòng)實(shí)現(xiàn)眠饮。
  2. Application Cache 緩存
    • 以文件單位進(jìn)行緩存元潘,且文件有一定的更新機(jī)制(類似于瀏覽器緩存機(jī)制)
    • 存儲(chǔ)靜態(tài)文件,是對(duì)瀏覽器緩存機(jī)制的補(bǔ)充
// 1.設(shè)置緩存路徑
String cacheDirPath = context.getFilesDir().getAbsolutePath() + "cache/"
webSetting.setAppCachePath(cacheDirPath)

// 2.設(shè)置緩存大小
webSetting.setAppCacheMaxSize(20 * 1024 * 1024)

// 3.開(kāi)啟Application Cache存儲(chǔ)機(jī)制
webSetting.setAppCacheEnable(true)

注:
每個(gè)Application 只調(diào)用一次 setAppCachePath()setAppCacheMaxSize()

  1. DOM Storage 緩存
  • 通過(guò)存儲(chǔ)字符串的Key - Value對(duì)來(lái)提供

DOM Storage分為sessionStoragelocalStorage君仆,兩者使用方法基本相同,區(qū)別在于作用范圍不同
1. sessionStorage:具備臨時(shí)性牲距,存儲(chǔ)與頁(yè)面相關(guān)的數(shù)據(jù)返咱,在頁(yè)面關(guān)閉后無(wú)法使用
2. localStorage:具備持久性,保存的數(shù)據(jù)在頁(yè)面關(guān)閉后也可以使用

  • 特點(diǎn):存儲(chǔ)空間大(5MB)牍鞠,Cookies才4KB咖摹;存儲(chǔ)安全便捷,無(wú)需經(jīng)常和服務(wù)器交互难述,而Cookies每次請(qǐng)求頁(yè)面都會(huì)向服務(wù)器發(fā)送網(wǎng)絡(luò)請(qǐng)求萤晴;存儲(chǔ)機(jī)制類似于SharePreference機(jī)制。
// 開(kāi)啟DOM Storage
setDomStorageEnabled(true)
  1. Web SQL Database 緩存(不再維護(hù))
  • 基于SQL的數(shù)據(jù)存儲(chǔ)機(jī)制
  • 可方便對(duì)數(shù)據(jù)進(jìn)行增刪改查
// 1. 設(shè)置本地緩存地址
String cacheDirPath = context.getFileDir().getAbsolutePath() + "cache/"
webSetting.setDatabasePath(cacheDirPath)

// 2. 開(kāi)啟數(shù)據(jù)庫(kù)存儲(chǔ)機(jī)制
webSetting.setDatabaseEnabled(true)
  1. Indexed Database 緩存
  • 通過(guò)存儲(chǔ)字符串的Key - Value對(duì)來(lái)提供
// 只需設(shè)置支持JS就自動(dòng)打開(kāi)IndexDB存儲(chǔ)機(jī)制
// Android 在4.4開(kāi)始加入對(duì)IndexedDB的支持
webSetting.setJavaScriptEnabled(true)

Android與JS交互

  • Android調(diào)用JS代碼
  1. loadUrl()
html代碼:
<!DOCTYPE html>
<html>
      <head>
            <meta charset="utf-8">

             // JS代碼
             <script>
                   // Android需要調(diào)用的方法
                   function callJS() {
                          alert("Android調(diào)用了JS的callJS方法");
                   }
             </script>
      </head>
</html>

Android代碼:
// 調(diào)用JS方法名需一致 callJS
webview.loadUrl("javascript:callJS()")

注:JS代碼必須在onPageFinished()回調(diào)之后調(diào)用

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

注:高效胁后,頁(yè)面不會(huì)刷新店读,可輕松獲得返回值;Android 4.4后可用

  • JS調(diào)用Android代碼
  1. 通過(guò)addJavascriptInterface()進(jìn)行對(duì)象映射
html代碼
<!DOCTYPE html>
<html>
      <head>
            <meta charset="utf-8">

             // JS代碼
             <script>
                   // Android需要調(diào)用的方法
                   function callAndroid() {
                          test.callWeb("JS調(diào)用了Android的callAndroid方法");
                   }
             </script>
      </head>
</html>

Android代碼
public class AndroidtoJs extrends Object {
       // 定義JS需要調(diào)用的方法攀芯,且方法名與html中一致
       // 被JS調(diào)用的方法必須加入@JavascriptInterface注解
       @JavascriptInterface
       public void callWeb(String msg) {
              // msg 值為 “JS調(diào)用了Android的callAndroid方法”
       }
}

調(diào)用:
// “test” 需與html約定一致
webView.addJavascriptInterface(new AndroidtoJs(), "test")

注:該方式存在嚴(yán)重漏洞問(wèn)題屯断,詳情請(qǐng)看后續(xù)WebView漏洞一段。

  1. 通過(guò)WebViewClientshouldOverrideUrlLoading()方式攔截url
html代碼
<!DOCTYPE html>
<html>
      <head>
            <meta charset="utf-8">

             // JS代碼
             <script>
                   // Android需要調(diào)用的方法
                   function callAndroid() {
                      // 約定的url協(xié)議
                      document.location = "js://webview?arg1=111&arg2=222";
                   }
             </script>
      </head>

      <!-- 點(diǎn)擊按鈕則調(diào)用callAndroid()方法  -->
      <body>
           <button type="button" id="button1" onclick="callAndroid()">點(diǎn)擊調(diào)用Android代碼</button>
      </body>
</html>

Android代碼
public class AndroidtoJs extrends WebViewClient {

       @Override
       public boolean shouldOverrideUrlLoading(WebView view, String url) {
              // 根據(jù)協(xié)議的參數(shù)侣诺,判斷是否是所需的url
              // 一般根據(jù)scheme殖演、authority判斷前兩個(gè)參數(shù)
              Uri uri = Uri.parse(url);
              if (uri.getScheme().equals("js") && uri.getAuthority().equals("webview")) {
                    // 執(zhí)行攔截操作
                    // 也可以通過(guò)uri.getQueryParameterNames()獲取參數(shù)
                    return true;
              }
              return super.shouldOverrideUrlLoading(view, url);
       }
}

另:該方法JS獲取Android方法的返回值復(fù)雜,如需獲取返回值年鸳,具體操作如下:
Android代碼
webview.loadUrl("javascript:returnResult(" + result + ")");

html代碼
function returnResult(result) {
       alert("result is " + result);
}
  1. 通過(guò)WebChromeClientonJsAlert(),onJsConfirm(),onJsPrompt()方法回調(diào)攔截JS對(duì)話框alert(),confirm(),prompt()消息
方法 作用 返回值 備注
alert() 彈出警告框 沒(méi)有 在文本加入“\n”可換行
confirm() 彈出確認(rèn)框 兩個(gè)返回值 返回布爾值趴久,表示點(diǎn)擊確定/取消
prompt() 彈出輸入框 任意設(shè)置返回值 點(diǎn)擊確定返回輸入框中的值,點(diǎn)擊取消返回null
html代碼
<!DOCTYPE html>
<html>
      <head>
            <meta charset="utf-8">

             // JS代碼
             <script>
                   // Android需要調(diào)用的方法
                   function test() {
                      // 約定的url協(xié)議
                      var result=prompt("js://webview?arg1=111&arg2=222");
                      alert("demo " + result);
                   }
             </script>
      </head>

      <!-- 點(diǎn)擊按鈕則調(diào)用callAndroid()方法  -->
      <body>
           <button type="button" id="button1" onclick="test()">調(diào)用prompt</button>
      </body>
</html>

Android代碼
public class AndroidtoJs extrends WebChromeClient {

       @Override
       public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JSPromptResult result) {
              // 根據(jù)協(xié)議的參數(shù)搔确,判斷是否是所需的url
              // 一般根據(jù)scheme彼棍、authority判斷前兩個(gè)參數(shù)
              Uri uri = Uri.parse(url);
              if (uri.getScheme().equals("js") && uri.getAuthority().equals("webview")) {
                    // 執(zhí)行攔截操作
                    // 也可以通過(guò)uri.getQueryParameterNames()獲取參數(shù)
                    result.confirm("js調(diào)用了Android的方法成功啦")
                    return true;
              }
              return super.onJsPrompt(view, url, message, defaultValue, result);
       }

       @Override
       public boolean onJsAlert(WebView view, String url, String message, JSResult result) {
              // 根據(jù)協(xié)議的參數(shù)灭忠,判斷是否是所需的url
              // 一般根據(jù)scheme、authority判斷前兩個(gè)參數(shù)
              Uri uri = Uri.parse(url);
              if (uri.getScheme().equals("js") && uri.getAuthority().equals("webview")) {
                    // 執(zhí)行攔截操作
                    // 也可以通過(guò)uri.getQueryParameterNames()獲取參數(shù)
                    result.confirm()
                    return true;
              }
              return super.onJsAlert(view, url, message, result);
       }

       @Override
       public boolean onJsConfirm(WebView view, String url, String message, JSResult result) {
              // 根據(jù)協(xié)議的參數(shù)滥酥,判斷是否是所需的url
              // 一般根據(jù)scheme更舞、authority判斷前兩個(gè)參數(shù)
              Uri uri = Uri.parse(url);
              if (uri.getScheme().equals("js") && uri.getAuthority().equals("webview")) {
                    // 執(zhí)行攔截操作
                    // 也可以通過(guò)uri.getQueryParameterNames()獲取參數(shù)
                    result.confirm()
                    return true;
              }
              return super.onJsConfirm(view, url, message, result);
       }
}

三種JS調(diào)用Android方法對(duì)比

調(diào)用方式 優(yōu)點(diǎn) 缺點(diǎn) 使用場(chǎng)景
方式一 方便簡(jiǎn)潔 4.2以下存在漏洞 4.2以上相對(duì)簡(jiǎn)單互調(diào)
方式二 無(wú)漏洞 需協(xié)議約束 不需返回值互調(diào)(ios主用)
方式三 無(wú)漏洞 需協(xié)議約束 滿足大多數(shù)情況下的互調(diào)場(chǎng)景

注:更多復(fù)雜交互場(chǎng)景可參考JsBridge


第二部分:WebView漏洞

webview中,漏洞主要分為以下三類

  • 任意代碼執(zhí)行漏洞
  • 密碼明文存儲(chǔ)漏洞
  • 域控制不嚴(yán)格漏洞

任意代碼執(zhí)行漏洞

出現(xiàn)該漏洞的原因有兩類:

A: WebView中的addJavascriptInterface()接口:接口引起遠(yuǎn)程代碼執(zhí)行漏洞
  1. 產(chǎn)生原因:
    JS調(diào)用Android的其中一個(gè)方式是通過(guò)addJavascriptInterface接口對(duì)象映射坎吻,如此 webview.addJavascriptInterface(new JSObject, "jsInterface")缆蝉。因此,當(dāng)JS拿到Android這個(gè)對(duì)象后瘦真,就可以調(diào)用這個(gè)Android對(duì)象中所有的方法刊头,包括系統(tǒng)類(java.lang.Runtime類),從而進(jìn)行任意代碼執(zhí)行诸尽。

    例如 可以執(zhí)行命令獲取本地設(shè)備中的文件等信息原杂,從而造成信息泄露。
    漏洞利用過(guò)程(結(jié)合Java反射機(jī)制):可調(diào)用getClass()方法您机,從而通過(guò)getClass().forName加載(java.lang.Runtime類)穿肄,最終通過(guò)該類執(zhí)行本地命令。流程偽碼大致如下:

function execute(cmdArgs)
{
      // 1. 遍歷window對(duì)象际看,找到包含getClass()的對(duì)象咸产,
      // 也就是上文中的《jsInterface》
      for (var obj in window) {
            if ("getClass" in window[obj]) {
                   // 2.利用反射調(diào)用forName()得到Runtime類對(duì)象
                   window[obj].getClass.forName("java.lang.Runtime")
                   // 3.調(diào)用靜態(tài)方法執(zhí)行一些命令,比如訪問(wèn)文件的命令
                   getMethod("getRuntime", null).invoke(null, null).exec(cmdArgs)
                   // 最終得到想要的數(shù)據(jù)流仲闽,如訪問(wèn)的文件
            }
      }
}

注:當(dāng)一些APP通過(guò)掃描二維碼打開(kāi)一個(gè)外部網(wǎng)頁(yè)時(shí)脑溢,攻擊者就可以執(zhí)行這段js代碼進(jìn)行漏洞攻擊。

  1. 解決方案:Android 4.2之后赖欣,對(duì)被調(diào)用的函數(shù)以@JavascriptInterface進(jìn)行注解
B: WebView內(nèi)置導(dǎo)出的searchBoxJavaBridge_屑彻、accessibilityaccessibilityTraversal對(duì)象
  1. 產(chǎn)生原因:Android 3.0以下,Android 系統(tǒng)會(huì)默認(rèn)通過(guò)searchBoxJavaBridge_的Js接口給WebView添加一個(gè)JS映射對(duì)象顶吮,該接口可能被利用社牲,實(shí)現(xiàn)遠(yuǎn)程任意代碼。
  2. 解決方案:刪除searchBoxJavaBridge_接口
// 通過(guò)調(diào)用改方法刪除接口
removeJavascriptInterface()

密碼明文存儲(chǔ)漏洞

  1. 產(chǎn)生原因:WebView默認(rèn)開(kāi)啟密碼保存功能云矫,當(dāng)用戶允許保存時(shí)會(huì)被明文保存到本地.../databases/webview.db中膳沽,這樣就有被盜取秘密的危險(xiǎn)。
  2. 解決方案:關(guān)閉密碼保存提醒
webSettings.setSavePassword(false)

域控制不嚴(yán)格漏洞

  1. 產(chǎn)生原因:由于某應(yīng)用(A)可通過(guò)設(shè)置exported屬性被另一個(gè)應(yīng)用(B)啟動(dòng)让禀。即B應(yīng)用可以通過(guò)A應(yīng)用導(dǎo)出的Activity讓B應(yīng)用加載一個(gè)惡意file協(xié)議的url挑社,從而通過(guò)一系列手段可以獲取B應(yīng)用的內(nèi)部私有文件,從而可能導(dǎo)致數(shù)據(jù)泄露.

解釋:當(dāng)其他應(yīng)用啟動(dòng)此Activity時(shí)巡揍,intent中的data直接被當(dāng)做url來(lái)加載痛阻,其他APP通過(guò)使用顯式ComponentName或者其他類似當(dāng)時(shí)就可以很輕松啟動(dòng)該WebViewActivity并加載惡意url。

注:危險(xiǎn)來(lái)源在于腮敌,當(dāng)應(yīng)用A設(shè)置可被外部應(yīng)用B啟動(dòng)時(shí)阱当,B可傳遞任意url(包含危險(xiǎn)惡意url)讓應(yīng)用A去加載俏扩。
下面著重分析WebView中的getSettings類的方法對(duì)WebView安全性的影響。

  1. setAllowFileAccess()

方法解釋:
// 設(shè)置是否允許WenView使用File協(xié)議
webview.getSettings().setAllowFileAccess()
// 默認(rèn)設(shè)置為true弊添,即允許在File域下執(zhí)行任意JavaScript代碼

注:使用File域加載的js代碼能夠使用進(jìn)行同源策略跨域訪問(wèn)录淡,從而導(dǎo)致隱私信息泄漏

同源策略跨域訪問(wèn):對(duì)私有目錄文件進(jìn)行訪問(wèn)
針對(duì)IM類產(chǎn)品,泄漏的是聊天消息油坝、聯(lián)系人等等
針對(duì)瀏覽器類軟件嫉戚,泄漏的是cookie信息

a. 解決方案:
不允許使用file協(xié)議,則不會(huì)存在上述威脅澈圈,但彬檀,同時(shí)也我要發(fā)加載本地html文件。因此瞬女,需要區(qū)分對(duì)待窍帝。

1. 不需要使用file協(xié)議的應(yīng)用,禁用file協(xié)議
setAllowFileAccess(false)
2. 需要使用file協(xié)議的應(yīng)用诽偷,禁止file協(xié)議加載JavaScript.
setAllowFileAccess(true)
// 禁止file協(xié)議加載JavaScript
if (url.startWith("file://")) {
    setJavaScriptEnabled(false)
} else {
    setJaveScriptEnabled(true)
}
  1. setAllowFileAccessFromFileURLs()

方法解釋:
// 設(shè)置是否允許通過(guò)file url加載的Js代碼讀取其他的本地文件
webSettings.setAllowFileAccessFromFileURLs(true)
// Android 4.1前默認(rèn)允許
// Android 4.1后默認(rèn)禁止

當(dāng)AllowFileAccessFromFileURLs()設(shè)置為true時(shí)坤学,攻擊者的JS代碼如下:

// 通過(guò)以下代碼可成功讀取/etc/hosts 內(nèi)容數(shù)據(jù)的
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. 解決方案:
禁止通過(guò)file url加載的js代碼讀取其他本地文件,setAllowFileAccessFromFileURLs(false)报慕,表示瀏覽器禁止從file url中的javascript讀取其他本地文件拥峦。

  1. setAllowUniversalAccessFromFileURLs()

方法解釋
// 設(shè)置是否允許通過(guò)file url加載的Javascript可以訪問(wèn)其他源(包括http、https等)
webSettings.setAllowUniversalAccessFromFileURLs(true)
// Android 4.1前默認(rèn)允許
// Android 4.1后默認(rèn)禁止

當(dāng)AllowUniversalAccessFromFileURLs()設(shè)置為true時(shí)卖子,攻擊者的JS代碼如下:

// 通過(guò)以下代碼可成功讀取/etc/hosts 內(nèi)容數(shù)據(jù)的
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. 解決方案:
禁止通過(guò)file url加載的js代碼訪問(wèn)其他源,setAllowUniversalAccessFromFileURLs(false)刑峡,表示瀏覽器禁止從file url中的javascript訪問(wèn)其他源洋闽。

  1. setJavaScriptEnabled()

方法解釋:
// 設(shè)置是否允許WebView使用JavaScript, 默認(rèn)不允許
webSettings.setJavaScriptEnabled(true)

注:即使把setAllowFileAccessFromFileURLs()setAllowUniversalAccessFromFileURLs()都設(shè)置成false,通過(guò)file url加載的javascript還是有辦法訪問(wèn)其他的本地文件:通過(guò)符號(hào)鏈接跨域攻擊, 前提是設(shè)置setJavaScriptEnabled(true)

這一攻擊能奏效的原因是:通過(guò)javascript的延時(shí)執(zhí)行和將當(dāng)前文件換成指向其他文件的軟鏈接就可以讀取到唄符號(hào)鏈接所指的文件突梦。具體攻擊步驟如下:

  1. 把惡意js代碼輸入到攻擊應(yīng)用的目錄下诫舅,隨機(jī)命名,并修改該目錄的讀寫權(quán)限
  2. 修改后休眠1s宫患,讓文件操作完成
  3. 通過(guò)系統(tǒng)的Chrome應(yīng)用去打開(kāi)該文件
  4. 等待4s讓Chrome加載完成該html刊懈,最后將該html刪除,并使用In -s命令為Chrome的Cookie文件創(chuàng)建軟連接

注:在命令執(zhí)行前該文件是不存在的娃闲,執(zhí)行完這條命令之后虚汛,就生成了這個(gè)文件,并將Coolie文件鏈接到了該文件上皇帮。

于是就可以通過(guò)鏈接來(lái)訪問(wèn)Chrome的Cookie
Google沒(méi)有進(jìn)行修復(fù)卷哩,只是讓Chrome最新版本默認(rèn)禁用file協(xié)議,所以這一漏洞在最新版的Chrome中并不存在属拾。但是将谊,在日常大量使用WebView的App和瀏覽器冷溶,都有可能因?yàn)榇寺┒炊鴮?dǎo)致數(shù)據(jù)泄露。

如果是file協(xié)議尊浓, 禁用JavaScript可以很大程度上減少跨源漏洞對(duì)WebView的威脅逞频。但,并不能完全杜絕文件的泄露栋齿,例如:

應(yīng)用實(shí)現(xiàn)了下載功能苗胀,對(duì)于無(wú)法加載的頁(yè)面,會(huì)自動(dòng)下載到sd卡中褒颈,由于sd卡中的文件所有應(yīng)用都可以訪問(wèn)柒巫,于是可以通過(guò)構(gòu)造一個(gè)file url指向被攻擊應(yīng)用的私有文件,然后用此url啟動(dòng)被攻擊應(yīng)用的WebActivity谷丸,這樣由于該WebActivity無(wú)法加載該文件堡掏,就會(huì)將該文件下載到sd卡下面,然后就可以從sd卡上讀取這個(gè)文件了刨疼。

a. 解決方案

1. 對(duì)于不需要使用file協(xié)議的應(yīng)用泉唁,禁止file協(xié)議
// 禁用file協(xié)議
setAllowFileAccess(false)
setAllowFileAccessFromFileURLs(false)
setAllowUniversalAccessFromFileURLs(false)

2. 對(duì)于需要使用file協(xié)議的應(yīng)用,禁止file協(xié)議加載JavaScript
// 需要使用file協(xié)議
setAllowFileAccess(true)
setAllowFileAccessFromFileURLs(true)
setAllowUniversalAccessFromFileURLs(true)

// 禁止file協(xié)議加載JavaScript
if (url.startWith("file://")) {
    setJavaScriptEnabled(false)
} else {
    setJavaScriptEnabled(true)
}

第三部分:進(jìn)階:網(wǎng)頁(yè)加速揩慕、加載優(yōu)化

首先亭畜,了解WebView加載html流程,客戶端加載html步驟如下:

  • 創(chuàng)建并初始化WebView

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

  • 渲染展示html

    • 解析html迎卤,構(gòu)建DOM樹(shù)

    • DOM樹(shù)與css樣式文件附著呈現(xiàn)

    • 布局拴鸵、繪制、展示


      html加載流程.png

html加載流程大致分為三個(gè)階段蜗搔,html主要耗時(shí)點(diǎn)為圖中三個(gè)階段劲藐,以下按三個(gè)階段介紹優(yōu)化思路。
Q:

  1. 創(chuàng)建初始化WebView耗時(shí)
  2. 網(wǎng)絡(luò)請(qǐng)求網(wǎng)頁(yè)資源耗時(shí)

A:

  1. 優(yōu)化創(chuàng)建初始化WebView耗時(shí)
    (1)樟凄、預(yù)加載WebView
    (2)聘芜、WebView復(fù)用 ,例如:騰訊X5
  2. 減少網(wǎng)絡(luò)請(qǐng)求網(wǎng)頁(yè)資源耗時(shí)缝龄,使網(wǎng)頁(yè)“秒出”
    (1)汰现、靜態(tài)直出:服務(wù)端生成首屏html,提前填充html第一屏數(shù)據(jù)叔壤。大部分主流頁(yè)面在服務(wù)器端拉取首屏數(shù)據(jù)后通過(guò)NodeJs渲染瞎饲,然后生成一個(gè)包含首屏數(shù)據(jù)的html,發(fā)布到CDN上炼绘。
    前提:html數(shù)據(jù)固定企软,即每個(gè)用戶看到的頁(yè)面相同。
    (2)饭望、離線預(yù)推:網(wǎng)絡(luò)環(huán)境不佳時(shí)仗哨,為保證預(yù)覽效果形庭,增加離線包。通過(guò)離線預(yù)推的方式厌漂,把頁(yè)面的資源提前拉取到本地萨醒,當(dāng)用戶加載資源時(shí),直接加載本地離線包數(shù)據(jù)
    Q:解決離線包包體過(guò)大下載時(shí)間過(guò)長(zhǎng)
    A:離線包與歷史離線包苇倡,先行對(duì)比生成差分包富纸,最后差分包與歷史離線包合并。最終需要下載的只有對(duì)比之后的差異包旨椒。

以下為VasSonic框架實(shí)現(xiàn)思路

為了更好的為用戶推薦喜歡的內(nèi)容晓褪,根據(jù)算法動(dòng)態(tài)向用戶推薦內(nèi)容,因此综慎,不同用戶看到的內(nèi)容可能是不同的涣仿,并且同一個(gè)用戶在不同時(shí)間看到的內(nèi)容可能也不同。為了滿足業(yè)務(wù)示惊,騰訊提出動(dòng)態(tài)直出的方案好港。

問(wèn)題:但是動(dòng)態(tài)直出方案需要解決幾個(gè)明顯的問(wèn)題

  1. 根據(jù)需求,生成首屏html時(shí)需提前獲取用戶信息米罚,根據(jù)用戶渲染直出钧汹,總時(shí)間不可控。
  2. 首屏無(wú)法使用離線預(yù)推等緩存策略录择,因?yàn)槊總€(gè)用戶看到的內(nèi)容不一樣拔莱,無(wú)法將html全部發(fā)布到cdn。

思路:優(yōu)化加載時(shí)長(zhǎng)隘竭,核心是提升資源加載速度辨宠。
方案:
并行加載
加載時(shí),WebVeiw初始化與請(qǐng)求資源為串行進(jìn)行货裹,因此,可考慮將html加載流程的第一階段與第二階段并行執(zhí)行精偿,對(duì)比圖如下:

第一階段為:創(chuàng)建初始化WebView(Launch WebView)
第二階段為:請(qǐng)求網(wǎng)頁(yè)資源(Native(Sonic) Request)

串行模式.png

并行模式.png

問(wèn)題:并行模式縮短了整體加載時(shí)間弧圆,但第一階段與第一階段誰(shuí)先誰(shuí)后并無(wú)法得知
方案:流式攔截,加入中間層來(lái)橋接內(nèi)核和數(shù)據(jù)

  1. 啟動(dòng)子線程請(qǐng)求頁(yè)面主資源笔咽,子線程中不斷將網(wǎng)絡(luò)數(shù)據(jù)讀取到內(nèi)存中搔预。
  2. WebView初始化完成的時(shí)候,提供中間層BridgeStream來(lái)連接WebView和數(shù)據(jù)流叶组;
  3. WebView讀取數(shù)據(jù)時(shí)拯田,中間層BridgeStream先把內(nèi)存的數(shù)據(jù)讀取返回后,再繼續(xù)讀取網(wǎng)絡(luò)的數(shù)據(jù)甩十。
中間件.png

通過(guò)橋接流的方式船庇,整個(gè)內(nèi)核無(wú)需等待吭产,邊加載邊解析

動(dòng)態(tài)緩存

為解決弱網(wǎng)場(chǎng)景下加載緩慢,引入動(dòng)態(tài)緩存概念鸭轮。將用戶已經(jīng)加載的頁(yè)面內(nèi)容緩存下來(lái)臣淤,等用戶下次點(diǎn)擊時(shí),優(yōu)先展示頁(yè)面緩存窃爷,同事去請(qǐng)求新頁(yè)面的數(shù)據(jù)邑蒋,等新的頁(yè)面數(shù)據(jù)拉取下來(lái)之后,再重新加載一遍按厘。流程如下:

動(dòng)態(tài)緩存.png

Q:優(yōu)先展示緩存医吊,獲取新數(shù)據(jù)后重新加載一遍,這樣會(huì)導(dǎo)致頁(yè)面閃爍嚴(yán)重逮京。
思路:局部刷新卿堂。只刷新新數(shù)據(jù)與緩存數(shù)據(jù)不一致的節(jié)點(diǎn)。
方案:同一個(gè)用戶的頁(yè)面造虏,大部分?jǐn)?shù)據(jù)都是不變的御吞,只有少量數(shù)據(jù)經(jīng)常變化,由此引入兩個(gè)概念模板(template)數(shù)據(jù)塊(data):頁(yè)面中經(jīng)常變化的數(shù)據(jù)稱之為數(shù)據(jù)塊漓藕,除數(shù)據(jù)塊之外的數(shù)據(jù)成為模板陶珠。
頁(yè)面分離
將整個(gè)頁(yè)面html通過(guò)VasSonic標(biāo)簽劃分,包裹在標(biāo)簽中的內(nèi)容為data享钞,標(biāo)簽外的內(nèi)容為模板揍诽。
頁(yè)面分離.png

通過(guò)在請(qǐng)求頭帶上支持頁(yè)面更新節(jié)點(diǎn)、頁(yè)面更新時(shí)間等關(guān)鍵字段標(biāo)記是否需要更新本地緩存栗竖,原理與svn暑脆、git代碼管理庫(kù)類似。

Q:自定義WebView 無(wú)法彈出輸入法
A:重寫構(gòu)造方法時(shí)狐肢,不能修改參數(shù)添吗,正確寫法如下。

    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);
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末份名,一起剝皮案震驚了整個(gè)濱河市碟联,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌僵腺,老刑警劉巖鲤孵,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異辰如,居然都是意外死亡普监,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)凯正,“玉大人毙玻,你說(shuō)我怎么就攤上這事∑峒剩” “怎么了淆珊?”我有些...
    開(kāi)封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)奸汇。 經(jīng)常有香客問(wèn)我施符,道長(zhǎng),這世上最難降的妖魔是什么擂找? 我笑而不...
    開(kāi)封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任戳吝,我火速辦了婚禮,結(jié)果婚禮上贯涎,老公的妹妹穿的比我還像新娘听哭。我一直安慰自己,他們只是感情好塘雳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布陆盘。 她就那樣靜靜地躺著,像睡著了一般败明。 火紅的嫁衣襯著肌膚如雪隘马。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼路召。 笑死,一個(gè)胖子當(dāng)著我的面吹牛幔嗦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沥潭,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼邀泉,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了钝鸽?” 一聲冷哼從身側(cè)響起汇恤,我...
    開(kāi)封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎寞埠,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體焊夸,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仁连,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饭冬。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡使鹅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出昌抠,到底是詐尸還是另有隱情患朱,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布炊苫,位于F島的核電站裁厅,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏侨艾。R本人自食惡果不足惜执虹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望唠梨。 院中可真熱鬧袋励,春花似錦、人聲如沸当叭。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蚁鳖。三九已至磺芭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間才睹,已是汗流浹背徘跪。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留琅攘,地道東北人垮庐。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像坞琴,于是被迫代替她去往敵國(guó)和親哨查。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容