详解NSURLCache缓存

1,757 阅读5分钟

前言

最近项目App交付第三方安全机构进行安全检测,暴露出了一个安全漏洞,具体表现为在沙盒里面检测到了交易登录账号和密码明文(HTTPS接口),如果手机被盗丢失就有可能被破解获取到(虽然手机被盗丢失概率甚微再加上密码破解、越狱等一系列操作难度也甚大,但作为一名程序猿我们还是得从理论上来避免不是)。刚看到报告的时候,第一时间是有点诧异的,因为从来就没主动保存过密码,后来发现原来是NSURLCache引起的。

NSURLCache

NSURLCache将NSURLRequest和NSCachedURLResponse一一映射起来,配合NSURLRequestCachePolicy,iOS将为你建立了一套完整的HTTP缓存机制。NSURLRequestCachePolicy相信大家都很熟悉,这里不多说。NSURLCache同时提供了内存和磁盘的缓存,我们可以自定义缓存的大小;磁盘缓存路径默认为沙盒Library/Caches/{bundle id}/Cache.db,当然 我们也是可以自定义缓存路径的。

存储数据库

NSURLCache最终以数据库的形式存储数据,我们打开Cache.db一探究竟。我们只需关注下图的三个表:

  • cfurl_cache_response,包含了request_key字段,也就是我们请求的url,如下图: 看到我在测试环境下的登录接口是http://***/login(https表现一样),同时还包含了一个主键entry_ID。
  • cfurl_cache_receiver_data,包含了服务端返回的数据,我们根据上图entry_ID查找,数据如下图:
  • cfurl_cache_blob_data, 包含了请求和响应数据,格式为blob,如下图: 我们将其保存成文本,然后命令行cat即可查看到里面包含了请求的参数,如下图为登录的账号和密码: 同时我们看到文件内容以bplist00开头,其实它就是一个plist文件。

删除缓存

好了,至此我们已经定位到了账号密码的缓存所在,为安全起见,必须将其删除掉。查一下API文档,NSURLCache提供了以下两个针对特定请求删除缓存的接口,那么我们顺理成章地调用它们不就把缓存解决掉了吗?事与愿违地是,经调用测试发现这两个接口都是无效的,并不能把对应的缓存删除掉,具体得等苹果爸爸在后续哪个版本修复了:

- removeCachedResponseForRequest:
    Removes the cached URL response for a specified URL request.
- removeCachedResponseForDataTask:
    Removes the cached URL response for a specified data task.

此外,还有两个接口可以删除缓存,不过并不能做到针对具体请求,所以并不满足我们的需求。

- removeCachedResponsesSinceDate:
    Clears the given cache of any cached responses since the provided date.
- removeAllCachedResponses
    Clears the receiver’s cache, removing all stored cached URL responses.

删除缓存的方案宣告失败。

Cache-Control

转而一想,HTTP头部有个Cache-Control字段,我们何不利用它来管理缓存的存储方案呢? Cache-Control对应请求和响应又有不同的值,主要包括以下:

  • 请求指令:
字段说明
max-age=<seconds>设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)。与Expires相反,时间是相对于请求的时间
max-stale[=<seconds>]表明客户端愿意接收一个已经过期的资源。 可选的设置一个时间(单位秒),表示响应不能超过的过时时间。
min-fresh=<seconds>表示客户端希望在指定的时间内获取最新的响应。
no-cache在释放缓存副本之前,强制高速缓存将请求提交给原始服务器进行验证。
no-store缓存不应存储有关客户端请求或服务器响应的任何内容。
no-transform不得对资源进行转换或转变。Content-Encoding, Content-Range, Content-Type等HTTP头不能由代理修改。例如,非透明代理可以对图像格式进行转换,以便节省缓存空间或者减少缓慢链路上的流量。 no-transform指令不允许这样做。
only-if-cached表明客户端只接受已缓存的响应,并且不要向原始服务器检查是否有更新的拷贝
  • 响应指令:
字段说明
must-revalidate缓存必须在使用之前验证旧资源的状态,并且不可使用过期资源。
no-cache在释放缓存副本之前,强制高速缓存将请求提交给原始服务器进行验证。
no-store缓存不应存储有关客户端请求或服务器响应的任何内容。
no-transform
public表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存。
private表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它),可以缓存响应内容。
proxy-revalidate与must-revalidate作用相同,但它仅适用于共享缓存(例如代理),并被私有缓存忽略。
max-age=<seconds>设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)。与Expires相反,时间是相对于请求的时间
s-maxage=<seconds>覆盖max-age 或者 Expires 头,但是仅适用于共享缓存(比如各个代理),并且私有缓存中它被忽略。

这里我们只要针对该请求或者服务端响应设置no-store值,客户端NSURLCache就不会对其进行缓存,问题得以完美解决。

小结

涉及到安全的问题还是得慎重严谨处理为上,关于此次NSURLCache引发的安全漏洞还是相对隐蔽的,因为并不是我们的主动行为。以上内容如有错误纰漏,欢迎指正。