前言
最近專案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引發的安全漏洞還是相對隱蔽的,因為並不是我們的主動行為。以上內容如有錯誤紕漏,歡迎指正。