IOS中UIWebView的UXSS漏洞及修複方法
做IOS開發的同學經常用到UIWebView,大多時候是載入外部地址,但是有一些時候也會用來載入本地的html檔案。
UIWebView載入外部地址的時候遵循了“同源”策略,而載入本地網頁的時候卻繞夠了“同源”策略,導致可以訪問系統任意路徑。
這就是UIWebView中存在的UXSS漏洞。已知尚未修複該漏洞的App有:微盤、檔案全能王、QQ閱讀。
漏洞複現方式大體相似,現在微盤為例:
在PC上編輯一個網頁,命名為test.html. 內容如下:
<script>alert(document.location);var aim='file:///private/etc/passwd';var d=document;function doAttack(){ var xhr1= new XMLHttpRequest(); xhr1.overrideMimeType('text/plain; charset=iso-8859-1'); xhr1.open('GET',aim); xhr1.onreadystatechange = function() { if(xhr1.readyState ==4) { var txt=xhr1.responseText; alert(txt); } }; xhr1.send();}doAttack();</script>
通過檔案發送到手機端,在手機端點擊剛才發過來的檔案,選擇用其他應用開啟,在彈出來的應用列表裡選擇“微盤”,這個時候會進入微盤介面,點擊上傳按鈕,上傳完畢後,在我的微盤檔案清單中點擊剛才上傳的檔案,這個時候會彈出一個alert框顯示當前檔案所在路徑,點擊“好”,接著就會顯示系統賬戶和密碼資訊(也就是passwd檔案的內容)。
如下:
修複方案
<1>禁用從外部開啟HTML檔案;(切斷攻擊入口)
<2>針對本地HTML檔案中指令碼做一些許可權限制;(初步防範措施)
<3>新增一個NSURLProtocol, 專門用來處理本地網頁的載入,根據同源策略來安全地載入本地檔案。(徹底的解決方案)
前面兩種方案相對簡單一些,這裡不贅述。
這裡主要講講第三種方案,
我們知道IOS中對於各種協議(http,https, ftp, file)的處理都是通過NSURLProtocol來實現的,
每一種對應了一個NSURLProtocol,有以下幾個重要的方法:
+ (BOOL)registerClass:(Class)protocolClass
註冊NSURLProtocol,
+ (void)unregisterClass:(Class)protocolClass
反註冊NSURLProtocol
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
表示是否走該NSURLProtocol的處理邏輯,返回YES,表示走,NO 表示不走,
- (void)startLoading
表示開始載入請求,由系統調用該方法,我們只需在該方法內部做網路資料請求就可以
- (void)stopLoading
表示停止載入請求,由系統調用該方法,我們只需在該方法內部做一些取消請求操作
我們建立一個類派生自NSURLProtocol, 暫且命名為SeMobSandBoxFileProtocol
在AppDelegate的application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions回調中調用
[NSURLProtocol registerClass:[SeMobSandBoxFileProtocol class]]; //註冊我們的協議
SeMobSandBoxFileProtocol.h、SeMobSandBoxFileProtocol.m內容分別如下:
#import @interface SeMobSandBoxFileProtocol : NSURLProtocol@end
#import "SeMobSandBoxFileProtocol.h"@implementation SeMobSandBoxFileProtocol+ (NSArray *)supportedScheme{ return [NSArray arrayWithObjects:@"file", nil];}+ (BOOL)canInitWithRequest:(NSURLRequest *)request{ NSURL* url=[request URL]; NSUInteger index = [[self supportedScheme] indexOfObject:[url scheme]]; if (index!=NSNotFound) { NSURL* baseURL=[[request mainDocumentURL] URLByDeletingLastPathComponent]; NSString* baseString=[[baseURL absoluteString] lowercaseString]; //得到主資源的路徑 NSRange sharpRange=[baseString rangeOfString:@"#"]; if (sharpRange.length) { baseString=[baseString substringToIndex:sharpRange.location]; //路徑過濾處理,去掉#號以及#號後面的內容 } if([baseURL isFileURL]) { BOOL ok=![[[url absoluteString] lowercaseString] hasPrefix:baseString]; //判斷子資源路徑是否包含主資源路徑首碼 return ok; } else { return baseString.length>0; } } return NO;}- (void)stopLoading{ }-(void)startLoading{ [[self client] URLProtocol:self didFailWithError:[NSError errorWithDomain:@"CFNetwork" code:kCFURLErrorUnknown userInfo:@{@"NSErrorFailingURLKey":self.request.URL}]];}+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { return request;}@end
程式碼分析
總體思路是根據主資源與子資源的檔案路徑判斷它們是不是父子目錄關係,如果是的話,就允許訪問子資源,否則就不允許,這樣就阻止了子資源訪問主資源對應目錄以外的目錄,因為判斷是否為父子目錄關係,是根據是否包含目錄首碼來判斷的,所以需要對路徑進行過濾處理,把路徑中#號後面的內容連同#一起過濾掉。