WebView深度學(xué)習(xí)(三)之WebView的內(nèi)存泄漏、漏洞以及緩存機(jī)制原理和解決方案

上兩篇文章講到了WebView的基本使用以及Android和js的交互 以及 全面總結(jié)WebView遇到的坑及優(yōu)化 ,這篇文章講一下內(nèi)存泄漏和漏洞處理。如果你想更深入的了解WebView,這篇文章值得一看。



? 六、WebView的內(nèi)存泄漏怎么辦?

1.不在xml中定義 Webview ,而是在需要的時候在Activity中創(chuàng)建,并且Context使用 getApplicationgContext()

    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 
                                         ViewGroup.LayoutParams.MATCH_PARENT);
    mWebView = new WebView(getApplicationContext());
    mWebView.setLayoutParams(params);
    mLayout.addView(mWebView);

2.在 Activity 銷毀( WebView )的時候,先讓 WebView 加載null內(nèi)容,然后移除 WebView,再銷毀 WebView,最后置空。

@Override
protected void onDestroy() {
    if (mWebView != null) {
        mWebView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
        mWebView.clearHistory();

        ((ViewGroup) mWebView.getParent()).removeView(mWebView);
        mWebView.destroy();
        mWebView = null;
    }
    super.onDestroy();
}

? 七、WebView的使用漏洞 及其修復(fù)方式

WebView中,主要漏洞有三類:

1.任意代碼執(zhí)行漏洞
2.密碼明文存儲漏洞
3.域控制不嚴(yán)格漏洞

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

  • (1)addJavascriptInterface 接口引起遠(yuǎn)程代碼執(zhí)行漏洞

1. 漏洞產(chǎn)生原因:

    js調(diào)用Android的其中一個方式是通過addJavascriptInterface接口進(jìn)行對象映射:

    webView.addJavascriptInterface(new JSObject(), "myObj");
    // 參數(shù)1:Android的本地對象
    // 參數(shù)2:JS的對象
    // 通過對象映射將Android中的本地對象和JS中的對象進(jìn)行關(guān)聯(lián),從而實現(xiàn)JS調(diào)用Android的對象和方法
    所以,漏洞產(chǎn)生原因是:當(dāng)JS拿到android這個對象后,就可以調(diào)用這個Android對象中所有的方法,包括系統(tǒng)類(Java.lang.Runtime 類),
從而進(jìn)行任意代碼執(zhí)行。(比如**我們可以執(zhí)行命令獲取本地設(shè)備的SD卡中的文件等信息從而造成信息泄露**)

具體獲取系統(tǒng)類的描述:(結(jié)合 Java 反射機(jī)制)

    1. Android中的對象有一公共的方法:getClass() ;
    1. 該方法可以獲取到當(dāng)前類 類型Class
    1. 該類有一關(guān)鍵的方法: Class.forName;
    1. 該方法可以加載一個類(可加載 java.lang.Runtime 類)
    1. 而該類是可以執(zhí)行本地命令的

以下是攻擊的Js核心代碼:

function execute(cmdArgs)  {  
    // 步驟1:遍歷 window 對象
    // 目的是為了找到包含 getClass ()的對象
    // 因為Android映射的JS對象也在window中,所以肯定會遍歷到
    for (var obj in window) {  
        if ("getClass" in window[obj]) {  
            // 步驟2:利用反射調(diào)用forName()得到Runtime類對象
            alert(obj);          
            return  window[obj].getClass().forName("java.lang.Runtime")  
            // 步驟3:以后,就可以調(diào)用靜態(tài)方法來執(zhí)行一些命令,比如訪問文件的命令
            getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);  
            // 從執(zhí)行命令后返回的輸入流中得到字符串,有很嚴(yán)重暴露隱私的危險。
            // 如執(zhí)行完訪問文件的命令之后,就可以得到文件名的信息了。
        }  
    }  
} 

當(dāng)一些 APP 通過掃描二維碼打開一個外部網(wǎng)頁時,攻擊者就可以執(zhí)行這段 js 代碼進(jìn)行漏洞攻擊。在微信盛行、掃一掃行為普及的情況下,該漏洞的危險性非常大

2.解決方法

Android 4.2版本之后:Google 在Android 4.2 版本中規(guī)定對被調(diào)用的函數(shù)以 @JavascriptInterface進(jìn)行注解從而避免漏洞攻擊

Android 4.2版本之前:采用攔截prompt()進(jìn)行漏洞修復(fù)。
具體步驟如下:

