iOS 应用数据泄漏问题及解决策略
1. 自动补全机制与按键记录
iOS 的单词自动补全机制广为人知,它带来娱乐的同时也会产生有趣的拼写错误。然而,这个机制在无意中充当了按键记录器,会将用户输入的部分文本记录在一个基本为纯文本的文件中,以便未来补全使用。法医攻击者可以获取这个补全数据库。
对于密码字段,这种记录行为已被禁用,但应用中的其他表单可能会处理敏感数据。开发者需要在用户体验和安全性之间进行权衡。对于处理少量敏感数据的字段,如安全问题的答案,应通过设置
UITextField
和
UITextView
对象的
autocorrectionType
属性为
UITextAutocorrectionTypeNo
来禁用自动补全行为。对于
UISearchBar
对象,这也是个好主意,因为搜索内容泄露到磁盘通常是不可取的。示例代码如下:
UITextField *sensitiveTextField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 25, 25)];
[sensitiveTextField setAutocorrectionType:UITextAutocorrectionTypeNo];
在 iOS 5.1 左右出现了一个 bug,即使禁用了自动补全、自动大写、拼写检查等功能,磁盘上的单词缓存仍会更新。目前有两种解决方法:
-
“愚蠢”的方法
:使用
UITextView
并发送
setSecureTextEntry:YES
消息。
UITextView
类没有正确实现
UITextInputTraits
协议,文本不会像密码输入框那样被圆圈遮挡,但可以防止文本写入磁盘。示例代码如下:
-(BOOL)twiddleTextView:(UITextView *)textView {
[textView setSecureTextEntry:YES];
}
-
“荒谬”的方法
:适用于
UITextView和UITextField对象,先开启再关闭setSecureTextEntry。示例代码如下:
-(BOOL)twiddleTextField:(UITextField *)textField {
[textField setSecureTextEntry:YES];
[textField setSecureTextEntry:NO];
}
不过,
UISearchbar
没有正确实现该协议,不能使用此技巧。在进行这种开关操作之前,要确保应用运行的操作系统版本已经过测试。示例代码如下:
UITextField *sensitiveTextField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 25, 25)];
[sensitiveTextField setAutocorrectionType: UITextAutocorrectionTypeNo];
if ([[[UIDevice currentDevice] systemVersion] isEqual: @"8.1.4"]) {
[sensitiveTextField setSecureTextEntry:YES];
[sensitiveTextField setSecureTextEntry:NO];
}
为了验证应用是否意外泄露数据,可以检查模拟器或越狱设备上的
<device ID>/data/Library/Keyboard/dynamic-text.dat
文件。在 iOS 8 及更高版本中,键盘缓存中还会存储额外信息,位于
<device ID>/data/Library/Keyboard/en-dynamic.lm
目录下,包含
dynamic.dat
、
lexicon.dat
、
meta.dat
和
tags.dat
四个文件。
2. 用户偏好的滥用
用户偏好通常包含敏感信息,但用户默认设置实际上是用于定义应用 API 的 URL 或其他非敏感偏好信息。开发者通过
NSUserDefaults
API 或不太常用的
CFPreferences
API 来操作偏好设置,但很多开发者可能不清楚这些数据在设备上的处理方式。这些文件的限制较为宽松,使用常见工具(如 iExplorer)可以轻松读取和操作用户偏好。
以下是一个来自 iGoat 项目的糟糕示例:
NSUserDefaults *credentials = [NSUserDefaults standardUserDefaults];
[credentials setObject:self.username.text forKey:@"username"];
[credentials setObject:self.password.text forKey:@"password"];
[credentials synchronize];
这是数据泄露的最坏情况,凭证以明文形式存储在应用的 plist 文件中。此外,开发者可能会使用
NSUserDefaults
存储用户不应控制的信息,如安全控制设置。为了保护用户,应尽可能让服务器来执行这些决策。在审核应用时,要检查每个
NSUserDefaults
或
CFPreferences
API 的使用情况,确保存储的数据合适,不应包含秘密信息或不希望用户更改的信息。
3. 快照中的敏感数据处理
iOS 在将应用发送到后台之前会对当前屏幕状态进行快照,以便在重新打开应用时生成动画。这可能导致敏感信息泄露到磁盘,即使用户并非有意将应用置于后台。例如,在输入敏感信息时接听电话,屏幕状态会被写入磁盘,直到被另一个快照覆盖。
一旦这些快照被写入磁盘,物理攻击者可以使用常见的取证工具轻松获取。可以在模拟器中观察文件的写入过程,只需暂停应用并打开
UIApplicationAutomaticSnapshot Default-Portrait.png
文件,该文件位于应用的
Library/Caches/Snapshots/com.mycompany.myapp
目录下。应用无法手动删除快照,但可以通过以下方法防止数据泄露:
3.1 屏幕清理策略
可以在截图实际发生之前更改屏幕状态,这需要在
applicationDidEnterBackground
委托方法中实现。该方法在应用即将暂停时被调用,开发者有几秒钟的时间完成任务。这个委托方法与
applicationWillResignActive
或
applicationWillTerminate
事件不同,前者在应用暂时失去焦点时调用,后者在应用被强制终止或选择不进行后台操作时调用。iOS 应用的简化生命周期如下:
| 事件 | 说明 |
| ---- | ---- |
|
didFinishLaunchingWithOptions
| 应用启动完成 |
|
applicationDidBecomeActive
| 应用变为活跃状态 |
|
applicationWillResignActive
| 应用即将失去焦点 |
|
applicationDidEnterBackground
| 应用进入后台 |
|
applicationWillTerminate
| 应用即将终止 |
以下是几种屏幕清理的方法:
-
放置启动屏幕
:这是最简单和可靠的方法,在所有当前视图之上放置一个带有标志艺术的启动屏幕。示例代码如下:
- (void)applicationDidEnterBackground:(UIApplication *)application {
application = [UIApplication sharedApplication];
self.splash = [[UIImageView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[self.splash setImage:[UIImage imageNamed:@"myimage.png"]];
[self.splash setUserInteractionEnabled:NO];
[[application keyWindow] addSubview:splash];
}
-
隐藏相关容器对象
:设置相关容器对象(如
UITextFields)的hidden属性,也可以隐藏整个UIView。 - 执行动画 :进行一些自定义动画,如淡入淡出效果。示例代码如下:
- (void)fadeMe {
[UIView animateWithDuration:0.2
animations:^{view.alpha = 0.0;}
completion:^(BOOL finished){[view removeFromSuperview];}
];
}
- 模糊截图 :对当前屏幕状态进行截图并通过模糊算法处理,但要确保模糊效果足够强,攻击者无法逆转。
无论采用哪种混淆方法,都需要在
applicationDidBecomeActive
或
applicationWillEnterForeground
委托方法中恢复更改。例如,要移除启动屏幕,可以在
applicationWillEnterForeground
方法中添加以下代码:
- (void)applicationWillEnterForeground:(UIApplication *)application {
[self.splash removeFromSuperview];
self.splash = nil;
}
在完成之前,要通过在不同应用状态下多次暂停应用并监控
Library/Caches/Snapshots/com.mycompany.myapp
目录,确保清理技术有效,检查保存的 PNG 图像是否被启动屏幕完全遮挡。
3.2 屏幕清理策略的工作原理
iOS 应用的视图和窗口层次结构如下:
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A(UI elements):::process --> B(UIView):::process
A --> C(UIWindow):::process
A --> D(CALayer):::process
每个在屏幕上显示内容的应用都由一个层(默认是
CALayer
)支持,层之上是
UIWindow
类的实例,它管理一个或多个
UIView
类的实例。
UIView
是分层的,一个视图可以有多个子视图以及按钮、文本字段等。iOS 应用通常只有一个
UIWindow
,但多个窗口也是可能的。默认情况下,窗口的
windowLevel
属性为 0.0,表示处于
UIWindowLevelNormal
级别。其他定义的级别包括
UIWindowlevelAlert
和
UIWindowlevelStatusBar
,它们的优先级高于
UIWindowLevelNormal
,会显示在其他窗口之上。当前接收用户事件的窗口称为关键窗口,可以通过
UIApplication
的
keyWindow
方法引用。
3.3 常见的清理错误
不了解 iOS 窗口和视图的开发者经常会错误地清理屏幕。常见的错误包括只隐藏关键窗口的根视图控制器,这会使根视图的子视图仍然可见;隐藏整个关键窗口也不是万无一失的,因为
UIAlertView
窗口会出现在其他内容之上并成为关键窗口,实际上只会隐藏警报。因此,建议开发者使用启动屏幕方法,对于某些用例,还有一种更简单、万无一失的方法:完全防止暂停。
3.4 防止暂停以避免快照
如果应用不需要暂停和恢复(即每次启动都希望有一个全新的开始),可以使用 Xcode plist 编辑器在 plist 文件中添加 “Application does not run in background” 并将其值设置为
YES
,也可以在
Info.plist
文件中设置
UIApplicationExitsOnSuspend
为
YES
。这样应用会直接跳转到
applicationWillTerminate
事件,而不是停在
applicationDidEnterBackground
事件,从而避免截图。
4. 状态保存导致的泄漏
iOS 6 引入了状态保存的概念,用于在应用调用之间维护应用状态,即使应用在此期间被杀死。当触发状态保存时,每个可保存对象的
encodeRestorableStateWithCoder
委托方法会被调用,该方法包含如何将各种 UI 元素序列化到磁盘的指令。在重新启动应用时,会调用
decodeRestorableStateWithCoder
方法。这可能导致敏感信息从用户界面泄露到磁盘存储,因为文本字段和其他界面数据的内容会被存储在本地。
在检查新代码库时,可以通过在代码库中搜索
restorationIdentifier
来快速确定是否存在状态保存。如果使用了状态保存,在
.storyboard
文件中会找到类似以下的结果:
<viewController restorationIdentifier="viewController2" title="Second" id="3"
customClass="StatePreservatorSecondViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="17">
<rect key="frame" x="0.0" y="20" width="320" height="499"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill"
translatesAutoresizingMaskIntoConstraints="NO" id="Zl1-tO-jGB">
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
</subviews>
</view>
</viewController>
如果应用委托实现了
encodeRestorableStateWithCoder
方法,可以指定一个
encodeObject
方法来保存
UITextView
的
.text
属性,以便后续恢复。示例代码如下:
-(void)encodeRestorableStateWithCoder:(NSCoder *)coder {
[super encodeRestorableStateWithCoder:coder];
[coder encodeObject:_messageBox.text forKey:@"messageBoxContents"];
}
在进行功能测试后,可以检查应用的
Library/Saved Application State/com.company.appname.savedState
目录下的
data.data
文件,该文件包含了分配了
restorationIdentifiers
的对象的序列化状态。通过检查该文件,可以确定用户界面中的敏感数据是否被编码。
5. 安全的状态保存
如果产品需要状态保存的用户体验和便利性,但需要在磁盘上安全存储数据,可以在将敏感对象内容传递给
encodeObject
方法之前对其进行加密。具体步骤如下:
1. 应用安装时,使用
secItemAdd
生成加密密钥并存储在钥匙串中。
2. 在
encodeRestorableStateWithCoder
方法中,从钥匙串中读取密钥并将其用作加密操作的密钥。
3. 将加密后的数据使用
NSCoder
的
encodeObject
方法进行序列化。
4. 在
decodeRestorableStateWithCoder
方法中,执行相反的操作以恢复应用状态。
可以使用
SecureNSCoder
项目来帮助实现此功能。以下是使用该工具的示例项目步骤:
1. 在项目中包含
SecureArchiveDelegate
和
SimpleKeychainWrapper
文件。
2. 在视图控制器的
.h
文件中包含
SecureArchiverDelegate.h
。示例代码如下:
#import <UIKit/UIKit.h>
#import "SecureArchiverDelegate.h"
@interface ViewController : UIViewController
// Some simple properties, adding one for the delegate
@property (weak, nonatomic) IBOutlet UITextField *textField;
@property (weak, nonatomic) SecureArchiverDelegate *delegate;
@end
-
实现
initWithCoder方法。示例代码如下:
- (id)initWithCoder:(NSKeyedUnarchiver *)coder {
if (self = [super initWithCoder:coder]) {
return self;
}
return nil;
}
通过以上方法,可以有效避免 iOS 应用中的数据泄露问题,保护用户的敏感信息安全。
iOS 应用数据泄漏问题及解决策略(续)
5. 安全的状态保存(续)
SecureNSCoder
项目为开发者提供了便利,它能够自动为应用生成密钥,将其存储在钥匙串中,并使用该密钥对程序状态进行编码和解码。下面详细介绍使用该工具的具体操作步骤:
5.1 项目文件引入
首先,要在项目里包含
SecureArchiveDelegate
和
SimpleKeychainWrapper
文件。这两个文件是
SecureNSCoder
项目的重要组成部分,为后续的加密和解密操作提供了基础支持。
5.2 头文件包含
在视图控制器的
.h
文件中包含
SecureArchiverDelegate.h
,代码如下:
#import <UIKit/UIKit.h>
#import "SecureArchiverDelegate.h"
@interface ViewController : UIViewController
// Some simple properties, adding one for the delegate
@property (weak, nonatomic) IBOutlet UITextField *textField;
@property (weak, nonatomic) SecureArchiverDelegate *delegate;
@end
这样做的目的是引入
SecureArchiverDelegate
类,以便在视图控制器中使用其提供的功能。
5.3 方法实现
实现
initWithCoder
方法,代码如下:
- (id)initWithCoder:(NSKeyedUnarchiver *)coder {
if (self = [super initWithCoder:coder]) {
return self;
}
return nil;
}
该方法在从归档中恢复对象时被调用,确保对象能够正确初始化。
6. 总结与建议
为了更清晰地展示 iOS 应用数据泄漏的问题及解决策略,下面以表格形式进行总结:
| 数据泄漏问题 | 具体表现 | 解决策略 |
| ---- | ---- | ---- |
| 自动补全机制与按键记录 | 自动补全机制充当按键记录器,记录用户输入文本,iOS 5.1 后存在即使禁用相关功能仍更新磁盘缓存的问题 | 对于少量敏感数据字段,禁用自动补全;使用
UITextView
并设置
setSecureTextEntry:YES
或对
UITextField
先开后关
setSecureTextEntry
;检查
dynamic-text.dat
等文件 |
| 用户偏好的滥用 | 开发者使用
NSUserDefaults
或
CFPreferences
API 存储敏感信息,文件限制宽松易被读取操作 | 审核 API 使用,确保存储非敏感数据,让服务器执行安全控制决策 |
| 快照中的敏感数据处理 | iOS 快照可能将敏感信息存储到磁盘,攻击者可获取 | 采用屏幕清理策略,如放置启动屏幕、隐藏容器对象、执行动画、模糊截图等;防止暂停以避免快照 |
| 状态保存导致的泄漏 | 状态保存机制可能将敏感 UI 数据序列化到磁盘 | 检查代码库中
restorationIdentifier
;对敏感数据加密后再进行状态保存,可使用
SecureNSCoder
项目 |
以下是一个简单的 mermaid 流程图,展示了处理数据泄漏问题的整体流程:
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A(发现数据泄漏风险):::process --> B(确定泄漏类型):::process
B --> C{类型判断}:::process
C -->|自动补全与按键记录| D(禁用自动补全,采用特殊方法):::process
C -->|用户偏好滥用| E(审核 API 使用):::process
C -->|快照敏感数据| F(屏幕清理或防止暂停):::process
C -->|状态保存泄漏| G(加密敏感数据):::process
D --> H(验证效果):::process
E --> H
F --> H
G --> H
7. 注意事项
在实际开发过程中,还需要注意以下几点:
-
操作系统版本
:在使用一些特殊方法(如对
setSecureTextEntry
进行开关操作)时,要确保应用运行的操作系统版本已经过测试,因为某些 bug 可能在未来版本中得到修复。
-
测试验证
:无论是采用屏幕清理策略还是加密状态保存,都要进行充分的测试验证。例如,多次在不同应用状态下暂停应用,检查相关文件是否包含敏感信息。
-
代码规范
:在编写代码时,要遵循良好的代码规范,确保代码的可读性和可维护性。例如,在使用
NSUserDefaults
或
CFPreferences
API 时,要明确存储的数据类型和用途。
通过以上对 iOS 应用数据泄漏问题的分析和解决策略的介绍,开发者可以在开发过程中采取有效的措施,保护用户的敏感信息,提高应用的安全性。同时,不断关注操作系统的更新和安全漏洞的发现,及时调整开发策略,以应对不断变化的安全挑战。
超级会员免费看
1234

被折叠的 条评论
为什么被折叠?



