收集整理网上的资料以及自己测试过程中遇到的问题

前言

APP 安全一直是开发者头痛的事情,越来越多的安全漏洞,使得开发者

越来越重视app安全,目前app安全主要有由以下几部分

<>APP组件安全

Android 包括四大组件:Activitie、Service、Content Provider、Broadband Receiver, 
它们每一个都可以通过外面隐式的Intent方式打开, 
android组件对外开放 就会被其他程序劫持,因此必须在manifest里面声明 
exported为false,禁止其他程序访问改组件, 
对于要和外部交互的组件,应当添加一下访问权限的控制, 在有权限后外部程序才可以开启,或者提供特定的action过滤器达到启动目的。 
还需要要对传递的数据进行安全的校验。不符合规则的一律不处理。

<>Webview 安全漏洞

Android API 4.4以前,谷歌的webview存在安全漏洞,网站可以通过js注入就可以随便拿到客户端的重要信息, 
甚至轻而易举的调用本地代码进行流氓行为,谷歌后来发现有此漏洞后 
,在API 4.4以后增加了防御措施,如果用js调用本地代码,开发者必须在代码申明JavascriptInterface, 
列如在4.0之前我们要使得webView加载js只需如下代码:

mWebView.addJavascriptInterface(new JsToJava(), “myjsfunction”);

4.4之后使用需要在调用Java方法加入@JavascriptInterface注解, 
如果代码无此申明,那么也就无法使得js生效,也就是说这样就可以避免恶意网页利用js对客户端的进行窃取和攻击。

<>APP反编译

App被反编后,源码暴露,不仅对数据造成隐私泄露,而且对一些接口造成易攻击的潜在风险。 
我们必须对apk进行源码混淆,并且进行apk加固

<>APP二次打包

即反编译后重新加入恶意的代码逻辑,或置入新病毒重新生成一个新APK文件。 

二次的目的一般都是是盈利广告和病毒结合,对正版apk进行解包,插入恶意病毒后重新打包并发布,因此伪装性很强。截住app重打包就一定程度上防止了病毒的传播。因此app加固是防止二次打包的重要措施。

<>APP进程劫持

一般我们称为进程注入,也就动态注入技术,hook技术目前主流的进程注入方式,通过对linux进行so注入,达到挂钩远程函数实现监控远程进程的目的。

<>APP DNS劫持


DNS劫持俗称抓包。通过对url的二次劫持,修改参数和返回值,进而进行对app访问web数据伪装,实现注入广告和假数据,甚至起到导流用户的作用,严重的可以通过对登录APi的劫持可以获取用户密码,也可以对app升级做劫持,下载病毒apk等目的,解决方法一般用https进行传输数据。

<>APP APi接口验签


一般服务端的接口也会被攻击,虽然是服务端安全问题,但还是属于App系统维护体系,如果app后端挂了,app也不叫app了,一般变现为被恶意程序频繁请求某一接口,导致订单等重复注入,验证码被盗刷,虚假用户注册,严重的甚至拖垮app.

今天先看下APP升级过程被劫持的问题

我们做app版本升级时一般流程是采用请求升级接口,如果有升级,服务端返回下一个下载地址,下载好Apk后,再点击安装。 
其实这个过程中有三个地方会被劫持。 请求升级时,下载文件时,安装时。

* 升级APi
升级Api建议用https,防止被恶意程序劫持,结果是恶意返回下载地址,这样就把伪装的apk下载到本地,结果你应该懂的!

*
下载API:

如果升级api你做了加固,下载api没做加固,还是徒劳,恶意程序也可以返回恶意文件或者apk,直到被你错误的安装在手机上。

* 安装过程;
假设你以上两个过程都做了加固,但是在安装apk的时候,本地文件path被错误修改了,仍然可以安装错误的apk,这不仅 
会对用户体验产生不利,甚至会威胁手机安全。

<>解决方案:

*
升级api加入https 这个肯定不用再过多介绍,看了我介绍的retrofit 的https就明白了。

*
下载Api也需加入https,也不用再做介绍,这里着重强调的是需要对服务端返回的文件进行Hash值校验,防止文件被篡改, 
通过对文件hash值,还要对服务端返回的自定义key的进行校验验签,防止不是自己服务器返回错误的文件

