前言
对于一款成熟的 App, 在某个时间点一定会开始涉及或者说要开始考虑安全问题本着一贯的风格, 把自己最近一段时间以来了解和搜集的安全方面的知识整理归纳下, 一是方便内部培训, 再就是分享出来, 希望对大家的实际开发工作能有所帮助
1. 代码保护
1.1 代码混淆
1.1.1 ProGuard
在 Android Studio 当中混淆 APK, 借助 SDK 中自带的 Proguard 工具, 只需要修改 build.gradle 中的一行配置即可如下所示:
- release {
- minifyEnabled true
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
其中 minifyEnabled 用于设置是否启用混淆, proguardFiles 用于选定混淆配置文件 build.gradle 中 minifyEnabled 的值, 这里我们只需要把值改成 true, 打出来的 APK 包就会是混淆过的了
使用 ProGuard 混淆的应用, 通过 apktool 还是可以看到 Manifest 和 res 资源, 使用 dex2jar 也可以看到混淆后的源码, 虽然大部分代码已经混淆了还是可以看个大概, 而且通过 smail 的修改, 重新进行逆向 apk
对于一般的应用足够, 而对于应用中用了很多开源项目的, 在业务层进行混淆也应该足够了
参考文章:
Android 安全攻防战, 反编译与混淆技术完全解析(上)
1.1.2 DexGuard
DexGuard 是收费的, 是在 Proguard 基础上, 加入了更多的保护措施使用 DexGuard 混淆后, 生成的 apk 文件, 就无法正常使用 apktool 反编译了
DexGuard:https://www.guardsquare.com/dexguard
即使做了混淆处理面对那群让人心生敬畏的黑客和逆向工作者, 我们还是太脆弱了
那群鸟人, 即使你的代码搞成. so 他们也能给你反编译了, 更别说他们还有大把的手段直接从内存下手肿么办? 凉拌因为安全本来就是一个相对的概念, 没有绝对的安全, 就看谁的成本更大, 手段更高超而已
1.2 代码加固
除了常规的 apk 混淆之外, 我更推荐大家进一步使用一款专业的加固工具对 APK 进行加固, 如果公司肯花点钱, 搞个收费版的效果更佳市场上加固工具也有很多, 哪位同学比较熟悉各种工具之间效能对比的话, 还希望能够给代价讲讲, 我再这里不对说了
1.3 日志清理
黑客为了解析我们的代码逻辑, 从日志下手对他们来说是非常常见而且有效的手段日志中往往隐藏了对黑客来说非常重要的关键词和代码逻辑
所以为了减少信息的泄漏或者说增加代码被解析的难读, release 版本的日志屏蔽是非常重要的
成熟的做法是 APP 构建自己的日志框架, 在打 release 包时, 能够方便的屏蔽掉所有的日志信息
关于日志框架(LogUtil 类等等), 网上一大堆, 读者自行百度吧
2. 存储安全
数据存储安全是 APP 安全策略的重中之重, 敏感数据像用户名密码秘钥令牌以及通讯录等等的保护都是需要重点考虑的
常见的问题:
将隐私数据明文存储在外部存储中
将系统数据明文保存在外部存储中
将软件运行时依赖的数据存储在外部存储中
将软件安装包或二进制代码存储在外部存储中
全局可读写的内部存储文件
下面介绍了将数据保存在设备上的三种基本方法
2.1 数据存储
2.1.1 内部存储
默认情况下, 您在内部存储空间中创建的文件仅供您的应用访问这项保护措施由 Android 实现, 而且这对于大多数应用来说足够了
要为敏感数据提供额外的保护, 您可以选择使用该应用无法直接访问的密钥来对本地文件进行加密例如, 您可以将密钥存储在
[KeyStore](https://developer.android.com/reference/java/security/KeyStore.html)
中, 并使用未存储在相应设备上的用户密码加以保护不过, 如果攻击者获得超级用户权限, 就可以在用户输入密码时进行监控, 数据也就失去了这层保护屏障; 但是, 这种方式可以保护丢失设备上的数据, 而无需进行文件系统加密
2.1.2 外部存储
在外部存储设备 (例如 SD 卡) 上创建的文件不受任何读取和写入权限的限制对于外部存储设备中的内容, 不仅用户可以将其移除, 而且任何应用都可以对其进行修改, 因此最好不要使用外部存储设备来存储敏感信息
就像处理来源不受信任的数据一样, 您应对外部存储设备中的数据执行输入验证
2.1.3 content provider
如果您不打算向其他应用授予访问您的 ContentProvider 的权限, 请在应用清单中将其标记为 [android:exported=false]; 要允许其他应用访问存储的数据, 请将 [android:exported]属性设置为 "true"
在创建要导出以供其他应用使用的 ContentProvider 时, 您可以在清单中指定允许读取和写入的单一权限, 也可以针对读取和写入操作分别指定权限
如果您要使用内容提供程序仅在自己的应用之间共享数据, 最好将 [android:protectionLevel]属性设置为 "signature" 保护级别
访问内容提供程序时, 请使用参数化的查询方法(例如 query()update() 和 delete()), 以免产生来源不受信任的 SQL 注入风险请注意, 如果以组合用户数据的方式构建 selection 参数, 然后再将其提交至参数化方法, 则使用参数化方法可能不够安全
2.2 数字签名
数字签名技术是 Android APP 的安全基石之一, APP 开发者使用私钥, 以证书的形式对 APP 进行签名
对关键的代码或者数据进行数字签名验证, 可以有效的降低 apk 被篡改带来的风险
2.3 秘钥保护
将重要的数据比如秘钥等存储在. so 中, 以 APP 的数字签名进行提取之前的验证条件, 可以较好地降低敏感数据暴露的风险
另外, 如果需要使用动态的秘钥, 则可以采用 RSA 非对称加密, 从服务器获取动态秘钥;
获取动态秘钥后, 使用 AES 对数据进行秘钥加密
3. 组件安全
保护 APP 组件的途径有两条, 其一是正确地使用 AndroidManifest.xml 文件, 其二是在代码级别上强制进行权限检查
参考书籍:
Android 安全技术解密与规范 周圣涛著
3.1 原则
3.1.1 最小化组件暴露
检查 AndroidManifest.xml 中对组件的导出设置, 如果组件不允许被其他 APP 调用, 则 android:exported 的属性值需要设置为 false 需要注意的是, 如果 APP 的 minsdkVersion 的值设置<=16 或组件设置了 intent-filter, 则 android:exported 的属性值默认是为 true 的
3.1.2 设置组件访问权限
如果你的组件导出了, 为了降低被任意 APP 滥用或攻击, 你需要考虑是否需要对调用方做过滤和筛选
如果需要过滤的话, 比较好的做法是对组件声明 android:permission;
对 permission 可根据需要进一步设置
android:protectedLevel
, 特别是当设置为 signature 级别表明只允许相同数字签名的 APP 访问组件
3.1.3 组件传输数据验证
对组件之间, 特别是跨应用的组件之间的数据传入和返回做数据验证, 防止恶意数据传入, 更要防止敏感数据的返回
对于导出的组件, 最好做 intent 的校验:
数据格式或数据结构校验;
异常处理: 防止 crash 攻击
3.1.4 暴露组件的代码检查
Android SDK 提供了很多 API, 我们能够利用这些 API 在程序运行时检查执行授予和撤销权限
3.2 Activity 组件安全
使用 activity 的风险和选择取决于需求对 activity 的定义这里我们基于 activity 的使用方式将其分为 4 中类型:
私有 activity: 只能有 APP 内部使用的 activity, 最安全的 activity
公共 activity: 任意 APP 都能启动此类 activity
伙伴 activity: 合作伙伴 APP 才能启动的 activity, 授权
内部 activity: 只有内部 APP 才能启动, 全家桶
3.2.1 私有 activity
私有 activity 不能由其他应用程序启动, 因此它是最安全的当一个 activity 只在本 APP 内部使用时, 只要将 activity 声明为显式 intent 调用方式, 那么就不需要担心其他应用程序调用开启
然而, 有一个风险是第三方 APP 可以读取一个开启 activity 的 intent 因此, 为了避免第三方应用程序读取 Intent 来复制 intent, 我们可以在 intent 中放置一些 extra 做判断, 避免第三方应用程序调用
一个私有 activity 必须做到的关键几点如下:
1)不声明 taskAffinity
2)不声明 launchMode
3)设置 exported 属性为 false
4)保证 intent 发送时的安全性, 确定 intent 是来自本应用程序(签名验证和包名验证)
5)在确保是本应用程序发送 Intent 的时候, 可以防止一些敏感信息
6)启动 activity 的时候不设置 FLAG_ACTIVITY_NEW_TASK
7)使用显示的 intent 和指定的类的方式来调用一个 activity
8)敏感信息放在 extra 中发送
9)在 onActivityResult 的时候需要对发挥的 data 小心处理
在 AndroidManifest.xml 中的声明如下:
- <activity
- android:name=".PrivateActivity"
- android:exported="false"
- android:label="@string/app_name" />
在 java 代码中的操作如下:
- private final static int REQUEST_CODE = 1;
- @Override
- public void onUseAactivityClick(View view){
- // 显示 intent 调用, 直接写类名
- Intent intent = new Intent(this, PrivateActivity.class);
- // 设置包名
- intent.setPackage(getPackageName());
- intent.putExtra("PARAM","敏感数据");
- startActivityForResult(intent,REQUEST_CODE);
- }
- @Override
- public void onActivityResult(int requestCode, int resultCode, intent data){
- super.onActivityResult(requestCode,resultCode,data);
- // 判断 result 的状态
- if(resultCode != RESULT_OK){
- return;
- }
- switch(requestCode){
- case REQUEST_CODE:
- // 注意返回数据的处理
- String result = data.getStringExtra("RESULT");
- ...
- break;
- }
- }
3.2.2 公共 activity
我能要知道, 一旦一个 activity 声明为公共 activity, 那么, 任何一个 APP 都可以发送一个 intent 来启动它, 他的安全性也必须要更加注意了
一个公共 activity 必须做到的关键几点如下:
1)设置 exported 属性为 true
2)接收到 intent 的时候要小心处理
3)finish 的时候, 不要再返回 intent 中放置敏感信息
在 AndroidManifest.xml 中的声明如下:
- <activity
- android:name=".PublicActivity"
- android:exported="true"
- android:label="@string/app_name" >
- <!-- 定义一个 action-->
- <intent-filter>
- <action android:name="com.example.PUBLIC_ACTIVITY_ACTION"/>
- <category android:name="android.intent.category.DEFAULT"/>
- </intent-filter>
- </activity>
在 java 代码中的操作如下:
- public calss PublicActivity extends Activity{
- @Override
- public void onCreate(Bundle savedInstanceState){
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- // 判断确保 param 来源的正确性, 发送之前可以适当做一些加密处理
- String param = getIntent().getStringExtra("PARAM");
- }
- public void onReturnResultClick(View view){
- Intent intent = new Intent();
- //finish 时不要在 intent 中放置敏感信息, 放置被截取
- intent.putExtra("RESULT","非敏感信息");
- setResult(RESULT_OK,intent);
- finish();
- }
- }
3.2.3 伙伴 activity
伙伴 activity, 顾名思义是只能由特定的应用程序使用的 activity, 即得到授权的 APP, 共享其信息和方法这样的 activity 会存在一个风险, 第三方应用程序可以读取启动这个 activity 的 intent 信息因此, 尽量不要在 intent 中放置一些敏感信息, 也有必要做一些操作不让第三方应用读取到 intent
一个伙伴 activity 必须做到的关键几点如下:
1)不声明 taskAffinity
2)不声明 launchMode
3)设置 exported 属性为 true
4)不添加 intent-filter
5)使用白名单机制验证签名
6)处理 partner activity 来的 intent 的时候要小心注意
7)只返回给 partner activity 一些公开信息
在 AndroidManifest.xml 中的声明如下:
- <activity
- android:name=".PartnerActivity"
- android:exported="true"
- android:label="@string/app_name" />
在 activity 中做签名验证的操作如下:
- public calss PartnerActivity extends Activity{
- // 伙伴应用的签名
- private static final String PARTNER_SIGNATURE = "0de0f9c90dd0ed0e8d0edd...";
- // 伙伴应用的包名
- private static final String PARTNER_SIGNATURE = "com.xxxx.yyyyyy";
- // 检查是否是伙伴应用
- private static boolean checkPartner(Context context, String pkgname){
- // 包名不正确
- if(!TextUtils.equals(pkgname,PARTNER_SIGNATURE )){
- return false;
- }
- // 签名不正确
- if(!getSignature(pkgname).eauals(PARTNER_SIGNATURE ))){
- return false;
- }
- }
- @Override
- public void onCreate(Bundle savedInstanceState){
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- // 判断确保 param 来源的正确性, 发送之前可以适当做一些加密处理
- String param = getIntent().getStringExtra("PARAM");
- }
- public void onReturnResultClick(View view){
- Intent intent = new Intent();
- //finish 时不要在 intent 中放置敏感信息, 放置被截取
- intent.putExtra("RESULT","非敏感信息");
- setResult(RESULT_OK,intent);
- finish();
- }
- }
3.2.4 内部 activity(全家桶)
内部 activity 是禁止由内部 APP 之外的其他 APP 使用的 activity 一般都是设置一个 signature 的权限, 如系统内部使用的应用程序, 都具有相同的签名
这样的 activity 也会存在一个风险, 第三方应用可以读取启动这个 activity 的 intent 信息因此, 尽量不要在 intent 中放置一些敏感的信息, 也有必要做一些操作不让第三方应用读取到 intent
一个内部 activity 必须做到的关键几点如下:
1)定义 activity 的权限为 signature
2)不声明 taskAffinity
3)不声明 launchMode
4)设置 exported 属性为 true
5)不添加 intent-filter
6)验证签名
7)通过 intent 传输的数据要小心
在 AndroidManifest.xml 中的声明如下:
- ......
- <!-- 自定义一个 signature 的 permission-->
- <permission
- android:name="com.xxxx.activity.inhouseactivity.MY_PERMISSION"
- android:protectionLevel="signature"/>
- ......
- <!-- 声明内部 activity-->
- <activity
- android:name=".InHouseActivity"
- android:exported="true"
- android:permission="com.xxxx.activity.inhouseactivity.MY_PERMISSION" />
在 activity 中的操作如下:
- public class InHouseActivity extends Activity{
- // 启动权限
- private static final String MY_PERMISSION = "com.xxxx.activity.inhouseactivity.MY_PERMISSION";
- @Override
- public void onCreate(Bundle savedInstanceState){
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- // 检查 permission
- if(!TextUtils.equals(MY_PERMISSION,getPermission(this))){
- // 权限不正确
- finish();
- return;
- }
- ......
- }
- public void onReturnResultClick(View view){
- Intent intent = new Intent();
- intent.putExtra("RESULT","敏感信息");
- setResult(RESULT_OK,intent);
- finish();
- }
- }
3.3 Broadcast Receiver 组件安全
Broadcast Receiver(广播接收器)是专注于接收广播通知信息, 并做出对应处理的组件 APP 可以拥有任意数量的广播接收器以对所有它感兴趣的通知信息予以响应
对于 broadcast receiver 的创建和接收, 需要根据不同的需求与风险做不同的选择决策根据广播的发送接收流程和广播的使用方式, broadcast 也可以分为三个不同的级别:
私有广播接收器: 此类广播只能被 APP 内部所接受, 所以是最安全的广播
公共广播接收器: 此类广播没有指定具体的接收者, 能被任意的 APP 接收
内部广播接收器: 广播只能被一些特定的 APP 所接收
broadcast receiver 根据注册声明方式可以分为两种: 静态广播和动态广播
静态广播: 在 Androidmanifest.xml 中注册
动态广播: 在 android 代码中注册和注销
Google 官方也意识到了广播的安全性问题, 在 supportv4 包中提供了一个类 LocalBroadcastManager, 主要负责程序内部广播的注册与发送使用 LocalBroadcastManager 有如下好处:
发送的广播只会在 APP 内传播, 不会泄露给其他 APP, 确保隐私数据不会泄露
其他 APP 也无法向自己的 APP 发送该广播
比系统全局广播更加高效
但是, 它只适用于在代码中注册发送广播, 在 Androidmanifest 中注册的广播接收则不适用
3.3.1 私有 Broadcast Receiver
私有广播是最安全的广播, 其发出来的广播只能有 APP 内部能够接收动态广播无法注册为私有广播, 所以私有广播只存在于静态广播中
私有广播的几个关键点如下:
1)不添加 intent-filter
2)exported 属性为 false
3)广播处理完毕后要终止掉广播
私有广播的声明(Androidmanifest):
- <!-- 私有广播 -->
- <receiver
- android:name=".PrivateReceiver"
- android:exported="false"/>
私有广播的定义(Java):
- public class PrivateReceiver extends BraodcastReceiver{
- @Override
- public void onReceive(Context context,Intent intent){
- String param = intent.getStringExtra("PARAM");
- setResultCode(Activity.RESULT_OK);
- // 处理一些重要敏感信息
- setResultData("敏感信息");
- // 终止掉广播, 不需要再继续接收广播了
- abortBroadcast();
- }
- }
在发送广播的时候注意在创建 intent 时, 使用具体类名做声明即可, 免除 intent 被拦截的风险代码如下:
- // 普通广播
- public void onSendNormalClick(View view){
- Intent intent = new Intent(this,PrivateReceiver.class);
- intent.putExtra("PARAM","敏感信息");
- sendBroadcast(intent);
- }
- // 顺序广播
- public void onSendOrderedClick(View view){
- Intent intent = new Intent(this,PrivateReceiver.class);
- intent.putExtra("PARAM","敏感信息");
- sendOrderedBroadcast(intent,null,mResultReceiver,null,0,null,null);
- }
3.3.2 公共 Broadcast Receiver
公共广播, 没有确定的接收方, 可以被一些未确定的 APP 接收所以, 使用公共广播的时候必须要注意此类广播被一些恶意应用接收造成数据泄露
公共广播的关键点如下:
1)exported 属性为 true
2)获取 intent 的时候小心处理
3)return result 的时候别放置敏感信息
公共广播应该是通用的, 所以能够被静态广播接收器和动态广播接收器所接收
- public class PublicReceiver extends BroadcastReceiver{
- private static final String MY_BRROADCAST_PUBLIC="com.xxx.MY_BRROADCAST_PUBLIC";
- @Override
- public void onReceive(Context context,Intent intent){
- // 判断 action
- if(MY_BRROADCAST_PUBLIC.equals(intent.getAction())){
- String param = intent.getStringExtra("PARAM");
- // 数据小心处理
- }
- setResultCode(Activity.RESULT_OK);
- // 存放非敏感信息
- setResultData("非敏感信息");
- // 终止掉广播
- abortBroadcast();
- }
- }
静态公共广播声明(Androidmanifest):
- <!-- 静态公共广播接收器 -->
- <receiver
- android:name=".PublicReceiver"
- android:exported="true">
- <intent-filter>
- <action android:name="com.xxx.MY_BRROADCAST_PUBLIC"/>
- </intent-filter>
- </receiver>
动态公共广播声明(java):
动态的公共广播我们在 service 中声明, 代码如下:
- public class DynamicReceiverService extends Service{
- private static final String MY_BROADCAST_PUBLIC = "com.xxx.MY_BRROADCAST_PUBLIC";
- private PublicReceiver mReceiver;
- @Override
- public IBinder onBind(Intent intent){
- return null;
- }
- @Override
- public void onCreat(){
- super.onCreate();
- // 动态注册公共广播接收器
- mReceiver = new PublicReceiver();
- InterFilter filter = new IntentFilter();
- filter.addAction(MY_BROADCAST_PUBLIC);
- filter.setPriority(100);// 优先考虑动态广播而不是静态广播
- registerReceiver(mReceiver,filter);
- }
- @Override
- public void onDestroy(){
- super.onDestroy();
- // 注销广播接收器
- unregisterReceiver(mReceiver);
- mReceiver = null;
- }
- }
未完待续
4. 网络安全
4.1 中间人攻击和网络劫持
使用 Https 和 ssl 证书校验
5. AndroidManifest.xml
需要特别注意的点:
权限检查:
a) 最小化系统权限
b) 自定义权限进行级别保护设置
调试标记检查: debuggable="false"
程序数据任意备份检查: allowBackup="false"
webview:
a) 防远程代码调用:
- removeJavascriptInterface(accessibility);
- removeJavascriptInterface(accessibilityTraversal);
- removeJavascriptInterface(searchBoxJavaBridge_);
b) 自动保存用户名密码检查: webview.getsettings().setsavePassword(false)
以上资料描述上可能存在错误, 也不够详细, 在后期会逐渐完善
参考资料:
- http://www.droidsec.cn
- https://developer.android.com/training/best-security.html
- http://www.jsondream.com/2016/11/12/chat-about-Internet-Security-Architecture.html
来源: http://www.jianshu.com/p/0eb6df1e9c4d