UIWebView/WKWebView存在控件跨域访问漏洞(CNNVD-201801-515)
漏洞描述
国家信息安全漏洞库(CNNVD)收录,iOS 平台WebView组件漏洞(UIWebView/ WKWebView)跨域访问漏洞(CNNVD-201801-515):成成功利用该漏洞的攻击者可以远程获取手机应用沙盒内所有本地文件系统内容,包括浏览器的Cookies、用户的配置文件、文档等敏感信息。iOS平台使用了WebView组件的应用可能均受影响。
漏洞介绍
WebView是iOS用于显示网页的控件,是一个基于Webkit引擎、展现web页面的控件。WebView控件功能除了具有一般View的属性和设置外,还可对URL请求、页面加载、渲染、页面交互进行处理。
iOS平台的WebView组件(UIWebView/WKWebView)存在控件跨域访问漏洞(CNNVD-201801-515),漏洞源于UIWebView 默认允许“file://” 域发起跨域请求,而WKWebView可通过手动设置允许“file://”域发起跨域请求。攻击者可利用App文件下载机制将恶意文件写入沙盒内并诱导用户打开,当用户打开恶意文件时,其中的恶意代码可通过AJAX向“file://”域发起请求,从而远程获取App沙盒内所有的本地敏感数据。
漏洞等级
根据CNNVD漏洞分级规范,该漏洞的危害评级为高危。
漏洞验证
UIWebView
做iOS开发经常用到UIWebView,大多时候是加载外部地址,但是有一些时候也会用来加载本地的html文件。
UIWebView加载外部地址的时候遵循了“同源”策略,而加载本地网页的时候却绕够了“同源”策略,导致可以访问系统任意路径。
这就是UIWebView中存在的UXSS漏洞。
恶意网页
以下是HTML 代码快。
iOS源码(部分)
本文通过APP中UIWebView去读取本地index.html文件方式。
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
NSURL *url = [[NSURL alloc] initWithString:filePath];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[self.webView loadRequest:request];
运行结果
测试结果
UIWebView 允许“file://” 域发起跨域请求,通过 JavaScript可以访问沙箱内的文件,甚⾄至可以静默上传文件到远端,默认存在此漏洞。
WKWebView
从 iOS 8.0 和 OS X 10.10 开始,在你的APP中使用 WKWebView 添加网页内容,不要使用 UIWebView 或 WebView。
WKWebView可将网页处理限制在App的网页视图中,从而确保不安全的网站内容不会影响到App的其他部分。此外,iOS、macOS和Mac Catalyst均支持WKWebView。
WKWebView 可通过手动设置允许“file://”域发起跨域请求。
WKWebView 默认 allowFileAccessFromFileURLs 和 allowUniversalAccessFromFileURLs 选项为 false。
恶意网页
以下是HTML 代码快。
iOS源码
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
configuration.preferences.javaScriptEnabled = YES;
configuration.preferences.javaScriptCanOpenWindowsAutomatically = YES;
configuration.suppressesIncrementalRendering = YES; // 是否支持记忆读取
[configuration.preferences setValue:@YES forKey:@"allowFileAccessFromFileURLs"];
if (@available(iOS 10.0, *)) {
[configuration setValue:@YES forKey:@"allowUniversalAccessFromFileURLs"];
}
WKWebView *wkweb = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:configuration];
wkweb.center = self.view.center;
wkweb.UIDelegate = self;//代理,需要实现alert
[self.view addSubview:wkweb];
wkweb.backgroundColor = UIColor.redColor;
NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
NSString *filePath =[resourcePath stringByAppendingPathComponent:@"index.html"];
NSMutableString *htmlstring=[[NSMutableString alloc] initWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
NSURL *baseUrl=[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
[wkweb loadHTMLString:htmlstring baseURL:baseUrl];
运行结果
无信息展示。
测试结果
无法复现漏洞。
漏洞总结
市面上大多数使用HTML5技术开发的应用均使用WebView进行HTML5页面的展示。除了从远程服务器加载Web页面,WebView还可以通过修改特定配置,从文件中进行HTML5页面的加载。在未正确配置WebView的情况下,导致WebView同源策略失效,导致HTTP协议、file协议跨源攻击。该漏洞导致WebView能够访问当前应用内部数据,如果WebView加载了来源不明的HTML文件,可能导致当前应用内部数据被攻击者窃取,如身份认证信息、加密密钥、用户隐私信息等。
成功利用该漏洞的攻击者,可以远程获取手机应用沙盒内所有本地文件系统内容,包括浏览器的Cookies、用户的配置文件、文档等敏感信息。iOS平台的应用如果在使用WebView组件时,未考虑上述情况,则会受到漏洞影响。
附件
建议
- 避免开启文件域的全局访问;
- 避免在WebView中加载来自外部传入的“file://”域页面;
- 对于敏感信息数据,建议进行加密处理后存储,或使用iOS平台推荐的KeyChain服务进行存储,可缓解漏洞可能造成的破坏;
- WKWebView 检查 allowingReadAccessToURL 路径是否安全,WKWebView 默认不允许“file://”域发起跨域请求。
- WKWebView 默认 allowFileAccessFromFileURLs 和 allowUniversalAccessFromFileURLs 选项为 false。
官方警告
苹果表示,App仍在使用已弃用的UIWebView API嵌入网络内容的开发者,应尽快更新为WKWebView以提升安全性和稳定性。
WKWebView可将网页处理限制在App的网页视图中,从而确保不安全的网站内容不会影响到App的其他部分。此外,iOS、macOS和Mac Catalyst均支持WKWebView。
苹果提醒称,2020年4月起App Store将不再接受使用UIWebView的新App,2020年12月起将不再接受使用UIWebView的App更新。
修复方案
- 禁用从外部打开HTML文件;(切断攻击入口)
- 针对本地HTML文件中脚本做一些权限限制;(初步防范措施)
- 新增一个NSURLProtocol, 专门用来处理本地网页的加载,根据同源策略来安全地加载本地文件。(彻底的解决方案)
方案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
全局使用
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
// 注册我们的协议
[NSURLProtocol registerClass:[SeMobSandBoxFileProtocol class]];
return YES;
}
局部使用
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
// 注册我们的协议
[NSURLProtocol registerClass:[SeMobSandBoxFileProtocol class]];
}
SeMobSandBoxFileProtocol.h
#import
NS_ASSUME_NONNULL_BEGIN
@interface SeMobSandBoxFileProtocol : NSURLProtocol
@end
NS_ASSUME_NONNULL_END
SeMobSandBoxFileProtocol.m
#import "SeMobSandBoxFileProtocol.h"
@implementation SeMobSandBoxFileProtocol
+ (NSArray *)supportedScheme {
return [NSArray arrayWithObjects:@"file", nil];
}
/// 表示是否走该NSURLProtocol的处理逻辑
/// 返回:YES 表示走。
/// 返回:NO 表示不走。
/// @param request 请求
+ (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 {
NSError * _Nonnull error = [NSError errorWithDomain:@"CFNetwork"
code:kCFURLErrorUnknown
userInfo:@{@"NSErrorFailingURLKey":self.request.URL}];
[[self client] URLProtocol:self didFailWithError:error];
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}
@end
代码分析
总体思路是根据主资源与子资源的文件路径判断它们是不是父子目录关系,如果是的话,就允许访问子资源,否则就不允许,这样就阻止了子资源访问主资源对应目录以外的目录,因为判断是否为父子目录关系,是根据是否包含目录前缀来判断的,所以需要对路径进行过滤处理,把路径中#号后面的内容连同#一起过滤掉。