*
安装过程也必须对Apk文件进行包名和签名验证,防止Apk被恶意植入木马,或替换。

假设我的升级bean为;
public class UpgradeModel { private int code; private String msg; private
DataBean data; public int getCode() { return code; } public void setCode(int
code) { this.code = code; } public String getMsg() { return msg; } public void
setMsg(String msg) { this.msg = msg; } public DataBean getData() { return data;
} public void setData(DataBean data) { this.data = data; } public static class
DataBean { private String description; private String downUrl; private String
version; private String hashcod; private String key; private String isForce;
public String getDescription() { return description; } public void
setDescription(String description) { this.description = description; } public
String getDownUrl() { return downUrl; } public void setDownUrl(String downUrl)
{ this.downUrl = downUrl; } public String getVersion() { return version; }
public void setVersion(String version) { this.version = version; } public
String getHashcod() { return hashcod; } public void setHashcod(String hashcod)
{ this.hashcod = hashcod; } public String getKey() { return key; } public void
setKey(String key) { this.key = key; } public String getIsForce() { return
isForce; } public void setIsForce(String isForce) { this.isForce = isForce; } }
* 1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
* 9
* 10
* 11
* 12
* 13
* 14
* 15
* 16
* 17
* 18
* 19
* 20
* 21
* 22
* 23
* 24
* 25
* 26
* 27
* 28
* 29
* 30
* 31
* 32
* 33
* 34
* 35
* 36
* 37
* 38
* 39
* 40
* 41
* 42
* 43
* 44
* 45
* 46
* 47
* 48
* 49
* 50
* 51
* 52
* 53
* 54
* 55
* 56
* 57
* 58
* 59
* 60
* 61
* 62
* 63
* 64
* 65
* 66
* 67
* 68
* 69
* 70
* 71
* 72
* 73
* 74
* 75
* 76
* 77
* 78
* 79
* 80
* 81
* 82
* 83
* 84
* 85
* 86
* 87
* 88
}