1.繼承 WebView ,重寫 addJavascriptInterface 方法,然后在內(nèi)部自己維護(hù)一個對象映射關(guān)系的 Map ( 將需要添加的 JS 接口放入該Map中 )
2.每次當(dāng) WebView 加載頁面前加載一段本地的 JS 代碼,原理是:
    1) 讓JS調(diào)用一Javascript方法:該方法是通過調(diào)用prompt()把JS中的信息(含特定標(biāo)識,方法名稱等)傳遞到Android端;
    2) 在Android的onJsPrompt()中 ,解析傳遞過來的信息,再通過反射機(jī)制調(diào)用Java對象的方法,這樣實現(xiàn)安全的JS調(diào)用Android代碼。
關(guān)于Android返回給JS的值:可通過prompt()把Java中方法的處理結(jié)果返回到Js中

具體需要加載的JS代碼如下:

 javascript:(function JsAddJavascriptInterface_(){  
    // window.jsInterface 表示在window上聲明了一個Js對象
    // jsInterface = 注冊的對象名
    // 它注冊了兩個方法,onButtonClick(arg0)和onImageClick(arg0, arg1, arg2)
    // 如果有返回值,就添加上return
    if (typeof(window.jsInterface)!='undefined') {      
        console.log('window.jsInterface_js_interface_name is exist!!');}   
    else {  
        window.jsInterface = {     
            // 聲明方法形式:方法名: function(參數(shù))
            onButtonClick:function(arg0) {   
            // prompt()返回約定的字符串
            // 該字符串可自己定義
            // 包含特定的標(biāo)識符MyApp和 JSON 字符串(方法名,參數(shù),對象名等)    
                return prompt('MyApp:'+JSON.stringify({obj:'jsInterface',func:'onButtonClick',args:[arg0]}));  
            },  
            onImageClick:function(arg0,arg1,arg2) {   
                return prompt('MyApp:'+JSON.stringify({obj:'jsInterface',func:'onImageClick',
args:[arg0,arg1,arg2]}));  
            },  
        };  
    }  
}  
)()
// 當(dāng)JS調(diào)用 onButtonClick() 或 onImageClick() 時,就會回調(diào)到Android中的 onJsPrompt ()
// 我們解析出方法名,參數(shù),對象名
// 再通過反射機(jī)制調(diào)用Java對象的方法

關(guān)于采用攔截prompt()進(jìn)行漏洞修復(fù)需要注意的兩點細(xì)節(jié):

細(xì)節(jié)1:加載上述JS代碼的時機(jī)

由于當(dāng) WebView 跳轉(zhuǎn)到下一個頁面時,之前加載的 JS 可能已經(jīng)失效,所以,通常需要在以下方法中加載js:
    onLoadResource();
    doUpdateVisitedHistory();
    onPageStarted();
    onPageFinished();
    onReceivedTitle();
    onProgressChanged();

細(xì)節(jié)2:需要過濾掉 Object 類的方法

由于最終是通過反射得到Android指定對象的方法,所以同時也會得到基類的其他方法(最頂層的基類是 Object類)
為了不把 getClass()等方法注入到 JS 中,我們需要把 Object 的共有方法過濾掉,需要過濾的方法列表如下:
    getClass()
    hashCode()
    notify()
    notifyAl()
    equals()
    toString()
    wait()
  • (2)searchBoxJavaBridge_接口引起遠(yuǎn)程代碼執(zhí)行漏洞

1. 產(chǎn)生原因

1) 在Android 3.0以下,Android系統(tǒng)會默認(rèn)通過searchBoxJavaBridge_的Js接口給 WebView 添加一個JS映射對象:
searchBoxJavaBridge_對象
2) 該接口可能被利用,實現(xiàn)遠(yuǎn)程任意代碼。

2. 解決方法

刪除searchBoxJavaBridge_接口
// 通過調(diào)用該方法刪除接口removeJavascriptInterface();
  • (3)accessibility和 accessibilityTraversal接口引起遠(yuǎn)程代碼執(zhí)行漏洞

1. 產(chǎn)生原因

1) 在Android 3.0以下,Android系統(tǒng)會默認(rèn)通過searchBoxJavaBridge_的Js接口給 WebView 添加一個JS映射對象:
searchBoxJavaBridge_對象
2) 該接口可能被利用,實現(xiàn)遠(yuǎn)程任意代碼。

2. 解決方法

刪除searchBoxJavaBridge_接口
// 通過調(diào)用該方法刪除接口removeJavascriptInterface();

(二)密碼明文存儲漏洞

  • (1)問題分析
//WebView默認(rèn)開啟密碼保存功能 :
mWebView.setSavePassword(true)
開啟后,在用戶輸入密碼時,會彈出提示框:詢問用戶是否保存密碼;
如果選擇”是”,密碼會被明文保到 /data/data/com.package.name/databases/webview.db 中,這樣就有被盜取密碼的危險
  • (2)解決方案
