内容简介:在现在安卓应用原生开发中,为了追求开发的效率以及移植的便利性,使用WebView作为业务内容展示与交互的主要载体是个不错的折中方案。那么在这种Hybrid(混合式) App中,难免就会遇到页面JS需要与Java相互调用,调用Java方法去做那部分网页JS不能完成的功能。网上的方法可以告诉我们这个时候我们可以使用addjavascriptInterface来注入原生接口到JS中,但是在安卓4.2以下的系统中,这种方案却我们的应用带来了很大的安全风险。攻击者如果在页面执行一些非法的JS(诱导用户打开一些钓鱼网站
在现在安卓应用原生开发中,为了追求开发的效率以及移植的便利性,使用WebView作为业务内容展示与交互的主要载体是个不错的折中方案。
那么在这种Hybrid(混合式) App中,难免就会遇到页面JS需要与 Java 相互调用,调用Java方法去做那部分网页JS不能完成的功能。网上的方法可以告诉我们这个时候我们可以使用addjavascriptInterface来注入原生接口到JS中,但是在安卓4.2以下的系统中,这种方案却我们的应用带来了很大的安全风险。攻击者如果在页面执行一些非法的JS(诱导用户打开一些钓鱼网站以进入风险页面),极有可能反弹拿到用户手机的 shell 权限。接下来攻击者就可以在后台默默安装木马,完全洞穿用户的手机
==那么如何避免呢?我们从以下几个方面进行优化webView.==
1. 谨慎支持JS功能,避免不必要的麻烦
提到对于Android4.2以下的JS任意代码执行漏洞 , 还有包括证书版本过低不安全的因素 . 对于低版本 , 对于2019年未来来说,建议可以放弃支持.
2. 请使用https的链接
- 第一是安全;
- 第二是避免被恶心的运营商劫持,插入广告,影响用户体验
我觉得这个很有必要,不仅仅是因为安全,包括微信公众号,googleplay 都在强制要求开发者必须使用https
3. 处理file协议安全漏洞
//若不需支持,则直接禁止 file 协议 setAllowFileAccess(false); setAllowFileAccessFromFileURLs(false); setAllowUniversalAccessFromFileURLs(false); 复制代码
4. 密码明文保存漏洞
由于webView默认开启密码保存功能,所以在用户输入密码时,会弹出提示框,询问用户是否保存。若选择保存,则密码会以明文形式保存到 ==/data/data/com.package.name/databases/webview.db==中,这样就有被盗取密码的危险。所以我们应该禁止网页保存密码,设置
WebSettings.setSavePassword(false) 复制代码
5. 开启安全浏览模式
<manifest> <meta-data android:name="android.webkit.WebView.EnableSafeBrowsing" android:value="true" /> <application> ... </application> </manifest> 复制代码
启用安全浏览模式后,WebView 将参考安全浏览的恶意软件和钓鱼网站数据库检查访问的 URL ,在用户打开之前给予危险提示,体验类似于Chrome浏览器 . 如果遇到不安全网站,会有如图所示情况
二. 通信安全
这个也是重中之重,大多数的数据安全都是通过网络攻击造成的,那么我们如何去避免呢?
1. 使用HTTPS协议
HTTPS的主要思想是在不安全的网络上创建一安全信道,并可在使用适当的加密包和服务器证书可被验证且可被信任时,对窃听和中间人攻击提供合理的防护。可以说是非常基础的安全防护级别了。
2. 验证证书.
android中实现Https基本就这两种方式,一种是不验证证书,一种是有验证证书(预防钓鱼)。
第二种验证证书稍微复杂一点,这种方式也只能简单的防止钓鱼,不能有效的防止钓鱼。防止钓鱼最终还是靠用户分辨,在正规渠道下载应用。
==但是如果把证书放在apk中也是一件很危险的事情,因为现在的反编译技术不得不服,所以目前觉得最好的方式,就是放在.so文件中.==可以进一步的降低风险 .
3. 通信数据尽量不使用明文形式.
前后端进行自定义算法进行加密,md5 base64 AES RSA 等等,==算法推荐.so和java相互调用的形式==.
4. 防抓包
System.getProperty("http.proxyHost"); System.getProperty("http.proxyPort"); 复制代码
正常这两行代码获取的是null,如果返回不为空,就是挂代理了,那么就可以考虑是否不给数据了
5. token等等进一步的防护措施.
道高一尺,魔高一丈,未来的路还会很长.
三. 数据存储安全
有了数据就得存放,如果存放,就会被别人发现.所以得存好喽
1. 隐藏数据存储位置
在Andoid设备中,以'.'开头文件或者文件夹是不可见的,并且也可以进行读写操作.如果隐藏了存储了文件位置,就可以避免用户误操作和被发现的机会.
2. 存储内容不要使用明文
就算被找到,内容如果以密文的形式,也会降低被泄漏的风险
3. 代码中禁止硬编码重要信息内容
硬编码很容易被反编译找到.建议存储到.so中(虽然.so也可以被破解,但是相较于java更安全点)
4. 存储位置推荐
尽量存储到手机内部存储上,不要存储到外部存储卡上(因为手机内部存储只对相应的应用开放,外部存储对所有的应用都开放)
四. 组件安全
规范安卓标准组件(Activity、Service、Receiver、Provider)的访问权限
1. 设置权限开放属性:android:exported=["true" | "false"]
exported属性为四大组件共有属性,其中含义大同小异。默认值由其包含 ==== 与否决定。若未包含====,默认为“false”,若存在至少一个====,则默认值为“true”。
- 在Activity中:
表示是否允许外部应用组件启动。若为“false”,则 Activity 只能由同一应用或同一用户 ID 的不同应用启动。
- 在Service中:
表示是否允许外部应用组件调用服务或与其进行交互。若为“false”,则 Activity 只能由同一应用或同一用户 ID 的不同应用启动。
- 在Receiver中:
表示是否可以接收来自其应用程序之外的消息,如来自系统或或其他应用的广播。若为“ false”,则广播接收器只能接收具有相同用户ID的相同应用程序或应用程序的组件发送的消息。
- 在Provider中:
表示是否允许其他应用程序访问内容提供器。若为“false”,则具有与Provider相同的用户ID(UID)的应用程序才能访问它。如果需要给其他应用程序提供内容,则应当限定读写权限。
2. 配置自定义权限
自定义权限,限制自己的组件访问,详情看 Android自定义权限与使用
3. 使用更加安全高效的LocalBroadcastManager
区别基于Binder实现的BroadcastReceiver,LocalBroadcastManager 是基于Handler实现的,拥有更高的效率与安全性。安全性主要体现在数据仅限于应用内部传输,避免广播被拦截、伪造、篡改的风险。简单了解下用法:
(1).自定义BroadcastReceiver
public class MyReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { //Do SomeThing Here } } 复制代码
(2).注册Receive
MyReceiver myReceiver = new MyReceiver(); LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this); IntentFilter filter = new IntentFilter(); filter.addAction("MY_ACTION"); localBroadcastManager.registerReceiver(myReceiver, filter); 复制代码
(3).发送本地广播
Bundle bundle = new Bundle(); bundle.putParcelable("DATA", content); Intent intent = new Intent(); intent.setAction("MY_ACTION"); intent.putExtras(bundle); LocalBroadcastManager.getInstance(context).sendBroadcast(intent); 复制代码
(4).在Activity销毁时取消注册
@Override protected void onDestroy() { super.onDestroy(); localBroadcastManager.unregisterReceiver(myReceiver); } 复制代码
4. Application相关属性配置
(1). debugable属性 android:debuggable=["true" | "false"]
很多人说要在发布的时候手动设置该值为false,其实根据官方文档说明,默认值就是false。
(2). allowBackup属性 android:allowBackup=["true" | "false"]
设置是否支持备份,默认值为true,应当慎重支持该属性,避免应用内数据通过备份造成的泄漏问题。
5. 自定义键盘.
对于操作密码等危险输入的情况,可以考虑自定义键盘处理,防止通过键盘被盗用密码.
五. 其他防护措施
多做一层安全措施,少一点风险.
1. 代码安全
- 加固 360加固宝 等等
- 混淆
2. 控制日志输出
自定义 工具 类,不要在线上出现敏感的信息
3. 漏洞检测工具
各种云测平台进行测试
- testin 云测
- 阿里云测 免费的
- 腾讯的 wetest
4. 防止模拟器
判断手机是否包含蓝牙等模块,一些信息是否跟手机真机不一致等.防止通过模拟器篡改信息
5. 二次打包
通过判断签名信息,防止,被二次打包,调试应用信息
6. 账号与设备绑定
账号与相应设备进行绑定,如果发现与常用设备不符合,增加短信登录形式进行重新登录
7. dex文件的校验
重编译apk其实就是重编译了classes.dex文件,重编译后,生成的classes.dex文件的hash值就改变了,因此我们可以通过检测安装后classes.dex文件的hash值来判断apk是否被重打包过。
(1). 读取应用安装目录下/data/app/xxx.apk中的classes.dex文件并计算其哈希值,将该值与软件发布时的classes.dex哈希值做比较来判断客户端是否被篡改。
(2). 读取应用安装目录下/data/app/xxx.apk中的META-INF目录下的MANIFEST.MF文件,该文件详细记录了apk包中所有文件的哈希值,因此可以读取该文件获取到classes.dex文件对应的哈希值,将该值与软件发布时的classes.dex哈希值做比较就可以判断客户端是否被篡改。
为了防止被破解,软件发布时的classes.dex哈希值应该存放在服务器端。
8.调试器检测
为了防止apk被动态调试,可以检测是否有调试器连接。在Application类中提供了isDebuggerConnected()方法用于检测是否有调试器连接,如果发现有调试器连接,可以直接退出程序。
9.是否root
检测是否包含su程序,和ro.secure是否为1,如果root了,可以禁止某些核心功能 检测是否root的代码
public boolean isRoot() { int secureProp = getroSecureProp(); if (secureProp == 0)//eng/userdebug版本,自带root权限 return true; else return isSUExist();//user版本,继续查su文件 } private int getroSecureProp() { int secureProp; String roSecureObj = CommandUtil.getSingleInstance().getProperty("ro.secure"); if (roSecureObj == null) secureProp = 1; else { if ("0".equals(roSecureObj)) secureProp = 0; else secureProp = 1; } return secureProp; } private boolean isSUExist() { File file = null; String[] paths = {"/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su", "/system/bin/failsafe/su", "/data/local/su"}; for (String path : paths) { file = new File(path); if (file.exists()) return true;//可以继续做可执行判断 } return false; } 复制代码
10. 是否装有xposd框架
检测是否安装有xposd框架,如果有提示并隐藏核心功能模块.接口禁用某些功能 所有的方案回归到一点:==判断xposed的包是否存在。== (1).是通过主动抛出异常查栈信息; (2).是主动反射调用。
private static final String XPOSED_HELPERS = "de.robv.android.xposed.XposedHelpers"; private static final String XPOSED_BRIDGE = "de.robv.android.xposed.XposedBridge"; //手动抛出异常,检查堆栈信息是否有xp框架包 public boolean isEposedExistByThrow() { try { throw new Exception("gg"); } catch (Exception e) { for (StackTraceElement stackTraceElement : e.getStackTrace()) { if (stackTraceElement.getClassName().contains(XPOSED_BRIDGE)) return true; } return false; } } //检查xposed包是否存在 public boolean isXposedExists() { try { Object xpHelperObj = ClassLoader .getSystemClassLoader() .loadClass(XPOSED_HELPERS) .newInstance(); } catch (InstantiationException e) { e.printStackTrace(); return true; } catch (IllegalAccessException e) { //实测debug跑到这里报异常 e.printStackTrace(); return true; } catch (ClassNotFoundException e) { e.printStackTrace(); return false; } try { Object xpBridgeObj = ClassLoader .getSystemClassLoader() .loadClass(XPOSED_BRIDGE) .newInstance(); } catch (InstantiationException e) { e.printStackTrace(); return true; } catch (IllegalAccessException e) { //实测debug跑到这里报异常 e.printStackTrace(); return true; } catch (ClassNotFoundException e) { e.printStackTrace(); return false; } return true; } //尝试关闭xp的全局开关,亲测可用 public boolean tryShutdownXposed() { if (isEposedExistByThrow()) { Field xpdisabledHooks = null; try { xpdisabledHooks = ClassLoader.getSystemClassLoader() .loadClass(XPOSED_BRIDGE) .getDeclaredField("disableHooks"); xpdisabledHooks.setAccessible(true); xpdisabledHooks.set(null, Boolean.TRUE); return true; } catch (NoSuchFieldException e) { e.printStackTrace(); return false; } catch (ClassNotFoundException e) { e.printStackTrace(); return false; } catch (IllegalAccessException e) { e.printStackTrace(); return false; } } else return true; } 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 关于使用Koa2的点点滴滴
- Pandas之旅(二): 有关数据清理的点点滴滴
- APK编译及安全防护
- Android App 安全防护总结
- 云基础架构统一安全防护,360虚拟化安全新品重磅来袭
- 未来终端安全防护的发展方向
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。