iOS本地数据存储安全
iOS本地数据存储安全
移动APP通常会在设备本地存储一些数据,这可以为程序执行、更良好地性能或离线访问提供支持。由于移动设备使用地越来越广泛,设备失窃的风险也越来越大,因此不安全的本地数据存储已成为移动APP安全的主要威胁之一。
攻击者可以通过一些方式获取到存储在iOS设备上的敏感信息,主要有以下方法:
恶意程序
借助iOS系统的安全弱点,攻击者可以设计出一种远程偷取iPhone上文件的恶意程序。
备份
当iPhone连接至iTunes后,如果iPhone信任了所连接的电脑后,iTunes将自动对设备上的所有数据进行备份。通过备份,敏感数据也将会保存到电脑上。因此,攻击者如果可以接触到那台电脑,则可以通过备份文件读取到敏感信息。
物理接触
用户iPhone的丢失或被盗非常常见。在这两种情形下,攻击者都将可以物理接触到设备,并读取设备上存储的敏感信息。
在iOS上,移动APP可以将信息输出或存储到以下一个或多个文件中:
* Plist文件
* SQLite数据库文件
* Keychain文件
* 缓存文件
* 日志文件
Plist文件
功能模块介绍
属性列表(Plist,Property List)是一种结构化的二进制格式文件,包含了内嵌键值对的可执行bundle的基本配置信息。Plist文件主要用于存储App的用户设置及配置信息,例如,游戏类App经常会在Plist文件中存储游戏等级和分数信息。一般来说,App会将存储用户数据的Plist文件保存在“[App home目录]/documents/”目录下。Plist文件可以是XML格式或二进制格式。
风险描述
Plist文件主要用于存储用户设置及App的配置信息,但App可能使用Plist文件存储明文的用户名、密码或其它一些个人敏感信息。而保存在Plist文件中的二进制格式文件数据则可以使用Plist文件编辑器(如plutil)进行查看或修改,即使在一个没有越狱的设备上,plist文件也可以通过工具iExplorer获取。对于以编码、未加密或弱加密形式存储的敏感信息就可能会导致敏感信息泄露了。
WordPress的iOS版App曾经就在Plist文件中存储了明文的用户名和密码,如下图所示,后来WordPress紧急修复了它。
安全方案
1) 尽量不要在iOS设备的Plist文件中保存敏感信息(如证件号、银行卡号、详细住址及其各对应的编码格式等);
2) 对于有些APP功能需求,如果一定需要在iOS设备本地保存敏感信息,则可采用iOS提供的加密接口(如CommonCrypto)进行安全加密后保存。
SQLite存储
功能模块介绍
SQLite是一种自包含、可嵌入、0配置的SQL数据库引擎的跨平台C库文件。它的表、触发器和视图整个数据库都包含在一个硬盘文件中。SQLite数据库提供了所有标准的SQL结构,包括select、insert、update和delete。创建SQLite数据库文件时,可以添加任意文件后缀,也可以不添加后缀,常见的后缀一般有“.sqlitedb”和“.db”。APP一般会将其保存在“[App home目录]/documents/”目录下。由于SQLite的轻便、稳定和小巧,它已成为一个在iOS设备上存储数据的优秀解决方案。
风险描述
信息泄露
iOS自带的SQLite数据库没有内置的加密支持,因此,许多iOS APP会直接以明文格式将许多敏感数据存储在SQLite数据库中,除非APP自身对数据进行加密后再存储。例如,为提供离线的邮件访问功能,Gmail的iOS APP以明文方式将所有邮件存储在SQLite数据库中。一旦可以物理访问到设备或其备份文件,存储在SQLite中未加密的敏感信息容易被泄露。
如下图,某银行APP就将用户的登录手机号、登录密码、手势密码全部都以明文方式存在了客户端本地的SQLite数据库中。
数据恢复
除上述提到的信息泄露途径外,SQLite的数据恢复同样也会导致信息泄露。在iOS中,恢复被删除的SQLite数据库记录比恢复被删除的文件更为容易。因为如果删除一条记录,SQLite仅会将该记录标记为已删除,但不会清除它们。只要SQLite数据库文件本身没被删除,数据库中被删除的记录则会一直保留在SQLite文件的未分配空间内,直到新的记录覆盖它们。
攻击者可以使用strings命令打印SQLite数据库文件中数据,这其中就包括了被删除的数据。以下为整个恢复过程的演示。
(1)创建一个名为messages.sqlite的SQLite数据库,并插入测试数据。
(2)重新连接数据库,删除几条记录,然后再查看数据库记录,已无法查看到被删除的记录。
(3)使用strings命令查看该数据库文件,可以看到被删除的记录被打印出来。
安全方案
方案1,*简单的方法就是尽量不在客户端的SQLite数据库中保存敏感信息;
方案2,如果确实需要将某些敏感信息保存在SQLite数据库中时,可以结合使用以下几种方案:
(1) 数据加密:使用如AES256加密算法对数据进行安全加密后再存入SQLite中;
(2) 整库加密:可使用第三方的SQLite扩展库,对数据库进行整体的加密。
(3) 数据覆盖:在删除SQLite数据库某条记录之前,可以使用垃圾数据update一下该条目。这样即使有人尝试从SQLite文件中恢复已删除的数据库时,他们也无法获取到实际的数据。
对于方案2中提到的三种子方案,也各有优缺点:
键盘缓存
功能模块介绍
为提供自动填充和纠正的功能,iOS系统的自带键盘会缓存用户的输入信息。其会保存一个接近600个单词的列表,存放在“Library/Keyboard/ en_GB-dynamic-text.dat”或“/private/var/mobile/Library/Keyboard/dynamic-text.dat”文件中(iOS版本不同,位置及文件名会略有不同)。
风险描述
这个功能会带来一个安全问题:它会明文存储用户在输入框中输入过的所有信息,如用户名、密码短语、安全问题回答等。由于键盘会缓存这些输入框信息,使得在开始输入一个如安全问题答案的时候,缓存会帮助攻击者自动完成该问题的答案输入。
要想查看该键盘缓存,可以将上述如“en_GB-dynamic-text.dat”文件复制到电脑上,并使用十六进制编辑器打开。下图就是一个键盘缓存的使用16进制浏览的截图,可以看到明文的输入信息(this is a test)。
图 3. 键盘缓存记录的输入
安全方案
1, 首先,对于以下位置或方式的输入,iOS不会对其输入内容进行缓存:
* 在标记为secure的字段、passwords字段内输入的内容不会缓存;
* 输入只包括数字的字符串不会被缓存,这也即意味着银行卡号、信用卡号是安全的,(iOS 5之前的版本会缓存);
* 非常短的输入,如只有1或者2个字母组成的单词不会被缓存;
* 禁用了自动纠正功能的文本框会阻止输入内容被缓存;
2, 可以在不需要缓存的文本框处禁用自动纠正功能。如下代码所示:
UITextField *textField = [[UITextField alloc] initWithFrame: frame ];
textField.autocorrectionType = UITextAutocorrectionTypeNo;
1
2
3, 输入框也可以被标记为密码输入类型,使得输入变得更加安全,防止缓存。如:
textField.secureTextEntry = YES;
1
4,在所有敏感信息输入处均使用自定义键盘,当然自定义键盘也不能缓存用户输入。(这可能会影响用户体验)
另外除了文本输入的地方,在iOS系统上,当数据被复制到粘贴板上的时候,也会被进行明文缓存,而且粘贴板内容所有APP均可访问。为禁用文本框的复制/粘贴功能,使得用户无法在某些地方进行复制和粘贴,可在该文本输入的地方添加以下方法:
-(BOOL)canPerformAction:(SEL)action withSender:(id)sender {
UIMenuController *menuController = [UIMenuController sharedMenuController];
if (menuController) {
menuController.menuVisible = NO;
}
return NO;
}
应用快照缓存
功能模块介绍
当一个应用在后台被挂起时,iOS会生成一个当前屏幕的快照,当应用被重新唤起时,可以快速还原该APP之前的内容,以提高用户的使用体验。
风险描述
然而,这样做也可能会导致一些应用数据的泄露。应用快照保存在“/var/mobile/Containers/Data/Application/XXXXXXX-XXXXXXXXX-XXXXXXXXX/Library/Caches/Snapshots/”目录下。恶意APP可通过读取该文件并发送至远程服务端,从而获得其快照内容信息。
例如,以下快照截图就是在花旗银行APP上查看邮件时直接按下home按键后截图的内容。
安全方案
要防止这种信息泄露途径,屏幕内容就必须在iOS系统进行屏幕快照之前进行隐藏或模糊化处理,而iOS系统也提供了许多回调方法来提示程序将被挂起。例如以下两个方法:
* (void)applicationWillResignActive:(UIApplication *)application
应用程序将要入非活动状态执行,在此期间,应用程序不接收消息或事件
* (void)applicationDidEnterBackground:(UIApplication *)application
程序将被推送到后台。
下图为APP压入后台的过程,右边则是可供APP回调的方法,可利用其实现自己的一些需求:
图 5. 应用进入后台的过程
方案一:
一个简单方法就是设置关键窗口的hidden属性为YES,这样当前在屏幕上显示的内容将被隐藏,返回一个空白的快照来替代任何内容。
[UIApplication sharedApplication].keyWindow.hidden=YES;
1
注意:如果在当前窗口后面有其他的窗口,当关键窗口被隐藏时,那些窗口将会被显示出来。所以当使用这种方法时要确保也隐藏了其他窗口。。
当应用程序即将进入非活动状态时(如接到一个电话或切换到其他应用程序时),applicationWillResignActive方法会被回调。因此可以用以下代码来隐藏窗口。
-(void)applicationWillResignActive:(UIApplication *)application
{
[UIApplication sharedApplication].keyWindow.hidden=YES;
}
另一个重要的地方是把这些代码加入到aplicationDidEnterBackground方法中,此方法会在APP被压入后台但在屏幕快照被调用前被调用。
-(void)applicationDidEnterBackground:(UIApplication *)application
{
[UIApplication sharedApplication].keyWindow.hidden=YES;
}
方案二:
除隐藏当前界面内容外,还可以对当前界面所展示的内容进行模糊化处理。在上述的回调方法中,通过使用iOS的毛玻璃(blur glass)技术,可以达到程序后台运行界面的模糊化效果。
由于此部分代码较多,故不在此列出,感兴趣的同学可以参考下这两篇文章:http://blog.csdn.net/jp940110jpjp/article/details/44307515、http://www.code4app.com/ios/后台模糊效果/54912828933bf0d5388b4f25
应用日志
功能模块介绍
基于iOS APP程序开发排错的需要,开发人员一般都会写一些数据到日志中,而这些数据就可能包括证件号、登录用户名和密码、认证token或其它的一些敏感信息。
风险描述
应用程序的错误日志是不被应用程序的沙盒隔离保护的,一个APP产生的错误日志可以被另一个APP读取。此外,在越狱iOS设备上,APP还可获取到其他应用输出所有的日志信息。因此,如果一个APP使用日志功能输出了某些敏感信息,那么恶意APP就能够读取到这些信息,并可将其发送到一个远程服务器上。另外,可以直接从AppStore上下载安装“console”应用,查看iOS系统及APP输出的错误日志信息。可以在“/var/log/”目录下找到iOS的日志文件。
如下图所示,某个APP在日志中输出了用户名及登录密码:
图 6. 应用日志敏感信息泄露
安全方案
不要在APP的日志中记录或打印敏感信息,并且在正式发布的时候,确保关闭了日志打印开关。
keychain存储
功能模块介绍
Keychain是一个拥有有限访问权限的SQLite数据库(AES256加密),可以为多种应用程序或网络服务存储少量的敏感数据(如用户名、密码、加密密钥等)。如保存身份和密码,以提供透明的认证,使得不必每次都提示用户登录。在iPhone上,Keychain存放在“/private/var/Keychains/keychain-2.db”SQLite数据库。
Keychain数据库包含了一些Keychain条目,每个条目都由加密的数据和一系列未加密的描述属性组成,Keychain的条目类型(kSecClass)决定了其关联的一些描述属性。在iOS系统中,Keychain条目被分为5种类型:
其中,数字身份=证书+密钥。
在iOS的Keychain中,所有的Keychain条目都被存储在Keychain SQLite数据库的4张表中:genp、inet、cert和keys。genp数据表存储了普通密码的Keychain条目,inet数据表存储了网络密码的Keychain条目,cert和keys数据表分别存储了证书和密钥的Keychain条目。
图 7. Keychain数据表
Keychain的数据库内容使用了设备唯一的硬件密钥进行加密,该硬件密钥无法从设备上导出。因此,存储在Keychain中的数据只能在该台设备上读取,而无法复制到另一台设备上解密后读取。
图 8. Keychain文件格式
iOS APP的Keychain数据是存储在应用沙箱外面的,各APP的keychain数据内容为逻辑隔离,由系统进程securityd实施访问控制。一个应用默认无法读取到另一个应用在Keychain中存储的数据。在iOS系统中,每个APP都附带一个唯一的应用标识符,而Keychain服务则使用这个应用标识符限制其对其它Keychain数据的访问。默认情况下,APP只能访问与他们的应用标识符相关联的数据。为了在多个APP间能够共享Keychain信息,Apple引入了Keychain访问组概念:拥有相同Keychain访问组标识符的应用,可以共享Keychain数据。APP的Keychain访问权限(即标识符)被加密嵌入在了APP的二进制文件中,但可以通过使用grep或sed命令将其从该文件中提取出来。
通过SSH连接到iPhone上,进入到应用的home目录中(ios8:/var/mobile/Containers/Bundle/Application/[unique-id]/),运行以下命令:
sed –n ‘//,/<\/dict>/p’ [AppDirectory]/[ApplicationBinary]
例如:以下命令列出了QQ应用的keychain权限:
图 9. QQ应用的keychain权限
上述输出表明QQ应用在存储或访问Keychain数据时,使用“BJFH5U299Z.com.tencent.generickeychain”这一Keychain访问组权限。
当应用向Keychain中添加一个条目时,该应用的应用标识符或Keychain 访问组权限也将被自动获取并添加到该条目的agrp列(访问组)。之后,当某个应用尝试访问该条Keychain条目时,Keychain服务通过该Keychain条目中对应的agrp值验证应用标识符或Keychain访问组,以判断是否允许访问。下图显示了keychain-2.db样例文件,圈出了QQ的Keychain访问组权限。
图 10. QQ的keychain条目
随着iOS引入了数据保护机制,存储在Keychain中的数据被另一层与用户密码(passcode)相关联的加密机制保护着。数据保护加密密钥(保护类密钥-protection class keys)是由一个设备硬件密钥和一个由用户密码衍生的密钥共同产生的。可以通过向Keychain接口的SecItemAdd或SecItemUpdate方法的kSecAttrAccessible属性提供一个可访问常量来启用Keychain的数据保护。该可访问常量值决定一个Keychain条目在何时可被应用访问,同时也决定某个Keychain条目是否允许移动到另一台设备上。
以下是Keychain条目的数据保护可访问常量列表:
(1)kSecAttrAccessibleWhenUnlocked:Keychain条目只能在设备被解锁后被访问,此时用于解密Keychain条目的数据保护类密钥只在设备被解锁后才会被加载到内存中,并且当设备锁定后,加密密钥将在10s钟内自动清除。
(2)kSecAttrAccessibleAfterFirstUnlock:Keychain条目可以在设备*次解锁到重启过程中被访问,此时用于解密Keychain条目的数据保护类密钥只有在用户重启并解锁设备后才会被加载到内存中,并且该密钥将一直保留在内存中,直到下一次设备重启。
(3)kSecAttrAccessibleAlways:Keychain条目即使在设备锁定时也可被访问,此时用于解密Keychain条目的数据保护类密钥一直被加载在内存中。
(4)kSecAttrAccessibleWhenUnlockedThisDeviceOnly:Keychain条目只能在设备被解锁后被访问,并且无法在设备间移动。
(5)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly:Keychain条目可在设备*次解锁后访问,但无法在设备间移动
(6)kSecAttrAccessibleAlwaysThisDeviceOnly:Keychain条目即使在设备锁定时也可被访问,但无法再设备将移动
Keychain条目的数据保护可访问常量被映射到各Keychain表(genp、inet..)中的pdmn列(protection domain)。下表显示了keychain数据保护可访问常量和pdmn值之间的映射关系。
下图显示了keychain-2.db样例文件,红色方框为pdmn列。
图 11. pdmn样例
风险描述
为避免每次提示用户登录,APP很可能会在Keychain中直接存储明文的认证信息(如WiFi、邮箱密码)。而Keychain_dumper工具可用于导出越狱iOS设备上的所有Keychain条目,它使用“*”通配符格式的Keychain访问组权限,因而可以访问到设备上所有的Keychain条目。
APP在添加Keychain条目时可能也会设置数据保护的可访问常量,如前文所述,该可访问常量决定了Keychain条目何时才能被访问。而数据保护机制又是与用户密码相关联的,只有在当用户设置iOS密码时,它才会保护数据。如下为导出Keychain数据的演示过程:
(1)当设置了iOS密码且未解锁时,无法访问到Keychain中的数据
(2)解锁后,(或未设置iOS密码)即可访问到Keychain中的数据。如下图,*个是连接过的WiFi密码,第二个是在系统自带邮件应用中登录过的邮箱密码。
安全方案
一旦攻击者能够物理接触到没有设置密码的iOS设备时,他就可以通过越狱该设备,运行如keychain_dumper这样的工具,读取到设备所有的Keychain条目,获取里面存储的明文信息。而即使在应用向Keychain中存储数据时使用了数据保护的接口(即上文提到的keychain数据保护可访问常量),未设置用户密码的Keychain数据仍没有被有效地保护着。为保证Keychain中存储的数据的安全,可采用以下建议:
1, 尽量不在Keychain中直接存储明文的敏感信息。
2, 向Keychain中存储数据时,不要使用kSecAttrAccessibleAlways,而是使用更安全的kSecAttrAccessibleWhenUnlocked或kSecAttrAccessibleWhenUnlockedThisDeviceOnly选项。
3, 如果必须要存储,则可先检测用户是否设置了设备密码,并进行相应的风险提示。
4, 对用户来说,想要保护Keychain中的数据,就是设置更强的密码,iOS默认允许4位数字口令(从0-9999),结合iOS的某些漏洞或设置,很容易被人快速暴力破解,而设置字母加数字的密码则会让破解过程花费更多的时间。