//關(guān)閉密碼保存提醒
WebSettings.setSavePassword(false) 

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

先看Android里的WebViewActivity.java:

public class WebViewActivity extends Activity {
    private WebView webView;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_webview);
        webView = (WebView) findViewById(R.id.webView);

        //webView.getSettings().setAllowFileAccess(false);                    (1)
        //webView.getSettings().setAllowFileAccessFromFileURLs(true);         (2)
        //webView.getSettings().setAllowUniversalAccessFromFileURLs(true);    (3)
        Intent i = getIntent();
        String url = i.getData().toString(); //url = file:///data/local/tmp/attack.html 
        webView.loadUrl(url);
    }
 }

/**Mainifest.xml**/
// 將該 WebViewActivity 在Mainifest.xml設(shè)置exported屬性
// 表示:當(dāng)前Activity是否可以被另一個Application的組件啟動
android:exported="true"
  • (1)問題分析

上述demo中:即 A 應(yīng)用可以通過 B 應(yīng)用導(dǎo)出的 Activity 讓 B 應(yīng)用加載一個惡意的 file 協(xié)議的 url,從而可以獲取 B 應(yīng)用的內(nèi)部私有文件,從而帶來數(shù)據(jù)泄露威脅

具體:當(dāng)其他應(yīng)用啟動此 Activity 時, intent 中的 data 直接被當(dāng)作 url 來加載(假定傳進(jìn)來的 url 為 file:///data/local/tmp/attack.html ),其他 APP 通過使用顯式 ComponentName 或者其他類似方式就可以很輕松的啟動該 WebViewActivity 并加載惡意url。

下面我們著重分析WebView中g(shù)etSettings類的方法對 WebView 安全性的影響:

setAllowFileAccess()
setAllowFileAccessFromFileURLs()
setAllowUniversalAccessFromFileURLs()
  • (2) setAllowFileAccess()
// 設(shè)置是否允許 WebView 使用 File 協(xié)議,默認(rèn)設(shè)置為true,即允許在 File 域下執(zhí)行任意 JavaScript 代碼
webView.getSettings().setAllowFileAccess(true);     

但是同時也限制了 WebView 的功能,使其不能加載本地的 html 文件,( 移動版的 Chrome 默認(rèn)禁止加載 file 協(xié)議的文件 ) ,如下圖:

解決方案:

1) 對于不需要使用 file 協(xié)議的應(yīng)用,禁用 file 協(xié)議;
  setAllowFileAccess(false); 
  
