继续更新!Android的安全问题太多太多,这里只是总结了下我所了解(强制 = = )到的安全问题
话说,实际开发中还能想到多少呢??
现在感觉各行各业越来越重视安全,如果有机会接触到感觉还是很爽的!其实我想装逼
截屏风险
在登录和注册,或修改密码等敏感数据操作时,如果手机中有后台默认隐藏截屏的应用,在输入是一直截屏,就有可能盗取敏感数据信息。
解决方案:
在Activity onCreate 中加入:
1 | //一般写在setContentView上面 |
官方的意思就是设置了这个flag后, 系统会把当前窗口的内容视为安全隐私内容, 系统会阻止这些内容被截屏或者在不安全可靠的场景显示出来.
它起到的主要作用是:
阻止屏幕截图
在Recent apps(任务切换界面)中只显示应用名字和图标, 不显示内容
- Google App的Now on tap功能不会去分析你的页面的内容
最后,对于国内各种ROM对Android的丧心病狂的更改还是要测试下实际效果的。。。
关注debuggable
android:debuggable
属性的设置可能会引起 被动态调试的风险。
1 | <application |
debuggable 属性有两个值“true|false”;
只有Android:debuggable=”true”
时我们才可以在手机上调试Android程序。
但是当我们没在AndroidManifest.xml
中设置其debug属性时:
使用Eclipse运行这种方式打包时其debug属性为true,使用Eclipse导出这种方式打包时其debug属性为法false.
在使用ant打包时,其值就取决于ant的打包参数是release还是debug.
因此在AndroidMainifest.xml中最好不设置android:debuggable属性置,而是由打包方式来决定其值。
如果设置了 android:debuggable=”true” 那么在正式打包时 把它设置成false吧!!!
关注allowBackup
android:allowBackup
属性的设置可能会引起用数据被任意备份的风险
1 | <application |
Android API Level 8 及其以上 Android 系统提供了为应用程序数据的备份和恢复功能,此功能的开关决定于该应用程序中 AndroidManifest.xml
文件中的 allowBackup 属性值,其属性值默认是 True。当 allowBackup 标志为 true 时,用户即可通过 adb backup 和 adb restore 来进行对应用数据的备份和恢复。
一旦应用程序支持备份和恢复功能,攻击者即可通过 adb backup 和 adb restore 进行恢复新安装的同一个应用来查看聊天记录等信息;对于支付金融类应用,攻击者可通过此来进行恶意支付、盗取存款等;因此为了安全起见,开发者务必将 allowBackup 标志值设置为 false 来关闭应用程序的备份和恢复功能,以免造成信息泄露和财产损失。
安全的打印日志
如何打印日志?这不是很简单,直接使用android.util.Log
这个类不就行了?然而,日志属于非常敏感的信息;逆向工程师在逆向你的程序的时候,本来需要捕捉你程序的各种输出,然后进行推测,顺藤摸瓜然后得到需要的信息;一旦你的日志泄漏,无异于门户洞开,破解你的程序如入无人之境。
我们打印日志是用Log.d(TAG, msg);
当把APK进行反编译后,TAG这个字符串会原封不动的还原出来,推理推理也就差不多了,不管你是否混淆过….
安全的概念本来就是相对的,如果破解你程序的代价远远大于破解得到的价值,那么就可以认为程序是“安全的”;这里就分析一下,为了提高程序的安全性,在打印日志的时候应该注意什么。
让release版本里面不包含日志代码
我们想要的是在开发的时候,正常打印日志;一旦需要发布版本,把所有打印日志的语句代码,全部删除掉。
这里我们可以采用日志开关+proguard
的方式来进行优化,关于proguard这个工具,很多认只是觉得他是一个代码混淆的工具,实际上,它还可以帮你剔除无用代码!
无用代码就是类似下面的:
1 | if (true) { |
静态编译的时候被认为“永远不会执行的代码”,就被认为是无用代码,会被这个工具直接优化掉,生成的class文件里面,这个if语句直接就没有了。这个功能,完美符合我们的需求;我们只需要把输出日志的代码用这样的if语句包围起来,然后release的时候肯定会用这个工具混淆;然后,在release版本里面,所有的输出日志的代码全部都没有了!不会像以前一样,留下一个影子,只是不做事。
所以我们这样写:
1 | private static final boolean DEBUG = true; // 必须是static final 也就是常量,这样才能在编译器优化;删除if块 |
那么当DEBUG
变量为False
的时候proguard可以理所当然地认为,这一部分代码时绝对不会被执行的,这样,打印日志的语句就会被优化(删除)掉.
这里还需要注意的是,不要把打印日志进行封装,往里传个TAG和MSG,想省去写if包裹语句,这样的话就会使之前的工作失去作用,反编译后传参的部分会暴露出来….所以不要这么搞!
如果你实在懒得打,AS的话有框架提示,打个ifd
就会自动生成代码块!
AS的话还有另一种方式,详情去参考里翻一翻。
SQLite数据库安全风险
使用SQLite来存储数据却存在着一个问题。因为大多数的Android手机都是Root过的,而Root过的手机都可以进入到/data/data/<package_name>/databases
目录下面,在这里就可以查看到数据库中存储的所有数据。如果是一般的数据还好,但是当涉及到一些账号密码,或者聊天内容的时候,我们的程序就会面临严重的安全漏洞隐患。我们可以借助SQLCipher来解决这个安全性问题。
SQLCipher是一个在SQLite基础之上进行扩展的开源数据库,它主要是在SQLite的基础之上增加了数据加密功能,如果我们在项目中使用它来存储数据的话,就可以大大提高程序的安全性。SQLCipher支持很多种不同的平台。
使用SQLCipher替换掉程序中的SQLite的数据。将SQLCipher数据包导入项目相应目录中,将原有的SQlite import文件修改为SQLCipher,在程序启动界面添加SQLiteDatabase.loadLibs(this)
,并修改mysqlite.getWritableDatabase()
方法
首先创建一个MyDatabaseHelper继承自SQLiteOpenHelper,注意导入的包,除了导入的包不同,其他基本都和SQLiteOpenHelper相同。
1 | import android.content.Context; |
然后在使用到的Activity中这样写:
1 | public class MainActivity extends Activity { |
需要注意的是:加入SQLCipher后会使APP的安装包增加几M,安全与体积要权衡好
Android签名安全
现在Android逆向越来越火,并且相比PC端的EXE程序感觉Android的逆向还是很简单的,那就会面临着一个问题:会有人将APK进行反编译后修改代码然后进行二次打包发布,造成一些恶劣影响
我们知道打包APK必然要进行签名,原始的签名密钥肯定是安全的唯一的,二次打包会改变APP的签名信息,我们在APP启动的时候进行签名对比,如果不一致就强制JVM退出
当然了,没有绝对的安全,只要你逆向技术够高,这个是拦不住你的….
获取签名
获取签名我们可以采用两种方式,一种手动用keytool命令,还可以在代码里写
1 | //代码方式获取签名,根据包名 |
如果是手动用命令查的话,可以直接用RAR之类的打开APK文件,找到Apk文件中META-INF/CERT.RSA文件,解压,然后执行:keytool -printcert -file fileName
就可以查到签名了
PS:正常的签名文件查询命令是keytool -list -v -keystore filepath
(后缀一般为keystore,其实并不需要后缀)
签名进行对比
这里贴下主要代码:
1 | String signature="e79cf0a46d543ab6092b71f41d835543"; //正确的已知的签名 |
BroadCastReceiver安全风险
Android 可以在配置文件中声明一个receiver或者动态注册一个receiver来接收广播信息,攻击者假冒APP构造广播发送给被攻击的receiver,是被攻击的APP执行某些敏感行为或者返回敏感信息等,如果receiver接收到有害的数据或者命令时可能泄露数据或者做一些不当的操作,会造成用户的信息泄漏甚至是财产损失
那么如何避免应用中注册的广播响应其他应用发送的广播呢,对于显式的广播除非是别人故意攻击,一般很少出现响应别人的广播,但是对于隐式的广播就很容易出现上述问题,因为action很容易是一样的,一旦是一样的就出问题了
解决方案
如果仅在应用内部通信,可以使用私有receiver,设置
exported="false"
,该receiver可以接收相同应用程序组件或带有相同用户ID的应用程序所发出的消息[参考]。1
<receiver android:name=".permittedReceiver" android:exported="false" />
若只在当前进程内通信,可以使用
LocalBroadcastManager
,使其他应用程序不能向该receiver发送广播对于动态注册的广播
registerReceiver(BroadcastReceiver, IntentFilter, String permission, android.os.Handler)
,指定receiver必须具备的permission。
如果只允许自己的产品族使用,可以设置android:protectionLevel="signature"
,若提供给其他APP使用,则设置android:protectionLevel="normal"
,同时要避免敏感信息的传递。
其实就是自定义权限~~1
2
3
4
5
6
7
8<permission android:name="com.android.permission.send_permission" android:protectionLevel="signature" />
<receiver android:name=".permittedReceiver"
android:permission="com.android.permission.send_permission">
<intent-filter>
<action android:name="com.android.permitted_ACTION" />
</intent-filter>
</receiver>对接收来的广播进行验证,返回结果时需注意接收app是否会泄露信息
关于自定义权限的补充
首先说一下protectionLevel
这个属性:
- normal:默认的,应用安装前,用户可以看到相应的权限,但无需用户主动授权。
- dangerous:normal安全级别控制以外的任何危险操作。需要dangerous级别权限时,Android会明确要求用户进行授权。常见的如:网络使用权限,相机使用权限及联系人信息使用权限等。
- signature:它要求权限声明应用和权限使用应用使用相同的keystore进行签名。如果使用同一keystore,则该权限由系统授予,否则系统会拒绝。并且权限授予时,不会通知用户。它常用于应用内部。
上面的订阅方例子用到了signature这个值,多说一下,如果别的应用使用的不是同一个签名文件,就没办法使用该权限,从而保护了自己的接收者(可以理解为只接受拥有此权限的应用发送的广播)。
发送方和订阅方都是需要加入这个权限的,只不过订阅方需要在注册接收器的时候再写一遍权限,上面的例子是静态注册receiver,如果用动态的方式注册那就是registerReceiver(receiver, filter, permission, null);
,直接指定发送者应该具有的权限
android:permission —如果设置,具有相应权限的广播发送方发送的广播才能被此broadcastReceiver所接收
参考
http://wolfeye.baidu.com/blog/recieve-broadcast-security/
BroadcastReceiver安全问题
剪切板安全风险
同一部手机中安装的其他app,甚至是一些权限不高的app,都可以通过剪贴板功能获取密码管理器中的账户密码信息。原因是Android剪贴板的内容向任何权限的app开放,很容易就被嗅探泄密,如上代码剪切板中存有明文内容,如果是明文内容将会有信息泄露的风险
如下代码可以在任意APP中读取剪切板的内容:
1 | ClipboardManager cm = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE); |
所以,使用完clipboard及时清空,并避免使用剪贴板明文存储敏感信息
其他风险
在配置Database配置模式的时候要注意:避免使用
MODE_WORLD_WRITEABLE
和MODE_WORLD_READABLE
模式创建数据库(Database),最好还是使用MODE_PRIVATE
模式安卓SecureRandom安全:
在Android 4.2以下,SecureRandom是基于老版的Bouncy Castle实现的。如果生成SecureRandom对象后马上调用setSeed方法。SecureRandom会用用户设置的seed代替默认的随机源。使得每次生成随机数时都是会使用相同的seed作为输入。从而导致生成的随机数是相同的。
解决方案推荐:不要使用自定义随机源代替系统默认随机源,就是说不要调用以下函数:SecureRandom#SecureRandom(byte[] seed)
SecureRandom#setSeed(long seed)
SecureRandom#setSeed(byte[] seed)其实还可以在调用setSeed方法前先调用任意nextXXX方法(
nextBytes(byte[] bytes)
)不过不推荐这种方式现在基本上不用考虑了,毕竟已经到Android7.1+了,但是考虑到国内情况嘛….
评论框加载失败,无法访问 Disqus
你可能需要魔法上网~~