本文主要介绍备份安全和内存安全。文中提到的相关资源和代码可以在https://github.com/AndroidAppSec/resources 获取。
五、备份安全
Android 系统提供了数据备份功能,以方便进行应用的数据备份,此功能使用不当,可能会造成敏感信息泄露。备份数据有以下几种方式:
1、原生系统提供了通过 usb 进行备份。当使用 usb 连接设备,开启调试模式之后,可以使用adb backup命令来进行本地数据备份。
2、google 提供了"Back Up My Data" 的功能,可以将应用数据备份到 google 服务器。
3、一些 OEM 厂商可以会有自己的备份方式,比如 HTC 提供了"HTC Backup" 服务,可以定期将数据备份到云端。
拿到备份数据之后,可能会有几种利用方式:
1、还原明文数据获取敏感信息
可以使用 Android Backup Extractor 来还原备份数据,命令如下:
java -jar abe-all.jar unpack backup_nopass.ab nopass.tar(未加密)
java -jar abe-all.jar unpack backup_pass.ab pass.tar 123(加密)
注:备份数据加密采用的是 AES256 算法,所以要保证 abe 运行成功,需要覆盖本地 JCE 文件,路径为 jre\lib\security 。以上所需文件均可在 https://github.com/AndroidAppSec/resources/tree/master/%E5%AD%98%E5%82%A8%E5%AE%89%E5%85%A8 中获取。
2、绕过登录限制
一般会采用adb restore backup.ab的方式来将获取到的数据覆盖到本地。比如说之前有人成功用这种方式来登录别人的微博,也有人用来绕过锁屏密码。利用条件相对苛刻。本文不做详述。可以参见 http://www.freebuf.com/articles/terminal/60778.html、http://www.droidsec.cn/%E8%AF%A6%E8%A7%A3android-app-allowbackup%E9%85%8D%E7%BD%AE%E5%B8%A6%E6%9D%A5%E7%9A%84%E9%A3%8E%E9%99%A9/。
六、内存安全
这一部分主要翻译《owasp mobile security testing guide》的 Checking Memory for Sensitive Data 的相关内容。
1、概览
分析内存能够帮助开发者去发现很多问题的根因,比如应用崩溃。同时,它也可以被用来获取敏感信息。本部分会介绍如何检查内存的信息泄露。
首先识别存储在内存中的敏感信息。敏感资产很可能在某些时候被加载到内存中,我们的目的是验证此信息暴露的时间尽可能短。
要调查应用程序的内存,必须先创建内存镜像。你还可以通过调试器等实时分析内存。无论你使用什么方法,在验证的时候,内存镜像都是一个非常容易出错的过程。因为每个镜像都只包含已执行函数的输出,你可能会错过执行关键操作。此外,除非你知道数据的足迹(精确值或数据格式),否则在分析期间很可能会忽略数据。比如说,如果应用程序使用随机生成的对称密钥加密,您可能无法在内存中发现它,除非您可以在另一个上下文中识别密钥的值。
因此,最好从静态分析开始。
2、静态分析
在检查源代码之前,请先阅读应用的文档,并确定其使用的组件,这样能够对数据泄露源有个整体把握。例如,来自后端的敏感数据可能位于 HTTP 客户端,XML 解析器等中。您希望尽快从内存中删除所有这些副本。
此外,了解应用程序的体系结构和体系结构在系统中的作用将帮助您识别不必在内存中暴露的敏感信息。例如,假设您的应用程序只是简单的从一台服务器接收数据并将其传输到另一台服,那么可以对该数据加密,以免将明文暴露在内存中。
但是,如果您需要在内存中暴露敏感数据,应该遵循尽可能少尽可能简要的原则。换句话说,您应该集中处理敏感数据(即,使用尽可能少的组件)并基于原始的可变数据结构。后一个要求为开发人员提供了直接内存访问以确保他们使用此访问权限利用虚拟数据(通常为零)覆盖敏感数据。例如byte []和char []是优先的数据类型,但 String 或 BigInteger 不是。因为,每当您尝试修改 String 等不可变对象时,都会创建并更改该对象的副本。
使用非原始可变类型(如 StringBuffer 和 StringBuilder)可能是可以接受的,但它是指示性的并且需要小心。像 StringBuffer 这样的类型用于修改内容(这是你想要做的)。但是,要访问此类型的值,您将使用toString方法,该方法将创建数据的不可变副本。有几种方法可以在不创建不可变副本的情况下使用这些数据类型,但与简单地使用基本数组相比,它们需要更多的工作量。安全内存管理是使用 StringBuffer 等类型的一个好处,但这可能是一把双刃剑。如果您尝试修改这其中一种类型的内容并且副本超出缓冲区容量,则缓冲区大小将自动增加。缓冲区内容可能会被复制到其他位置,而旧的内容变得不可引用和覆盖。遗憾的是,很少有库和框架可以覆盖敏感数据。例如,如下所示,销毁密钥并不能真正从内存中删除密钥:
从 secretKey.getEncoded 覆盖后备字节数组也不会删除密钥,因为基于SecretKeySpec的密钥返回的是后备字节数组的副本。
RSA 密钥对基于BigInteger类型,因此在首次在 AndroidKeyStore 之外使用后驻留在内存中。某些密码(例如 BouncyCastle 中的 AES 密码)无法正确清理其字节数组。
用户提供的数据(凭证,社会安全号码,信用卡信息等)是可能在内存中暴露的另一种类型的数据。无论您是否将其标记为密码字段,EditText都会通过可编辑界面将内容传递给应用程序。如果您的应用程序未提供Editable.Factory,则用户提供的数据可能会在内存中暴露超过必要的时间。默认的可编辑实现 SpannableStringBuilder 与 Java 的 StringBuilder 和StringBuffer 有相同的问题(如上所述)。
总之,在执行静态分析以识别内存中暴露的敏感数据时,您应该:
尝试识别应用程序组件并映射使用数据的位置。
确保敏感数据由尽可能少的组件处理。
确保在不再需要包含敏感数据的对象后正确删除对象引用。
确保在删除引用后请求进行垃圾回收。
确保敏感数据在不再需要时立即被覆盖。
不要使用不可变数据类型(例如 String 和 BigInteger)表示此类数据。
避免使用非原始数据类型(例如 StringBuilder)。
在 finalize 方法之外,在删除引用之前覆盖引用。
注意第三方组件(库和框架)。公共 API 是很好的指标。确定公共 API 是否处理敏感数据,如本章所述。
以下部分描述了内存中数据泄漏的缺陷以及避免这些缺陷的最佳实践。
不要使用不可变结构(例如,String 和 BigInteger)来表示机密。使用null去覆盖这些结构将无效:垃圾收集器可能会收集它们,但它们可能在垃圾收集后保留在堆上。但是,您应该在每次关键操作后请求垃圾收集(例如,加密,解析包含敏感信息的服务器响应)。如果未正确清理信息副本(如下所述),您的请求将有助于缩短这些副本在内存中可用的时间。
要从内存中正确清除敏感信息,请将其存储在基本数据类型中,例如字节数组(byte [])和char数组(char [])。如上面的“静态分析”部分所述,您应该避免将信息存储在可变的非原始数据类型中。
确保在不再需要对象后覆盖关键对象的内容。用零覆盖内容是一种简单且非常流行的方法:
但是,这并不能保证在运行时覆盖内容。为了优化字节码,编译器将分析并决定不覆盖数据,因为之后不会使用它(即,它是不必要的操作)。即使代码在编译的 DEX 中,优化也可能在 VM 中的即时编译或提前编译期间发生。
这个问题没有灵丹妙药,因为不同的解决方案会产生不同的后果。例如,您可以执行其他计算(例如,将数据 XOR 转换为虚拟缓冲区),但您无法知道编译器优化分析的范围。另一方面,使用编译器范围之外的覆盖数据(例如,在临时文件中将其序列化)可以保证它将被覆盖但显然会影响性能和维护。
然后,使用 Arrays.fill 覆盖数据是一个坏主意,因为该方法是一个明显的挂钩目标。
上述示例的最后一个问题是内容仅用零覆盖。您应该尝试使用来自非关键对象的随机数据或内容覆盖关键对象。这将增大扫描器扫出敏感数据结构及路径的难度。
以下是上一个示例的改进版本:
更多信息,请查看 Securely Storing Sensitive Data in RAM (https://www.nowsecure.com/resources/secure-mobile-development/coding-practices/securely-store-sensitive-data-in-ram/)。
在“静态分析”部分中,我们提到了在使用 AndroidKeyStore 或 SecretKey 时处理加密密钥的正确方法。
要更好地实现 SecretKey,请查看下面的 SecureSecretKey 类。 虽然实现可能缺少一些可以使类与 SecretKey 兼容的示例代码,但它解决了主要的安全问题:
没有敏感数据的跨上下文处理。 密钥的每个副本都可以在创建密钥的范围内清除。
本地副本会根据上面给出的建议清除。
安全的用户提供的数据是通常在内存中找到的最终安全信息类型。这通常通过实现自定义输入方法来管理,您应该遵循此处给出的建议。 但是,Android 允许通过自定义Editable.Factory从EditText缓冲区中部分擦除信息。
有关可 Editable 实现的示例,请参阅上面的 SecureSecretKey 示例。 请注意,如果您提供工厂,则可以安全地处理 editText.getText 生成的所有副本。 您也可以尝试通过调用 editText.setText 来覆盖内部EditText缓冲区,但不能保证缓冲区不会被复制。 如果您选择依赖默认输入方法和 EditText,则无法控制键盘或其他使用的组件。 因此,您应该仅将此方法用于半机密信息。
3、动态分析
静态分析将帮助您识别潜在问题,但它无法提供有关数据在内存中暴露多长时间的统计信息,也无法帮助您识别闭源依赖项中的问题。 这是动态分析发挥作用的地方。
基本上有两种分析进程内存的方法:通过调试器进行实时分析、分析一个或多个内存镜像。 因为前者更像是一般的调试方法,所以我们将专注于后者。
对于基本分析,您可以使用 Android Studio 的内置工具。 它们位于 Android Profiler 选项卡上。 要转储内存,请选择要分析的设备和应用程序,然后单击 “Dump Java heap”。 这将保存一个.hprof 文件。
点击红色按钮开始记录内存使用情况
去 app 进行操作
点击方框 结束记录
这时候会调到分析界面,选择下面的 Arrange by package,并选择我们要分析的类即可。
如下图所示,在右侧窗体就能看到刚才我们输入的账号密码了。
也可以把内存保存下来使用MAT(Memory Analyzer Tool) 来进行分析。
MAT(Memory Analyzer Tool)提供了几种分析内存转储的工具。 例如,直方图提供了从给定类型捕获的对象数量的估计值,“线程概览”显示了进程的线程和堆栈帧。 Dominator Tree 提供有关对象之间保持活动依赖关系的信息。 您可以使用正则表达式来过滤这些工具提供的结果。
MAT工具可以在https://www.eclipse.org/mat/downloads.php下载。
比如下图中,展示了使用 OQL 语句查询所有文本框信息。
参考:
https://stackoverflow.com/questions/29830981/error-zlib-is-an-invalid-command
http://www.freebuf.com/articles/terminal/60778.html
https://blog.csdn.net/flaming999/article/details/53433727
https://sushi2k.gitbooks.io/the-owasp-mobile-security-testing-guide/content/0x05d-Testing-Data-Storage.html
领取专属 10元无门槛券
私享最新 技术干货