好友
阅读权限10
听众
最后登录1970-1-1
|
amew
发表于 2018-12-4 11:34
本帖最后由 amew 于 2018-12-4 13:04 编辑
Android App开发安全的一些浅见
国内的许多app,在安全方面除了使用常规的混淆/加固策略以外,很多基本的安全策略并没有很好的得到执行。相对于论坛中Craker 们的矛而言,Developer 们的盾是不是也该更新一下了?本人菜鸟一枚,水平有限,只针对自己在开发中自己遇到的一些常规漏洞做一些阐述,希望能抛砖引玉,大神请指正不足。
1.AllowBackup 漏洞
AllowBackup 漏洞是远古时代为了便于刷机时恢复数据启用的一个功能,但是这个功能也造成了用户数据可以轻易地被轻易盗取(复制备份然后拷贝到新手机上)
adb back -nosystem -f [backup name] com.xxx.xxx
adb restore [backup name]
- 由于
allowbackup 属性在AS 中默认是开启的状态,这个很久之前就存在的漏洞在很长的一段时间里面都存在着,未加注意就有个人信息被泄露的风险,如果在数据持久化上也存在问题的话,可能账号密码什么的就这么不小心被人看见了
- 解决方法很简单:
Manifest 中Application 里面加上:
2.运行环境校验
-
虚拟机
- 对于已经打包上架的
app 而言,在虚拟机环境下运行一般不会有什么好事,许多真机上无法获取的数据/操作能够在虚拟机里面实现,因此我自己的习惯一般是不允许已经release 的app 在虚拟机中运行的
-
对于虚拟机的判定,我用的是下面的3个方法(三个都为false 时认定为虚拟机。主流真机(三星``华为``小米``oppo``一加 )/虚拟机(夜神``Genymotion``原生虚拟机 )亲测有效)
private static String[] known_pipes = {
"/dev/socket/qemud",
"/dev/qemu_pipe"
};
public static boolean checkPipes() {
for (int i = 0; i < known_pipes.length; i++) {
String pipes = known_pipes[i];
File qemu_socket = new File(pipes);
if (qemu_socket.exists()) {
return true;
}
}
return false;
}
private static String[] known_qemu_drivers = {
"goldfish"
};
public static Boolean checkQEmuDriverFile() {
File driver_file = new File("/proc/tty/drivers");
if (driver_file.exists() && driver_file.canRead()) {
byte[] data = new byte[1024]; //(int)driver_file.length()
try {
InputStream inStream = new FileInputStream(driver_file);
inStream.read(data);
inStream.close();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
String driver_data = new String(data);
for (String known_qemu_driver : known_qemu_drivers) {
if (driver_data.indexOf(known_qemu_driver) != -1) {
return true;
}
}
}
return false;
}
private static String[] known_files = {
"/system/lib/libc_malloc_debug_qemu.so",
"/sys/qemu_trace",
"/system/bin/qemu-props"
};
public static boolean checkEmulatorFiles() {
for (int i = 0; i < known_files.length; i++) {
String file_name = known_files[i];
File qemu_file = new File(file_name);
if (qemu_file.exists()) {
return true;
}
}
return false;
}
-
root 环境
root 环境对于app 来讲绝对是一个重大的威胁,在su 用户下所有的私有文件都没有了意义,因此,对于安全要求较高的app ,在业务允许的前提下,我们可以判定在root 环境下禁止运行,或者提示使用者注意安全风险.
-
Android 系统本质上就是一个去除了su 管理员程序的linux 系统。root 的获取也就是想办法强行写入su 的过程,因此推论:检测root 环境也就是检测是否在系统关键位置有su 程序文件
public static boolean checkSuFile() {
Process process = null;
try {
process = Runtime.getRuntime().exec(new String[]{"which", "su"});
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
return in.readLine() != null;
} catch (Throwable t) {
return false;
} finally {
if (process != null) process.destroy();
}
}
public static boolean checkRootFile() {
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;
}
3.签名校验
- 二次打包的危害相信每一个移动开发者都能理解,所以在条件允许的条件下,这一步的工作能做一定是要做的。
-
签名校验最直接的逻辑,就是判断当前APP的签名MD5值是否与我们打包时使用的签名MD5值一致。最简单直接的逻辑示例如下:
```
public static void apkVerifySignature(Activity activity) {
String packageName = activity.getPackageName();
PackageManager pm = activity.getPackageManager();
PackageInfo pi;
String md5;
try {
pi = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
Signature[] s = pi.signatures;
MessageDigest msgDigest = MessageDigest.getInstance("MD5");
msgDigest.reset();
msgDigest.update(s[0].toByteArray());
BigInteger bInt = new BigInteger(1, msgDigest.digest());
md5 = bInt.toString(16);
if (!“我的签名MD5值”.equalsIgnoreCase(md5)) {
activity.finish();
Process.killProcess(Process.myPid());
}
} catch (Exception e) {
e.printStackTrace();
}
}
- 上面的方法最为简单,但是由于自身是
Java 代码中的一个判断,被破解篡改也十分容易。更进一步的方法是把这个判定链接到.so 文件中,利用JNI 来增强安全性,为了进一步保证代码安全,可以在执行时在.so 文件中直接放入逻辑判定:如果判定签名不一致,直接制造一个Crash 来造成程序崩溃。
- 以上两种判定都只是静态代码的本地判断,只能略微增加
Cracker 们的破解难度而已,最保险的方法依然是加入网络判定,让服务器来进行校验签名的正确性,服务端可以拒绝非法服务,也可以返回报文让客户端结束程序自身
4.防截屏
- 二维码本质其实就是一串字符串。其中的信息是在互联网交互时代极容易获得且极容易泄漏的部分。很多场景,如个人信息/支付等,我们是不希望这些信息被轻易获取的,加入二维码防截屏的必要性也因此而来。同样的,密码在输入时被截屏也是一种密码被窃取的路径之一
-
Android 加入防截屏的方法很简单,只需在相应页面加入一行代码即可:
```
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
```
5.密钥
- 密钥不要放在本地
- 密钥不要放在本地
- 密钥不要放在本地
- 重要的事情说三遍。无论采用以下何种方式进行存储,包括但不限于
- 硬编码在
Java 文件中
- 硬编码在
.so 文件中
- 存储为文件
- 存储到
SharedPreference
- 存储到
DataBase
- 都是不安全的
- 以
RSA 交互加密为例,相对安全的交互方式如下:
- 请求服务器获取获取服务器公钥
- 客户端动态生成一对公私钥,将公钥利用服务器的公钥加密传输给服务器
- 服务器使用服务器私钥解密报文,将真正的钥匙利用客户端公钥加密传输给客户端
- 客户端利用客户端私钥解密
response ,得到真正的钥匙,开始业务交互
6.防重放
- 重放攻击是一种常用攻击手段,简单有效。可以是截取客户端的请求后对服务器进行重放攻击,也可以是截取服务器的报文后对客户端进行重放攻击。其中以第一种情况为多(事件响应型交互模型)
- 防止重放攻击的手段也比较多,只需要判定每次交互的报文都不会完全一致就可以了。最通常的做法是利用时间戳+随机数字生成随机码,并入交互报文中即可
小结
以上这些只是个人对App 安全的一些肤浅见解而已,本地文件的存储加密/上传下载文件交互加密/密码键盘防窃听/深度混淆/SSL双向认证等,都没有提及。软件安全的攻与防从来都是矛与盾的共同体,再次希望各路大神能够指正不足
|
免费评分
-
查看全部评分
|
发帖前要善用【论坛搜索】功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。 |
|
|
|
|