2) 對于需要使用 file 協(xié)議的應(yīng)用,禁止 file 協(xié)議加載 JavaScript。
  setAllowFileAccess(true); 
  // 禁止 file 協(xié)議加載 JavaScript
  if (url.startsWith("file://") {
      setJavaScriptEnabled(false);
  } else {
      setJavaScriptEnabled(true);
  }
  • (3)setAllowFileAccessFromFileURLs()

設(shè)置是否允許通過 file url 加載的 Js代碼讀取其他的本地文件 , 在Android 4.1前默認(rèn)允許 , 在Android 4.1后默認(rèn)禁止

webView.getSettings().setAllowFileAccessFromFileURLs(true);

當(dāng)AllowFileAccessFromFileURLs()設(shè)置為 true 時,攻擊者的JS代碼為 ( 通過該代碼可成功讀取 /etc/hosts 的內(nèi)容數(shù)據(jù) ) :

<script>
    function loadXMLDoc(){
        var arm = "file:///etc/hosts";
        var xmlhttp;
        if (window.XMLHttpRequest){
            xmlhttp=new XMLHttpRequest();
        }
        xmlhttp.onreadystatechange=function(){
            //alert("status is"+xmlhttp.status);
            if (xmlhttp.readyState==4){
                  console.log(xmlhttp.responseText);
            }
        }
        xmlhttp.open("GET",arm);
        xmlhttp.send(null);
    }
    loadXMLDoc();
</script>

解決方案:

設(shè)置setAllowFileAccessFromFileURLs(false);

當(dāng)設(shè)置成為 false 時,上述JS的攻擊代碼執(zhí)行會導(dǎo)致錯誤,表示瀏覽器禁止從 file url 中的 JavaScript 讀取其它本地文件。

  • (4) setAllowUniversalAccessFromFileURLs()

設(shè)置是否允許通過 file url 加載的 Javascript 可以訪問其他的源(包括http、https等源),在Android 4.1前默認(rèn)允許(setAllowFileAccessFromFileURLs()不起作用),在Android 4.1后默認(rèn)禁止

webView.getSettings().setAllowUniversalAccessFromFileURLs(true);

當(dāng)AllowFileAccessFromFileURLs()被設(shè)置成true時,攻擊者的JS代碼是:

// 通過該代碼可成功讀取 http://www.so.com 的內(nèi)容
<script>
    function loadXMLDoc(){
        var arm = "http://www.so.com";
        var xmlhttp;
        if (window.XMLHttpRequest){
            xmlhttp=new XMLHttpRequest();
        }
        xmlhttp.onreadystatechange=function(){
            //alert("status is"+xmlhttp.status);
            if (xmlhttp.readyState==4){
                 console.log(xmlhttp.responseText);
            }
        }
        xmlhttp.open("GET",arm);
        xmlhttp.send(null);
    }
    loadXMLDoc();
</script>

解決方案:

設(shè)置setAllowUniversalAccessFromFileURLs(false);
  • (5) setJavaScriptEnabled()

設(shè)置是否允許 WebView 使用 JavaScript(默認(rèn)是不允許),但很多應(yīng)用(包括移動瀏覽器)為了讓 WebView 執(zhí)行 http 協(xié)議中的 JavaScript,都會主動設(shè)置為true,不區(qū)別對待是非常危險的,如下代碼所示:

webView.getSettings().setJavaScriptEnabled(true);  

即使把setAllowFileAccessFromFileURLs()和setAllowUniversalAccessFromFileURLs()都設(shè)置為 false,通過 file URL 加載的 javascript仍然有方法訪問其他的本地文件:符號鏈接跨源攻擊(前提是允許 file URL 執(zhí)行 javascript,即webView.getSettings().setJavaScriptEnabled(true);)

原因分析:

這一攻擊能奏效的原因是:通過 javascript 的延時執(zhí)行和將當(dāng)前文件替換成指向其它文件的軟鏈接就可以讀取到被符號鏈接所指的文件。

具體攻擊步驟:(在該命令執(zhí)行前 xx.html 是不存在的;執(zhí)行完這條命令之后,就生成了這個文件,并且將 Cookie 文件鏈接到了 xx.html 上。)
1. 把惡意的 js 代碼輸出到攻擊應(yīng)用的目錄下,隨機(jī)命名為 xx.html,修改該目錄的權(quán)限;
2. 修改后休眠 1s,讓文件操作完成;
3. 完成后通過系統(tǒng)的 Chrome 應(yīng)用去打開該 xx.html 文件
4. 等待 4s 讓 Chrome 加載完成該 html,最后將該 html 刪除,并且使用 ln -s 命令為 Chrome 的 Cookie 文件創(chuàng)建軟連接,
于是就可通過鏈接來訪問 Chrome 的 Cookie


注意事項:
  Google 沒有進(jìn)行修復(fù),只是讓Chrome 最新版本默認(rèn)禁用 file 協(xié)議,所以這一漏洞在最新版的 Chrome 中并不存在。
  但是,在日常大量使用 WebView 的App和瀏覽器,都有可能受到此漏洞的影響。通過利用此漏洞,容易出現(xiàn)數(shù)據(jù)泄露的危險
  如果是 file 協(xié)議,禁用 javascript 可以很大程度上減小跨源漏洞對 WebView 的威脅。
  但并不能完全杜絕跨源文件泄露。例:應(yīng)用實現(xiàn)了下載功能,對于無法加載的頁面,會自動下載到 sd 卡中;由于 sd 卡中的文件所有應(yīng)用都可以訪問,于是可以通過構(gòu)造一個 file URL 指向被攻擊應(yīng)用的私有文件,然后用此 URL 啟動被攻擊應(yīng)用的 WebActivity,這樣由于該 WebActivity 無法加載該文件,就會將該文件下載到 sd 卡下面,然后就可以從 sd 卡上讀取這個文件了

  • (6) 最終解決方案

1)對于不需要使用 file 協(xié)議的應(yīng)用,禁用 file 協(xié)議;

// 禁用 file 協(xié)議;
webView.setAllowFileAccess(false); 
webView.setAllowFileAccessFromFileURLs(false);
webView.setAllowUniversalAccessFromFileURLs(false);

2)對于需要使用 file 協(xié)議的應(yīng)用,禁止 file 協(xié)議加載 JavaScript。

// 需要使用 file 協(xié)議
webView.setAllowFileAccess(true); 
webView.setAllowFileAccessFromFileURLs(false);
webView. setAllowUniversalAccessFromFileURLs(false);

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

? 八、WebView 的緩存機(jī)制 & 資源預(yù)加載方案


參考文章:http://blog.csdn.net/carson_ho/article/details/71402764

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。