通过一次请求到服务端数据后,如果有版本更新 我们应该先验证 
Url和key是不是我们和服务端协商好的Url和key
UpgradeModel aResult = xxxx//解析服务器返回的后数据 if (aResult != null &&
aResult.getData() != null ) { String url = aResult.getData().getDownUrl(); if
(url == null || !TextUtils.equals(url, "这里是你知道的下载地址: 也可以只验证hostUrl")) { //
如果符合,说明不是目标下载地址,就不去下载 }
* 1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
* 9
* 10
* 11
* 12
接着我们验证下载url是你自己app的服务器地址,然后再去请求下载Api,这时用DownLoadModel接受请求头
,看是否符合自己和服务器约定的key和hash之,下载好apk到本地后,继续判断文件的hash和升级api返回的hashcode,
加之key是否是和下载服务器返回的key,如果不一致,就不安装
File file = DownUtils.getFile(url); // 监测是否要重新下载 if (file.exists() &&
TextUtils.equals(aResult.getData().getHashCode(), EncryptUtils.Md5File(file)))
{ && TextUtils.equals(aResult.getData().getKey(),
DownLoadModel.getData()..getKey()) // 如果符合,就去安装 不符合重新下载 删除恶意文件 }
* 1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
* 9

等我们验证了下载文件的地址是我们自己服务器提供的,验证没问题后就只剩安装了。接着还要对apk文件进行包名和签名校验,如果包名和签名不一致,那么就是伪装程序,这个漏洞显而易见!
/** installApK * @param context * @param path * @param name */ public static
void installApK(Context context, final String path, final String name ) { if
(!SafetyUtils.checkFile(path + name, context)) { return; } if
(!SafetyUtils.checkPagakgeName(context, path + name)) { Toast.makeText(context,
"升级包被恶意软件篡改 请重新升级下载安装", Toast.LENGTH_SHORT ).show(); DLUtils.deleteFile(path +
name); ((Activity)context).finish(); return; } switch
(SafetyUtils.checkPagakgeSign(context, path + name)) { case
SafetyUtils.SUCCESS: DLUtils.openFile(path + name, context); break; case
SafetyUtils.SIGNATURES_INVALIDATE: Toast.makeText(context, "升级包安全校验失败 请重新升级",
Toast.LENGTH_SHORT ).show(); ((Activity)context).finish(); break; case
SafetyUtils.VERIFY_SIGNATURES_FAIL: Toast.makeText(context, "升级包为盗版应用 请重新升级",
Toast.LENGTH_SHORT ).show(); ((Activity)context).finish(); break; default:
break; } }
* 1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
* 9
* 10
* 11
* 12
* 13
* 14
* 15
* 16
* 17
* 18
* 19
* 20
* 21
* 22
* 23
* 24
* 25
* 26
* 27
* 28
* 29
* 30
* 31
* 32
* 33
* 34
* 35
* 36
* 37
* 38
* 39
* 40
* 41
* 42
* 43
SafetyUtils安全类如下:
/** * 安全校验 * Created by LIUYONGKUI on 2016-04-21. */ public class SafetyUtils
{ /** install sucess */ protected static final int SUCCESS = 0; /**
SIGNATURES_INVALIDATE */ protected static final int SIGNATURES_INVALIDATE = 3;
/** SIGNATURES_NOT_SAME */ protected static final int VERIFY_SIGNATURES_FAIL =
4; /** is needcheck */ private static final boolean NEED_VERIFY_CERT = true;
/** * checkPagakgeSigns. */ public static int checkPagakgeSign(Context context,
String srcPluginFile) { PackageInfo PackageInfo =
context.getPackageManager().getPackageArchiveInfo(srcPluginFile, 0);
//Signature[] pluginSignatures = PackageInfo.signatures; Signature[]
pluginSignatures = PackageVerifyer.collectCertificates(srcPluginFile, false);
boolean isDebugable = (0 != (context.getApplicationInfo().flags &
ApplicationInfo.FLAG_DEBUGGABLE)); if (pluginSignatures == null) {
PaLog.e("签名验证失败", srcPluginFile); new File(srcPluginFile).delete(); return
SIGNATURES_INVALIDATE; } else if (NEED_VERIFY_CERT && !isDebugable) {
//可选步骤,验证APK证书是否和现在程序证书相同。 Signature[] mainSignatures = null; try { PackageInfo
pkgInfo = context.getPackageManager().getPackageInfo( context.getPackageName(),
PackageManager.GET_SIGNATURES); mainSignatures = pkgInfo.signatures; } catch
(PackageManager.NameNotFoundException e) { e.printStackTrace(); } if
(!PackageVerifyer.isSignaturesSame(mainSignatures, pluginSignatures)) {
PaLog.e("升级包证书和旧版本证书不一致", srcPluginFile); new File(srcPluginFile).delete();
return VERIFY_SIGNATURES_FAIL; } } return SUCCESS; } /** * checkPagakgeName *
@param context * @param srcNewFile * @return */ public static boolean
checkPagakgeName (Context context, String srcNewFile) { PackageInfo packageInfo
= context.getPackageManager().getPackageArchiveInfo(srcNewFile,
PackageManager.GET_ACTIVITIES); if (packageInfo != null) { return
TextUtils.equals(context.getPackageName(), packageInfo.packageName); } return
false; } /** * checkFile * * @param aPath * 文件路径 * @param context * context */
public static boolean checkFile(String aPath, Context context) { File aFile =
new File(aPath); if (aFile == null || !aFile.exists()) {
Toast.makeText(context, "安装包已被恶意软件删除", Toast.LENGTH_SHORT).show(); return
false; } if (context == null) { Toast.makeText(context, "安装包异常",
Toast.LENGTH_SHORT).show(); return false; } return true; } }
* 1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
* 9
* 10
* 11
* 12
* 13
* 14
* 15
* 16
* 17
* 18
* 19
* 20
* 21
* 22
* 23
* 24
* 25
* 26
* 27
* 28
* 29
* 30
* 31
* 32
* 33
* 34
* 35
* 36
* 37
* 38
* 39
* 40
* 41
* 42
* 43
* 44
* 45
* 46
* 47
* 48
* 49
* 50
* 51
* 52
* 53
* 54
* 55
* 56
* 57
* 58
* 59
* 60
* 61
* 62
* 63
* 64
* 65
* 66
* 67
* 68
* 69
* 70
* 71
* 72
* 73
* 74
* 75
* 76
* 77
* 78
* 79
* 80
* 81
* 82
* 83
* 84
* 85
* 86
* 87

<>

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:[email protected]
QQ群:637538335
关注微信