这个周末我们新开发的app超嘀折交给乌云测试检测,其中WebView 远程代码执行漏洞被反复提出,当时就蒙了,这是个什么东西,在网上查了很多资料,在这里做出记录。
一、 漏洞描述
Android的SDK中提供了的WebView组件,用于在应用中嵌入一个浏览器来进行网页浏览。 WebView组件中的addJavascriptInterface方法可以用于实现本地Java和JavaScript的交互。但是这个方法存在远程代码执行漏洞,远程攻击者利用此漏洞能实现本地java和js的交互,可对Android移动终端进行网页挂马从而控制受影响设备。
简单地说,就是用addJavascriptInterface可能导致不安全,因为JS可能包含恶意代码。当JS包含恶意代码时,它可以干任何事情:访问当前设备的SD卡上面的任何东西,甚至是联系人信息,短信等。
二、 漏洞检测
常用检测漏洞的方法是用webView打开下面的页面,如果当前 app 存在漏洞,将会在页面中输出存在漏洞的接口方便程序员做出修改:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>WebView 漏洞检测</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
</head>
<body>
<p>
<b>如果当前 app 存在漏洞,将会在页面中输出存在漏洞的接口方便程序员做出修改:</b>
</p>
<script type="text/javascript">
function check()
{
for (var obj in window)
{
try {
if ("getClass" in window[obj]) {
try{
window[obj].getClass();
document.write('<span style="color:red">'+obj+'</span>'); document.write('<br />');
}catch(e){
}
}
} catch(e) {
}
}
} check();
</script>
</body>
</html>
check()方法遍历所有window的对象,然后找到包含getClass方法的对象。如果getClass方法能够获取对象,那么就可以利用这个对象的类进行入侵操作。
三、解决方案
1、Android 4.2以上
Android4.2 开始,对于JavaScript代码通过addJavascriptInterface 添加的java 代码的调用做出了限制,只有public并且声明了@JavascriptInterface 的方法才可以被JavaScript代码调用。
@SuppressLint({"JavascriptInterface", "SetJavaScriptEnabled"})
public void showSalesActivity(String url) {
webView.loadUrl(url);
}
2、Android 4.2以下
Android 4.2一下版本就比较不容易解决。主要的思路是动态生成一段声明Javascript方法的JS脚本,通过loadUrl来加载它,从而注册到html页面中,这个js中调用prompt方法,通过prompt把JS中的信息传递给java(这些信息应该是我们组合成的一段有意义的文本,可能包含:特定标识,方法名称,参数等),在onJsPrompt方法中,我们去解析传递过来的文本,得到方法名,参数等,再通过反射机制,调用指定的方法,从而调用到Java对象的方法。
``` python
package com.heshidai.javatojs;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.webkit.JsPromptResult;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import org.json.JSONObject;
public class MainActivity extends AppCompatActivity {
private WebView webView;
private JSCallManager jsCallManager;
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@SuppressLint({"JavascriptInterface"})
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
webView = (WebView) findViewById(R.id.webView);
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
webSettings.setAllowFileAccess(true);//允许访问文件
webSettings.setSupportZoom(true);//设置支持缩放
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);//设置缓存模式(不使用缓存,只从网络获取数据.)
webSettings.setBuiltInZoomControls(true);//启动内置缩放
webSettings.setLoadsImagesAutomatically(true);//自动加载图片
webSettings.setDefaultTextEncodingName("UTF-8");
webSettings.setLoadWithOverviewMode(true);//自适应屏幕
webView.setWebViewClient(new MyWebViewClient());
webView.setWebChromeClient(new MyWebChromeClient());
webView.loadUrl("www.baidu.com");
//JSCallManager本地处理js消息的对象
if (Build.VERSION.SDK_INT >= 17) {
// 在sdk4.2以上的系统上继续使用addJavascriptInterface
webView.addJavascriptInterface(new JSCallManager(this), "Native");
} else {
//4.2之前 addJavascriptInterface有安全泄漏风险
//移除js中的searchBoxJavaBridge_对象,在Android 3.0以下,系统自己添加了一个叫
//searchBoxJavaBridge_的Js接口,要解决这个安全问题,我们也需要把这个接口删除
jsCallManager = new JSCallManager(this);
webView.removeJavascriptInterface("searchBoxJavaBridge_");
// 在 h5开始加载时动态给js注入Native对象和call方法,模拟addJavascriptInterface
//接口给js注入Native对象
//动态注入的好处就是不影响线上的h5数据,不影响ios使用
//在onPageStarted方法中注入是因为在h5的onload方法中有与本地交互的处理
//prompt()方法是js弹出的可输入的提示框
webView.loadUrl("javascript:if(window.Native == undefined){window.Native=\n"+ "{call:function(arg0,arg1){prompt('{\\\"methodName\\\":' + arg0 + ',\\\"jsonValue\\\":' + \n" + "arg1 + '}')}}};\"");
}
}
class MyWebViewClient extends WebViewClient {
//WebViewClient的方法 h5开始加载的回调
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
if (Build.VERSION.SDK_INT < 17) {
// 在 h5开始加载时动态给js注入Native对象和call方法,模拟addJavascriptInterface
//接口给js注入Native对象
//动态注入的好处就是不影响线上的h5数据,不影响ios使用
//在onPageStarted方法中注入是因为在h5的onload方法中有与本地交互的处理
//prompt()方法是js弹出的可输入的提示框
view.loadUrl("javascript:if(window.Native == undefined){" +
"window.Native=\n" +
"{" +
"onButtonClick:function(arg0,arg1){" +
"prompt('{\\\"methodName\\\":' + javaMethod + ',\\\"jsonValue\\\":' + \n" + "jsonValue + '}')" +
"}" +
"}" +
"};");
}
}
}
class MyWebChromeClient extends WebChromeClient {
//当js调用prompt方法时会触发onJsPrompt
//message就是js传给本地的信息
//result.confirm是回传给js的信息
@Override
public boolean onJsPrompt(WebView view, String url, final String message, String defaultValue, JsPromptResult result) {
if (Build.VERSION.SDK_INT < 17) {
new Thread(new Runnable() {
@Override
public void run() {
try {
//message的格式是json
//jsCallManager.call方法是原来处理js响应事件的方法,methodName是指要处理的js事件名称,jsonValue是指要处理的js事件的参数
JSONObject jsonObject = new JSONObject(message);
String methodName = jsonObject.getString("methodName");
String jsonValue = jsonObject.getString("jsonValue");
jsCallManager.call(methodName, jsonValue);
} catch (Exception e) {
}
}
}).start();
}
result.confirm("callBackMessage");//返回给js的去处理的数据
return super.onJsPrompt(view, url, message, defaultValue, result);
}
}
}
需要注意的是:
1】刚开始时在当WebView正常加载URL后去加载Js,但发现会存在问题,如果当WebView跳转到下一个页面时,之前加载的Js就可能无效了,所以需要再次加载。这个问题经过尝试,需要在以下几个方法中加载Js,它们是WebChromeClient和WebViewClient的方法:
onLoadResource
doUpdateVisitedHistory
onPageStarted
onPageFinished
onReceivedTitle
onProgressChanged
目前测试了这几个地方,没什么问题,这里我也不能完全确保没有问题。
【2】需要过滤掉Object类的方法。由于通过反射的形式来得到指定对象的方法,他会把基类的方法也会得到,最顶层的基类就是Object,所以我们为了不把getClass方法注入到Js中,所以我们需要把Object的公有方法过滤掉。这里严格说来,应该有一个需要过滤方法的列表。目前我的实现中,需要过滤的方法有:
“getClass”,
“hashCode”,
“notify”,
“notifyAll”,
“equals”,
“toString”,
“wait”,
3、removeJavascriptInterface
当系统辅助功能中的任意一项服务被开启后,所有由系统提供的WebView都会被加入两个JS objects,分别为是”accessibility” 和 “accessibilityTraversal”。如果APP使用了系统的WebView,并且设置了setJavaScriptEnabled(),那么恶意攻击者就可以使用”accessibility” 和 “accessibilityTraversal” 这两个Java Bridge来执行远程攻击代码。
不同的Android 系统版本的可被攻击性也是不一样的。从API Level 17 (含)也就是Android4.2 开始,对于JavaScript代码通过addJavascriptInterface 添加的java 代码的调用做出了限制,只有public并且声明了@JavascriptInterface 的方法才可以被JavaScript代码调用。所以理论上在Android4.1(含)之前的版本上此漏洞会更容易被利用且有更高的危害。但是经过测试发现,有些手机即使系统版本是4.2,仍然可以利用此漏洞,在JavaScript中调用getClass() 方法。
我们需要通过removeJavascriptInterface(该方法在API 11中才有的,需要做判断)显示的删除accessibilityTraversal、accessibility。同时在Android 3.0以下,系统自己添加了一个叫searchBoxJavaBridge_的Js接口,要解决这个安全问题,我们也需要把这个接口删除。
if (Build.VERSION.SDK_INT >= 11) {
webView.removeJavascriptInterface("searchBoxJavaBridge_");
webView.removeJavascriptInterface("accessibility");
webView.removeJavascriptInterface("accessibilityTraversal");
}
4、不在js中调用java方法
即不使用addJavascriptInterface 也不使用@SuppressLint({“JavascriptInterface”})。通过url拦截的形式去触发java中的方法
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {//此处能拦截超链接的url,即拦截href请求的内容.
if (url.contains("first_access_share")) {//如果包含“first_access_share”拦截跳转
firstAccessShare();//调用对应的方法
return true;
}
view.loadUrl(url);
return true;
}
});
总结
1、WebView 远程代码执行漏洞 在Android4.2之后得到了一定的修复(有的系统中4.2上还是存在),只有public并且声明了@JavascriptInterface 的方法才可以被JavaScript代码调用。
2、Android4.2以下如果需要在JavaScript代码调用,就需要比较复杂的方法。主要的思路是动态生成一段声明Javascript方法的JS脚本,通过loadUrl来加载它,从而注册到html页面中,这个js中调用prompt方法,通过prompt把JS中的信息传递给java(这些信息应该是我们组合成的一段有意义的文本,可能包含:特定标识,方法名称,参数等),在onJsPrompt方法中,我们去解析传递过来的文本,得到方法名,参数等,再通过反射机制,调用指定的方法,从而调用到Java对象的方法。
3、上述两种情况还是有漏洞需要显示的用 webView.removeJavascriptInterface去掉一些方法searchBoxJavaBridge_、accessibility、accessibilityTraversal
4、最后的方法就是不要在JavaScript中调用java方法,使用url拦截的形式去触发(我的项目中使用的这种方法)
本文参考了一下文章
http://blog.csdn.net/leehong2005/article/details/11808557
http://seclab.safe.baidu.com/2014-10/android-webview-cve-2014-7224.html
http://blog.csdn.net/zhouyongyang621/article/details/47000041