android WebView详解,常见漏洞详解和安全源码(下)
上篇博客主要分析了 WebView 的詳細(xì)使用,這篇來分析 WebView 的常見漏洞和使用的坑。?
上篇:android WebView詳解,常見漏洞詳解和安全源碼(上)?
轉(zhuǎn)載請注明出處:http://blog.csdn.net/self_study/article/details/55046348?
對技術(shù)感興趣的同鞋加群 544645972 一起交流。
WebView 常見漏洞
WebView 的漏洞也是不少,列舉一些常見的漏洞,實(shí)時更新,如果有其他的常見漏洞,知會一下我~~
WebView 任意代碼執(zhí)行漏洞
已知的 WebView 任意代碼執(zhí)行漏洞有 4 個,較早被公布是?CVE-2012-6636,揭露了 WebView 中 addJavascriptInterface 接口會引起遠(yuǎn)程代碼執(zhí)行漏洞。接著是?CVE-2013-4710,針對某些特定機(jī)型會存在 addJavascriptInterface API 引起的遠(yuǎn)程代碼執(zhí)行漏洞。之后是?CVE-2014-1939?爆出 WebView 中內(nèi)置導(dǎo)出的 “searchBoxJavaBridge_” Java Object 可能被利用,實(shí)現(xiàn)遠(yuǎn)程任意代碼。再后來是?CVE-2014-7224,類似于?CVE-2014-1939?,WebView 內(nèi)置導(dǎo)出 “accessibility” 和 “accessibilityTraversal” 兩個 Java Object 接口,可被利用實(shí)現(xiàn)遠(yuǎn)程任意代碼執(zhí)行。
一般情況下,WebView 使用 Javascript 腳本的代碼如下所示:
- 1
- 2
- 3
- 4
- 5
CVE-2012-6636?和?CVE-2013-4710
Android 系統(tǒng)為了方便 APP 中 Java 代碼和網(wǎng)頁中的 Javascript 腳本交互,在 WebView 控件中實(shí)現(xiàn)了 addJavascriptInterface 接口,如上面的代碼所示,我們來看一下這個方法的官方描述:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 1
- 2
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
所以當(dāng)一些 APP 通過掃描二維碼打開一個外部網(wǎng)頁的時候,就可以執(zhí)行這段 js 代碼,漏洞在 2013 年 8 月被披露后,很多 APP 都中招,其中瀏覽器 APP 成為重災(zāi)區(qū),但截至目前仍有很多 APP 中依然存在此漏洞,與以往不同的只是攻擊入口發(fā)生了一定的變化。另外一些小廠商的 APP 開發(fā)團(tuán)隊(duì)因?yàn)槿狈Π踩庾R,依然還在APP中隨心所欲的使用 addJavascriptInterface 接口,明目張膽踩雷。
出于安全考慮,Google 在 API17 版本中就規(guī)定能夠被調(diào)用的函數(shù)必須以 @JavascriptInterface 進(jìn)行注解,理論上如果 APP 依賴的 API 為 17(Android 4.2)或者以上,就不會受該問題的影響,但在部分低版本的機(jī)型上,API17 依然受影響,所以危害性到目前為止依舊不小。關(guān)于所有 Android 機(jī)型的占比,可以看看 Google 的?Dashboards:
?
截止 2017/1/9 日,可以看到 android5.0 之下的手機(jī)依舊不少,需要重視。
漏洞的解決
但是這個漏洞也是有解決方案的,上面的很多地方也都提到了這個漏洞,那么這個漏洞怎么去解決呢?這就需要用到 onJsPrompt 這個方法了,這里先給出解決這個漏洞的具體步驟,在下面的源碼部分有修復(fù)這個漏洞的詳細(xì)代碼:
- 繼承 WebView ,重寫 addJavascriptInterface 方法,然后在內(nèi)部自己維護(hù)一個對象映射關(guān)系的 Map,當(dāng)調(diào)用 addJavascriptInterface 方法,將需要添加的 JS 接口放入這個 Map 中;
- 每次當(dāng) WebView 加載頁面的時候加載一段本地的 JS 代碼:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
這段 JS 代碼定義了注入的格式,其中的 XXX 為注入對象的方法名字,終端和 web 端只要按照定義的格式去互相調(diào)用即可,如果這個對象有多個方法,則會注冊多個 window.XXX_js_interface_name 塊;
然后在 prompt 中返回我們約定的字符串,當(dāng)然這個字符串也可以自己重新定義,它包含了特定的標(biāo)識符 MyApp,后面包含了一串 JSON 字符串,它包含了方法名,參數(shù),對象名等;當(dāng) JS 調(diào)用 XXX 方法的時候,就會調(diào)用到終端 Native 層的 OnJsPrompt 方法中,我們再解析出方法名,參數(shù),對象名等,解析出來之后進(jìn)行相應(yīng)的處理,同時返回值也可以通過 prompt 返回回去;window.XXX_js_interface_name 代表在 window 上聲明了一個對象,聲明的方式是:方法名:function(參數(shù)1,參數(shù)2)。還有一個問題是什么時候加載這段 JS 呢,在 WebView 正常加載 URL 的時候去加載它,但是會發(fā)現(xiàn)當(dāng) WebView 跳轉(zhuǎn)到下一個頁面時,之前加載的 JS 可能就已經(jīng)無效了,需要再次加載,所以通常需要在一下幾個方法中加載 JS,這幾個方法分別是 onLoadResource,doUpdateVisitedHistory,onPageStarted,onPageFinished,onReceivedTitle,onProgressChanged。?通過這幾步,就可以簡單的修復(fù)漏洞問題,但是還需要注意幾個問題,需要過濾掉 Object 類的方法,由于通過反射的形式來得到指定對象的方法,所以基類的方法也可以得到,最頂層的基類就是 Object,為了不把 getClass 等方法注入到 JS 中,我們需要把 Object 的共有方法過濾掉,需要過濾的方法列表如下:“getClass”,“hashCode”,“notify”,“notifyAll”,“equals”,“toString”,“wait”,具體的代碼實(shí)現(xiàn)可以看看下面的源碼。
CVE-2014-1939
在 2014 年發(fā)現(xiàn)在 Android4.4 以下的系統(tǒng)中,webkit 中默認(rèn)內(nèi)置了 “searchBoxJavaBridge_”,代碼位于 “java/android/webkit/BrowserFrame.java”,該接口同樣存在遠(yuǎn)程代碼執(zhí)行的威脅,所以就算沒有通過 addJavascriptInterface 加入任何的對象,系統(tǒng)也會加入一個 searchBoxJavaBridge_ 對象,解決辦法就是通過 removeJavascriptInterface 方法將對象刪除。
CVE-2014-7224
在 2014 年,研究人員 Daoyuan Wu 和 Rocky Chang 發(fā)現(xiàn),當(dāng)系統(tǒng)輔助功能服務(wù)被開啟時,在 Android4.4 以下的系統(tǒng)中,由系統(tǒng)提供的 WebView 組件都默認(rèn)導(dǎo)出 ”accessibility” 和 ”accessibilityTraversal” 這兩個接口,代碼位于 “android/webkit/AccessibilityInjector.java”,這兩個接口同樣存在遠(yuǎn)程任意代碼執(zhí)行的威脅,同樣的需要通過 removeJavascriptInterface 方法將這兩個對象刪除。
WebView 密碼明文存儲漏洞
WebView 默認(rèn)開啟密碼保存功能 mWebView.setSavePassword(true),如果該功能未關(guān)閉,在用戶輸入密碼時,會彈出提示框,詢問用戶是否保存密碼,如果選擇”是”,密碼會被明文保到 /data/data/com.package.name/databases/webview.db 中,這樣就有被盜取密碼的危險(xiǎn),所以需要通過 WebSettings.setSavePassword(false) 關(guān)閉密碼保存提醒功能。
WebView 域控制不嚴(yán)格漏洞
要了解 WebView 中 file 協(xié)議的安全性,我們這里用一個簡單的例子來演示一下,這個 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
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 可以設(shè)置是否允許 WebView 使用 File 協(xié)議,Android 中默認(rèn) setAllowFileAccess(true),所以默認(rèn)值是允許,在 File 域下,能夠執(zhí)行任意的 JavaScript 代碼,?同源策略跨域訪問則能夠?qū)λ接心夸浳募M(jìn)行訪問,APP 嵌入的 WebView 未對 file:/// 形式的 URL 做限制,所以使用 file 域加載的 js 能夠使用同源策略跨域訪問導(dǎo)致隱私信息泄露,針對 IM 類軟件會導(dǎo)致聊天信息、聯(lián)系人等等重要信息泄露,針對瀏覽器類軟件,則更多的是 cookie 信息泄露。如果不允許使用 file 協(xié)議,則不會存在下面將要講到的各種跨源的安全威脅,但同時也限制了 WebView 的功能,使其不能加載本地的 html 文件。禁用 file 協(xié)議后,讓 WebViewActivity 打開 attack.html 會得到如下圖所示的輸出,圖中所示的文件是存在的,但 WebView 禁止加載此文件,移動版的 Chrome 默認(rèn)禁止加載 file 協(xié)議的文件。
那么怎么解決呢,不要著急,繼續(xù)往下看。
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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
setAllowUniversalAccessFromFileURLs
通過此 API 可以設(shè)置是否允許通過 file url 加載的 Javascript 可以訪問其他的源,包括其他的文件和 http,https 等其他的源。這個設(shè)置在 JELLY_BEAN 以前的版本默認(rèn)是允許,在 JELLY_BEAN 及以后的版本中默認(rèn)是禁止的。如果此設(shè)置是允許,則 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
- 1
- 2
- 3
以上漏洞的初步解決方案
通過以上的介紹,初步的方案是使用下面的代碼來杜絕:
setAllowFileAccess(true); //設(shè)置為 false 將不能加載本地 html 文件 setAllowFileAccessFromFileURLs(false); setAllowUniversalAccessFromFileURLs(false);- 1
- 2
- 3
使用符號鏈接跨源
為了安全的使用 WebView,AllowUniversalAccessFromFileURLs 和 AllowFileAccessFromFileURLs 都應(yīng)該設(shè)置為禁止,在 JELLY_BEAN(android 4.1) 及以后的版本中這兩項(xiàng)設(shè)置默認(rèn)也是禁止的,但是即使把這兩項(xiàng)都設(shè)置為 false,通過 file URL 加載的 javascript 仍然有方法訪問其他的本地文件,通過符號鏈接攻擊可以達(dá)到這一目的,前提是允許 file URL 執(zhí)行 javascript。這一攻擊能奏效的原因是無論怎么限制 file 協(xié)議的同源檢查,其 javascript 都應(yīng)該能訪問當(dāng)前的文件,通過 javascript 的延時執(zhí)行和將當(dāng)前文件替換成指向其它文件的軟鏈接就可以讀取到被符號鏈接所指的文件,具體攻擊步驟見?Chromium bug 144866,下面也貼出了代碼和詳解。因?yàn)?Chrome 最新版本默認(rèn)禁用 file 協(xié)議,所以這一漏洞在最新版的 Chrome 中并不存在,Google 也并沒有修復(fù)它,但是大量使用 WebView 的應(yīng)用和瀏覽器,都有可能受到此漏洞的影響,通過利用此漏洞,無特殊權(quán)限的惡意 APP 可以盜取瀏覽器的任意私有文件,包括但不限于 Cookie、保存的密碼、收藏夾和歷史記錄,并可以將所盜取的文件上傳到攻擊者的服務(wù)器。下圖為通過 file URL 讀取某手機(jī)瀏覽器 Cookie 的截圖:
截圖將 Cookie alert 出來了,實(shí)際情況可以上傳到服務(wù)器,攻擊的詳細(xì)代碼如下所示:
- 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
setJavaScriptEnabled
通過此 API 可以設(shè)置是否允許 WebView 使用 JavaScript,默認(rèn)是不允許,但很多應(yīng)用,包括移動瀏覽器為了讓 WebView 執(zhí)行 http 協(xié)議中的 JavaScript,都會主動設(shè)置允許 WebView 執(zhí)行 JavaScript,而又不會對不同的協(xié)議區(qū)別對待,比較安全的實(shí)現(xiàn)是如果加載的 url 是 http 或 https 協(xié)議,則啟用 JavaScript,如果是其它危險(xiǎn)協(xié)議,比如是 file 協(xié)議,則禁用 JavaScript。如果是 file 協(xié)議,禁用 javascript 可以很大程度上減小跨源漏洞對 WebView 的威脅,但是此時禁用 JavaScript 的執(zhí)行并不能完全杜絕跨源文件泄露。例如,有的應(yīng)用實(shí)現(xiàn)了下載功能,對于加載不了的頁面,會自動下載到 sd 卡中,由于 sd 卡中的文件所有應(yīng)用都可以訪問,于是可以通過構(gòu)造一個 file URL 指向被攻擊應(yīng)用的私有文件,然后用此 URL 啟動被攻擊應(yīng)用的 WebActivity,這樣由于該 WebActivity 無法加載該文件,就會將該文件下載到 sd 卡下面,然后就可以從 sd 卡上讀取這個文件了,當(dāng)然這種應(yīng)用比較少,這個也算是應(yīng)用自身無意產(chǎn)生的一個漏洞吧。
以上漏洞的解決方案
針對 WebView 域控制不嚴(yán)格漏洞的安全建議如下:
- 1
- 2
- 3
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
開發(fā)中遇見的坑
這里記錄一下開發(fā)中遇到的一些坑和解決辦法:
loadData() 方法
我們可以通過使用?WebView.loadData(String data, String mimeType, String encoding)?方法來加載一整個 HTML 頁面的一小段內(nèi)容,第一個就是我們需要 WebView 展示的內(nèi)容,第二個是我們告訴 WebView 我們展示內(nèi)容的類型,一般,第三個是字節(jié)碼,但是使用的時候,這里會有一些坑,我們來看一個簡單的例子:
String html = new String("<h3>我是loadData() 的標(biāo)題</h3><p>  我是他的內(nèi)容</p>"); webView.loadData(html, "text/html", "UTF-8");- 1
- 2
這里的邏輯很簡單,加載一個簡單的富文本標(biāo)簽,我們看看運(yùn)行后的效果:
可以注意到這里顯示成亂碼了,可是明明已經(jīng)指定了編碼格式為 UTF-8 啊,可是這就是使用的坑,我們需要將代碼進(jìn)行修改:
String html = new String("<h3>我是loadData() 的標(biāo)題</h3><p>  我是他的內(nèi)容</p>"); webView.loadData(html, "text/html;charset=UTF-8", "null");- 1
- 2
我們再來看看顯示效果:
這樣我們就可以看到正確的內(nèi)容了,Google 還指出,在我們這種加載的方法下,我們的 Data 數(shù)據(jù)里不能出現(xiàn) ’#’, ‘%’, ‘\’ , ‘?’ 這四個字符,如果出現(xiàn)了我們要用 %23, %25, %27, %3f 對應(yīng)來替代,網(wǎng)上列舉了未將特定字符轉(zhuǎn)義過程中遇到的異常現(xiàn)象:
A) % 會報(bào)找不到頁面錯誤,頁面全是亂碼。 B) # 會讓你的 goBack 失效,但 canGoBAck 是可以使用的,于是就會產(chǎn)生返回按鈕生效,但不能返回的情況。 C) \ 和 ? 在轉(zhuǎn)換時,會報(bào)錯,因?yàn)樗鼤?\ 當(dāng)作轉(zhuǎn)義符來使用,如果用兩級轉(zhuǎn)義,也不生效。- 1
- 2
- 3
頁面空白
當(dāng) WebView 嵌套在 ScrollView 里面的時候,如果 WebView 先加載了一個高度很高的網(wǎng)頁,然后加載了一個高度很低的網(wǎng)頁,就會造成 WebView 的高度無法自適應(yīng),底部出現(xiàn)大量空白的情況出現(xiàn),具體的可以看看我以前的博客:android ScollView 嵌套 WebView 底部空白,高度無法自適應(yīng)解決。
內(nèi)存泄漏
WebView 的內(nèi)存泄漏是一個比較大的問題,尤其是當(dāng)加載的頁面比較龐大的時候,解決方法網(wǎng)上也比較多,但是看情況大部分都不是能徹底根治的,這里說一下 QQ 和微信的做法,每當(dāng)打開一個 WebView 界面的時候,會開啟一個新進(jìn)程,在頁面退出之后通過 System.exit(0) 關(guān)閉這個進(jìn)程,這樣就不會存在內(nèi)存泄漏的問題了,具體的做法可以查看這篇博客:Android WebView Memory Leak WebView內(nèi)存泄漏,里面也提供了另外一種解決辦法,感興趣的可以去看一下。
setBuiltInZoomControls 引起的 Crash
當(dāng)使用 mWebView.getSettings().setBuiltInZoomControls(true) 啟用該設(shè)置后,用戶一旦觸摸屏幕,就會出現(xiàn)縮放控制圖標(biāo)。這個圖標(biāo)過上幾秒會自動消失,但在 3.0 之上 4.4 系統(tǒng)之下很多手機(jī)會出現(xiàn)這種情況:如果圖標(biāo)自動消失前退出當(dāng)前 Activity 的話,就會發(fā)生 ZoomButton 找不到依附的 Window 而造成程序崩潰,解決辦法很簡單就是在 Activity 的 onDestory 方法中調(diào)用 mWebView.setVisibility(View.GONE); 方法,手動將其隱藏,就不會崩潰了。
后臺無法釋放 JS 導(dǎo)致耗電
如果 WebView 加載的的 html 里有一些 JS 一直在執(zhí)行比如動畫之類的東西,如果此刻 WebView 掛在了后臺,這些資源是不會被釋放,用戶也無法感知,導(dǎo)致一直占有 CPU 增加耗電量,如果遇到這種情況,在 onStop 和 onResume 里分別把 setJavaScriptEnabled() 給設(shè)置成 false 和 true 即可。
4.4 版本之后 loadUrl 加載 js 傳遞 url 自動轉(zhuǎn)義
在開發(fā)中遇到過一個需求是在 WebView 中需要調(diào)用前端的 js 腳本處理一段 url,js 解析完這段 url 之后再把結(jié)果交由本地進(jìn)行處理,但是遇到一個問題是,比如一個 url 為 “https://www.aaaa.com/bb?param1=3333%23444¶m2=555%40666“,大家都知道 url 中如果存在類似“#@”這種特殊符號的時候就需要 encode 成 23% 和 40%,這樣才是一個符合要求的 url,要不然 “https://www.aaaa.com/bb?param1=3333#444¶m2=555@666”這個 url 是一個非法的 url,但是當(dāng)時將這段合法的 url 通過 loadUrl(“javascript:xxxxx”) 的方式傳遞給 js 的相關(guān)函數(shù)進(jìn)行處理,js 端獲取到 url 被自動轉(zhuǎn)義成了“https://www.aaaa.com/bb?param1=3333#444¶m2=555@666“,去 google 了很久才發(fā)現(xiàn)這個問題原來是 android 自帶的一個問題,見?issue:36995865,原來在 4.4 之前系統(tǒng)的 WebView 不會自動 decode,但是 4.4 和之后的系統(tǒng)上通過 loadUrl 傳遞的東西會自動將 %23 等 decode 成 #,這樣就造成在 4.4 之后通過 loadUrl 加載一段 js,傳遞一段 url,如果 url 里面有 # @ 等非法字符的時候就會造成 js 端獲取到的 url 非法,無法正常解析,解決辦法就是在 4.4 版本之后使用 evaluateJavascript 這個函數(shù),這個函數(shù)也正好是 4.4 版本引入的,使用 evaluateJavascript 這個函數(shù)傳遞 url,就不會自動 decode,js 函數(shù)獲取到的 url 仍然是轉(zhuǎn)義后的 %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";/*** 函數(shù)名*/private static final String KEY_FUNCTION_NAME = "func";/*** 參數(shù)數(shù)組*/private static final String KEY_ARG_ARRAY = "args";/*** 要過濾的方法數(shù)組*/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 初始化,設(shè)置監(jiān)聽,刪除部分Android默認(rèn)注冊的JS接口*/private void init() {setWebChromeClient(new WebChromeClientEx());setWebViewClient(new WebViewClientEx());safeSetting();removeUnSafeJavascriptImpl();}/*** 安全性設(shè)置*/private void safeSetting() {getSettings().setSavePassword(false);getSettings().setAllowFileAccess(false);//設(shè)置為 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,漏洞已經(jīng)被解決,直接調(diào)用基類的 addJavascriptInterface* 如果版本小于 4.2,則使用map緩存待注入對象*/@SuppressLint("JavascriptInterface")@Overridepublic void addJavascriptInterface(Object obj, String interfaceName) {if (TextUtils.isEmpty(interfaceName)) {return;}// 如果在4.2以上,直接調(diào)用基類的方法來注冊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 之后,都需要重新構(gòu)造 JS 注入mJsStringCache = null;injectJavascriptInterfaces();}}/*** 如果 WebView 是 SafeWebView 類型,則向 JavaScript Context 注入對象,確保 WebView 是有安全機(jī)制的*/private void injectJavascriptInterfaces(WebView webView) {if (webView instanceof SafeWebView) {injectJavascriptInterfaces();}}/*** 注入我們構(gòu)造的 JS*/private void injectJavascriptInterfaces() {if (!TextUtils.isEmpty(mJsStringCache)) {loadUrl(mJsStringCache);return;}mJsStringCache = genJavascriptInterfacesString();loadUrl(mJsStringCache);}/*** 根據(jù)緩存的待注入java對象,生成映射的JavaScript代碼,也就是橋梁(SDK4.2之前通過反射生成)*/private String genJavascriptInterfacesString() {if (mJsInterfaceMap.size() == 0) {return null;}/** 要注入的JS的格式,其中XXX為注入的對象的方法名,例如注入的對象中有一個方法A,那么這個XXX就是A* 如果這個對象中有多個方法,則會注冊多個window.XXX_js_interface_name塊,我們是用反射的方法遍歷* 注入對象中的帶有@JavaScripterInterface標(biāo)注的方法** 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對象,生成相應(yīng)的js對象try {while (iterator.hasNext()) {Map.Entry<String, Object> entry = iterator.next();String interfaceName = entry.getKey();Object obj = entry.getValue();// 生成相應(yīng)的js方法createJsMethod(interfaceName, obj, script);}} catch (Exception e) {e.printStackTrace();}// Endscript.append("})()");return script.toString();}/*** 根據(jù)待注入的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("={");// 通過反射機(jī)制,添加java對象的方法Method[] methods = objClass.getMethods();for (Method method : methods) {String methodName = method.getName();// 過濾掉Object類的方法,包括getClass()方法,因?yàn)樵贘s中就是通過getClass()方法來得到Runtime實(shí)例if (filterMethods(methodName)) {continue;}script.append(" ").append(methodName).append(":function(");// 添加方法的參數(shù)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(":[");// 添加參數(shù)到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;}/*** 利用反射,調(diào)用java對象的方法。* <p>* 從緩存中取出key=interfaceName的java對象,并調(diào)用其methodName方法** @param result* @param interfaceName 對象名* @param methodName 方法名* @param args 參數(shù)列表* @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); // 執(zhí)行接口調(diào)用boolean isVoid = returnObj == null || returnObj.getClass() == void.class;String returnValue = isVoid ? "" : returnObj.toString();result.confirm(returnValue); // 通過prompt返回調(diào)用結(jié)果succeed = true;} catch (NoSuchMethodException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();}result.cancel();return succeed;}/*** 解析出參數(shù)類型** @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調(diào)用prompt的參數(shù)message,提取出對象名、方法名,以及參數(shù)列表,再利用反射,調(diào)用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);// 參數(shù)數(shù)組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 :該函數(shù)用來在特定版本刪除上面提到的幾個 Google 注入的對象;
- setWebViewClient 和 setWebChromeClient :重寫這兩個函數(shù)用來防止子類使用原生的 WebViewClient 和 WebChromeClient 導(dǎo)致失效;
- 在上面提到的 onLoadResource,doUpdateVisitedHistory,onPageStarted,onPageFinished,onReceivedTitle,onProgressChanged 幾個方法里面調(diào)用 injectJavascriptInterfaces 方法來注入生成的 JS 代碼;
- genJavascriptInterfacesString 函數(shù)用來生成需要注入的 JS 代碼,其中通過 filterMethods 方法過濾掉了上面提到的幾個需要過濾的方法;
- 注入完 JS 之后,Web 端就可以根據(jù)方法名調(diào)用對應(yīng)終端注入的這段 JS 函數(shù),然后調(diào)用到終端的 onJsPrompt 方法,通過 message 變量將信息傳遞過來,終端解析出對象、方法名和參數(shù),最后通過反射的方法調(diào)用到 Native 層的代碼,另外如果需要返回值,則可以通過 JsPromptResult 對象通過 confirm 函數(shù)將信息從 Native 層傳遞給 Web 端,這樣就實(shí)現(xiàn)了一個完整的調(diào)用鏈。
引用
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
總結(jié)
以上是生活随笔為你收集整理的android WebView详解,常见漏洞详解和安全源码(下)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android WebView详解,常见
- 下一篇: Android:你不知道的 WebVie