WebView security on iOS
Łukasz Pilorz"
!
OWASP Poland meeting, 29 Jan 2014
Thank you:
browser-shredders.blogspot.com

Mike Tigas
Theory
[webView loadRequest:
[NSURLRequest requestWithURL:
[NSURL URLWithString:@“http://example.com“]
]
];
!
- loadRequest:
!
- loadHTMLString:baseURL:
!
- loadData:MIMEType:

textEncodingName:baseURL:
!
- stringByEvaluatingJavaScriptFromString:
!
- goBack
!
- goForward
!
- stopLoading
!
- reload
!
id<UIWebViewDelegate> delegate"
!
NSURLRequest request"
!
UIDataDetectorTypes dataDetectorTypes"
!
enum {
UIDataDetectorTypePhoneNumber = 1 << 0,
UIDataDetectorTypeLink = 1 << 1,
UIDataDetectorTypeAddress = 1 << 2,
UIDataDetectorTypeCalendarEvent = 1 << 3,
UIDataDetectorTypeNone = 0,
UIDataDetectorTypeAll = NSUIntegerMax
}
!
…
!
https://developer.apple.com/library/ios/documentation/
uikit/reference/UIWebView_Class/Reference/
Reference.html
UIWebViewDelegate
– webView:shouldStartLoadWithRequest:navigationType:
– webViewDidStartLoad:
– webViewDidFinishLoad:
– webView:didFailLoadWithError:
Questions:

How to recognize whether navigation happened in top document or a frame?

How to block images or JavaScript?

Can webViewDidFinishLoad not happen after webViewDidStartLoad?

Can webViewDidStartLoad not happen before webViewDidFinishLoad?
Limitations
• Lack of Nitro
• HTTP 401 not supported natively
• No option to turn off JavaScript
• [Also applies to Mobile Safari]

Content-Disposition: attachment; filename=“download.html”

Content-Type: text/plain

- guess how will UIWebView behave (see CVE-2011-3426, CVE-2013-5151)
• Blocks JavaScript on scrolling
• Limited support for target attribute and window.open() ~ document.location.assign()
• Does not support RSS
Practice
Advantages
• Content update without App Store update
• HTML5 + JavaScript + CSS
• Possibility to re-use code on many platforms

(+ Apache Cordova / PhoneGap)
• .html / .key / .numbers / .pages / .xls / .pdf / .ppt / .doc / .rftd.zip / .rtf
• Automatic SSL certificate verification
• Same Origin Policy… non-standard one
Security guidelines
• “Ensure that all UIWebView calls do not execute without proper input validation. Apply filters
for dangerous JavaScript characters if possible, using a whitelist over blacklist character
policy before rendering. If possible call mobile Safari instead of rending inside of UIWebView
which has access to your application.” (OWASP Mobile Top 10)
• “[…] maintain control of all UIWebView content and pages, and prevent the user from
accessing arbitrary, untrusted web content.” (OWASP iOS Developer Cheat Sheet)
• “Inspect remote content via the use of the NSData class method dataWithContentsOfURL
in an attempt to prohibit the loading of malicious script into a UIWebview. Do not load
content remotely and then process the data returned before passing to a UIWebview (if
at all avoidable) otherwise you grant local file system access to any malicious script that
smuggles itself past your content inspectors.” (MWR Labs blog)
• Sounds dangerous… :-)
UIWebView in iOS applications
• Chrome
• Coast
• Facebook
• SkyDrive
• Skype
• WinZip
• and hundreds of others
Secure UIWebView - how to start?
Requirements:
• without reducing planned functionality
• without spending weeks on building content filters

(and further ones on maintenance and fixes)
• minimal amount of code added
• efficiently
Step 1
Probably NO, if it’s mobile banking:

http://blog.ioactive.com/2014/01/personal-banking-apps-leak-info-
through.html
Is UIWebView needed in your application?
YES
!
NO



These aren't the droids we're looking for.
You can go about your business.
Step 2
Do the documents, which you intend to display,
need to be displayed in your application?
YES
!
!
!
NO
!
Use Safari,

Chrome (x-callback-url?)

or another available browser
Step 3
Is the presented document loaded directly through HTTP?
YES
- loadRequest(…)
!
Use https://
!
Don’t turn off SSL

certificate validation
NO
- data passed locally

!
!
!
Remember to set baseURL"
!
- loadRequest:
!
- loadHTMLString:baseURL:
!
- loadData:MIMEType:

textEncodingName:baseURL:
!
- stringByEvaluatingJavaScriptFromString:
!
- goBack
!
- goForward
!
- stopLoading
!
- reload
baseURL vs Same Origin Policy
• file:/// can read local files and any URLs - dangerous!
• nil/NULL == applewebdata:

same privileges as file: - dangerous!
• by default UIWebView assumes file://

(@“test” == @“file://test”)
• for http(s):// standard Same Origin Policy applies
• for about: and data: also, but with separate origin context
<script>

a = document.location.href.split('/');

if(a[0]==='file:') {

path = ‘file:///'+a[3]+'/'+a[4]+'/'+a[5]+'/'+a[6]+'/'+a[7]

+'/Library/Cookies/Cookies.binarycookies';

x = new XMLHttpRequest();

x.open('GET', path, false);

x.send();

alert(x.responseText);

}

</script>
[webView

loadHTMLString:

[NSString stringWithContentsOfFile:@“/sciezka/do/pliku.html”

encoding:NSUTF8StringEncoding

error:&error]

baseURL:[NSURL URLWithString:@“about:blank”]];
!
Potential problem: images, CSS etc. won’t be loaded from file:///
Example: Chrome for iOS
<!-- CVE-2012-2899 -->
!
<script>
function test() {
pop = window.open('about:blank', '_blank');
pop.document.write(
'<script>document.write(document.location)</scr'
+'ipt><br><iframe src=“http://example.com/“'
+'onload="alert(this.contentDocument.body.innerHTML)"></iframe>'
);
}
</script>
<input type="button" onclick="test()" value=“Click">
Example: Coast by Opera
http://www.youtube.com/watch?v=_J-qe61_tAQ
Demo
Step 4
Do you have control over the content loaded to UIWebView?
YES
- I have control over content
!
Make sure the documents are not
vulnerable to XSS
NO
- I don’t have control over content
!
Can the user recognize origin?

!
Use CSP or HTML sandbox
User interface
• clear separation of trusted and untrusted content
• address bar with current URL



webView.request.mainDocumentURL.absoluteString

vs

[webView stringByEvaluatingJavaScriptFromString:@"window.location.href"]
• SSL indicator
• warning before first display of untrusted document
• other ideas?
Cross-Site Scripting
• Stored (server-side or in the application)
• Reflected (watch for URL scheme handlers)
• DOM-based (!)
• [webView stringByEvaluatingJavaScriptFromString:[NSString
stringWithFormat:@"document.body.innerText='%@'", input]];
Cross-Site Scripting/JavaScript Injection
input: ';alert(0)//🌙ꆁ
!
[webView stringByEvaluatingJavaScriptFromString:[NSString
stringWithFormat:@"document.body.innerText='%@'", input]];
!
document.body.innerText='';alert(0)//🌙ꆁ'
- (NSString*) escapeForJavaScript:(NSString*)fromString
{
NSString *toString = @"";
for(int i=0;i<fromString.length;i++) {
toString = [NSString stringWithFormat:@“%@u%04X",
toString, [fromString characterAtIndex:i]
];
}
return toString;
}
escapeForJavaScript
input: ‘;alert(0)//🌙ꆁ
!
[webView stringByEvaluatingJavaScriptFromString:[NSString

stringWithFormat:@"document.body.innerText='%@'",

[self escapeForJavaScript:input]
]];
document.body.innerText='u0027u003Bu0061u006C
u0065u0072u0074u0028u0030u0029u002Fu002FuD83C
uDF19uA181'
innerHTML
[webView stringByEvaluatingJavaScriptFromString:[NSString

stringWithFormat:@"document.body.innerHTML='%@'",

[self escapeForJavaScript:input]
]];
!
Question: why the above code is not secure?
innerHTML
input: <img src=x onerror=alert(0)>
!
[webView stringByEvaluatingJavaScriptFromString:[NSString

stringWithFormat:@"document.body.innerHTML='%@'",

[self escapeForJavaScript:input]
]];
document.body.innerHTML='u003Cu0069u006D
u0067u0020u0073u0072u0063u003Du0078u0020u006F
u006Eu0065u0072u0072u006Fu0072u003Du0061u006C
u0065u0072u0074u0028u0030u0029u003E'
Step 5
Additional security
Whitelisting allowed URLs
!
http
https
data
about
Turning off JavaScript

!
Content-Security-Policy
HTML5 Sandbox
!
What can go wrong?
[webView loadRequest:

[NSURLRequest requestWithURL:
[NSURL URLWithString:@“https://unknown.tld/untrusted.php“]

]

];
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response
{
NSMutableDictionary *mHeaders = [NSMutableDictionary dictionary];
NSString *CSP = @"default-src 'none'; img-src *;style-src 'unsafe-inline' *;child-src *;frame-src *;sandbox allow-
forms allow-top-navigation";
for(id h in response.allHeaderFields) {
if(![[h lowercaseString] isEqualToString:@"content-security-policy"]

&& ![[h lowercaseString] isEqualToString:@"x-webkit-csp"]) {
[mHeaders setObject:response.allHeaderFields[h] forKey:h];
}
}
[mHeaders setObject:CSP forKey:@"Content-Security-Policy"];
[mHeaders setObject:CSP forKey:@"X-Webkit-CSP"];
NSHTTPURLResponse *mResponse = [[NSHTTPURLResponse alloc]

initWithURL:response.URL statusCode:response.statusCode

HTTPVersion:@"HTTP/1.1" headerFields:mHeaders
];
[self.client URLProtocol:self didReceiveResponse:mResponse

cacheStoragePolicy:NSURLCacheStorageNotAllowed
];
}
?
//<UIWebViewDelegate>

- (BOOL)webView:(UIWebView *)webView

shouldStartLoadWithRequest:(NSURLRequest *)request 

navigationType:(UIWebViewNavigationType)navigationType

{

if([request.URL.scheme isEqualToString:@"http"

|| [request.URL.scheme isEqualToString:@"https"]

|| [request.URL.scheme isEqualToString:@"about"]

|| [request.URL.scheme isEqualToString:@“data”]) {

return YES;

}

return NO;

}
Question: Will the above code block javascript: URLs? Where?
Step 6
What did we forget?
Pentest
Cordova/PhoneGap
!
and other
Javascript/Objective-C bridges
…
Links
(OWASP)
• https://www.owasp.org/index.php/IOS_Application_Security_Testing_Cheat_Sheet
• https://www.owasp.org/index.php/IOS_Developer_Cheat_Sheet
!
(iOS)
• http://www.apple.com/business/accelerator/develop/security.html & https://developer.apple.com/videos/wwdc/2010/
• http://stackoverflow.com/questions/3496505/differences-between-uiwebview-and-mobile-safari
!
(CSP)
• https://www.owasp.org/images/2/2b/Oxdef_csp_poland.pdf & http://niebezpiecznik.pl/OWASP2013-Krakow-CSP.pdf
• http://lists.w3.org/Archives/Public/public-webappsec/2012Mar/0043.html
http://browser-shredders.blogspot.com
Teaser: Breaking iOS browsers (before it will be cool ;-)
lukasz.pilorz@runic.pl
WebView security on iOS (EN)

WebView security on iOS (EN)

  • 1.
    WebView security oniOS Łukasz Pilorz" ! OWASP Poland meeting, 29 Jan 2014
  • 2.
  • 3.
  • 4.
    [webView loadRequest: [NSURLRequest requestWithURL: [NSURLURLWithString:@“http://example.com“] ] ];
  • 6.
    ! - loadRequest: ! - loadHTMLString:baseURL: ! -loadData:MIMEType:
 textEncodingName:baseURL: ! - stringByEvaluatingJavaScriptFromString: ! - goBack ! - goForward ! - stopLoading ! - reload
  • 7.
    ! id<UIWebViewDelegate> delegate" ! NSURLRequest request" ! UIDataDetectorTypesdataDetectorTypes" ! enum { UIDataDetectorTypePhoneNumber = 1 << 0, UIDataDetectorTypeLink = 1 << 1, UIDataDetectorTypeAddress = 1 << 2, UIDataDetectorTypeCalendarEvent = 1 << 3, UIDataDetectorTypeNone = 0, UIDataDetectorTypeAll = NSUIntegerMax } ! … ! https://developer.apple.com/library/ios/documentation/ uikit/reference/UIWebView_Class/Reference/ Reference.html
  • 8.
    UIWebViewDelegate – webView:shouldStartLoadWithRequest:navigationType: – webViewDidStartLoad: –webViewDidFinishLoad: – webView:didFailLoadWithError: Questions:
 How to recognize whether navigation happened in top document or a frame?
 How to block images or JavaScript?
 Can webViewDidFinishLoad not happen after webViewDidStartLoad?
 Can webViewDidStartLoad not happen before webViewDidFinishLoad?
  • 9.
    Limitations • Lack ofNitro • HTTP 401 not supported natively • No option to turn off JavaScript • [Also applies to Mobile Safari]
 Content-Disposition: attachment; filename=“download.html”
 Content-Type: text/plain
 - guess how will UIWebView behave (see CVE-2011-3426, CVE-2013-5151) • Blocks JavaScript on scrolling • Limited support for target attribute and window.open() ~ document.location.assign() • Does not support RSS
  • 10.
  • 11.
    Advantages • Content updatewithout App Store update • HTML5 + JavaScript + CSS • Possibility to re-use code on many platforms
 (+ Apache Cordova / PhoneGap) • .html / .key / .numbers / .pages / .xls / .pdf / .ppt / .doc / .rftd.zip / .rtf • Automatic SSL certificate verification • Same Origin Policy… non-standard one
  • 12.
    Security guidelines • “Ensurethat all UIWebView calls do not execute without proper input validation. Apply filters for dangerous JavaScript characters if possible, using a whitelist over blacklist character policy before rendering. If possible call mobile Safari instead of rending inside of UIWebView which has access to your application.” (OWASP Mobile Top 10) • “[…] maintain control of all UIWebView content and pages, and prevent the user from accessing arbitrary, untrusted web content.” (OWASP iOS Developer Cheat Sheet) • “Inspect remote content via the use of the NSData class method dataWithContentsOfURL in an attempt to prohibit the loading of malicious script into a UIWebview. Do not load content remotely and then process the data returned before passing to a UIWebview (if at all avoidable) otherwise you grant local file system access to any malicious script that smuggles itself past your content inspectors.” (MWR Labs blog) • Sounds dangerous… :-)
  • 13.
    UIWebView in iOSapplications • Chrome • Coast • Facebook • SkyDrive • Skype • WinZip • and hundreds of others
  • 14.
    Secure UIWebView -how to start? Requirements: • without reducing planned functionality • without spending weeks on building content filters
 (and further ones on maintenance and fixes) • minimal amount of code added • efficiently
  • 15.
    Step 1 Probably NO,if it’s mobile banking:
 http://blog.ioactive.com/2014/01/personal-banking-apps-leak-info- through.html Is UIWebView needed in your application? YES ! NO
 
 These aren't the droids we're looking for. You can go about your business.
  • 16.
    Step 2 Do thedocuments, which you intend to display, need to be displayed in your application? YES ! ! ! NO ! Use Safari,
 Chrome (x-callback-url?)
 or another available browser
  • 17.
    Step 3 Is thepresented document loaded directly through HTTP? YES - loadRequest(…) ! Use https:// ! Don’t turn off SSL
 certificate validation NO - data passed locally
 ! ! ! Remember to set baseURL"
  • 18.
    ! - loadRequest: ! - loadHTMLString:baseURL: ! -loadData:MIMEType:
 textEncodingName:baseURL: ! - stringByEvaluatingJavaScriptFromString: ! - goBack ! - goForward ! - stopLoading ! - reload
  • 19.
    baseURL vs SameOrigin Policy • file:/// can read local files and any URLs - dangerous! • nil/NULL == applewebdata:
 same privileges as file: - dangerous! • by default UIWebView assumes file://
 (@“test” == @“file://test”) • for http(s):// standard Same Origin Policy applies • for about: and data: also, but with separate origin context
  • 20.
    <script>
 a = document.location.href.split('/');
 if(a[0]==='file:'){
 path = ‘file:///'+a[3]+'/'+a[4]+'/'+a[5]+'/'+a[6]+'/'+a[7]
 +'/Library/Cookies/Cookies.binarycookies';
 x = new XMLHttpRequest();
 x.open('GET', path, false);
 x.send();
 alert(x.responseText);
 }
 </script>
  • 21.
  • 22.
  • 23.
    <!-- CVE-2012-2899 --> ! <script> functiontest() { pop = window.open('about:blank', '_blank'); pop.document.write( '<script>document.write(document.location)</scr' +'ipt><br><iframe src=“http://example.com/“' +'onload="alert(this.contentDocument.body.innerHTML)"></iframe>' ); } </script> <input type="button" onclick="test()" value=“Click">
  • 25.
  • 28.
  • 31.
    Step 4 Do youhave control over the content loaded to UIWebView? YES - I have control over content ! Make sure the documents are not vulnerable to XSS NO - I don’t have control over content ! Can the user recognize origin?
 ! Use CSP or HTML sandbox
  • 32.
    User interface • clearseparation of trusted and untrusted content • address bar with current URL
 
 webView.request.mainDocumentURL.absoluteString
 vs
 [webView stringByEvaluatingJavaScriptFromString:@"window.location.href"] • SSL indicator • warning before first display of untrusted document • other ideas?
  • 35.
    Cross-Site Scripting • Stored(server-side or in the application) • Reflected (watch for URL scheme handlers) • DOM-based (!) • [webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"document.body.innerText='%@'", input]];
  • 36.
    Cross-Site Scripting/JavaScript Injection input:';alert(0)//🌙ꆁ ! [webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"document.body.innerText='%@'", input]]; ! document.body.innerText='';alert(0)//🌙ꆁ'
  • 38.
    - (NSString*) escapeForJavaScript:(NSString*)fromString { NSString*toString = @""; for(int i=0;i<fromString.length;i++) { toString = [NSString stringWithFormat:@“%@u%04X", toString, [fromString characterAtIndex:i] ]; } return toString; }
  • 39.
    escapeForJavaScript input: ‘;alert(0)//🌙ꆁ ! [webView stringByEvaluatingJavaScriptFromString:[NSString
 stringWithFormat:@"document.body.innerText='%@'",
 [selfescapeForJavaScript:input] ]]; document.body.innerText='u0027u003Bu0061u006C u0065u0072u0074u0028u0030u0029u002Fu002FuD83C uDF19uA181'
  • 41.
  • 42.
    innerHTML input: <img src=xonerror=alert(0)> ! [webView stringByEvaluatingJavaScriptFromString:[NSString
 stringWithFormat:@"document.body.innerHTML='%@'",
 [self escapeForJavaScript:input] ]]; document.body.innerHTML='u003Cu0069u006D u0067u0020u0073u0072u0063u003Du0078u0020u006F u006Eu0065u0072u0072u006Fu0072u003Du0061u006C u0065u0072u0074u0028u0030u0029u003E'
  • 44.
    Step 5 Additional security Whitelistingallowed URLs ! http https data about Turning off JavaScript
 ! Content-Security-Policy HTML5 Sandbox ! What can go wrong?
  • 45.
    [webView loadRequest:
 [NSURLRequest requestWithURL: [NSURLURLWithString:@“https://unknown.tld/untrusted.php“]
 ]
 ];
  • 46.
    - (void)connection:(NSURLConnection *)connectiondidReceiveResponse:(NSHTTPURLResponse *)response { NSMutableDictionary *mHeaders = [NSMutableDictionary dictionary]; NSString *CSP = @"default-src 'none'; img-src *;style-src 'unsafe-inline' *;child-src *;frame-src *;sandbox allow- forms allow-top-navigation"; for(id h in response.allHeaderFields) { if(![[h lowercaseString] isEqualToString:@"content-security-policy"]
 && ![[h lowercaseString] isEqualToString:@"x-webkit-csp"]) { [mHeaders setObject:response.allHeaderFields[h] forKey:h]; } } [mHeaders setObject:CSP forKey:@"Content-Security-Policy"]; [mHeaders setObject:CSP forKey:@"X-Webkit-CSP"]; NSHTTPURLResponse *mResponse = [[NSHTTPURLResponse alloc]
 initWithURL:response.URL statusCode:response.statusCode
 HTTPVersion:@"HTTP/1.1" headerFields:mHeaders ]; [self.client URLProtocol:self didReceiveResponse:mResponse
 cacheStoragePolicy:NSURLCacheStorageNotAllowed ]; } ?
  • 47.
    //<UIWebViewDelegate>
 - (BOOL)webView:(UIWebView *)webView
 shouldStartLoadWithRequest:(NSURLRequest*)request 
 navigationType:(UIWebViewNavigationType)navigationType
 {
 if([request.URL.scheme isEqualToString:@"http"
 || [request.URL.scheme isEqualToString:@"https"]
 || [request.URL.scheme isEqualToString:@"about"]
 || [request.URL.scheme isEqualToString:@“data”]) {
 return YES;
 }
 return NO;
 } Question: Will the above code block javascript: URLs? Where?
  • 48.
    Step 6 What didwe forget? Pentest Cordova/PhoneGap ! and other Javascript/Objective-C bridges
  • 49.
  • 50.
    Links (OWASP) • https://www.owasp.org/index.php/IOS_Application_Security_Testing_Cheat_Sheet • https://www.owasp.org/index.php/IOS_Developer_Cheat_Sheet ! (iOS) •http://www.apple.com/business/accelerator/develop/security.html & https://developer.apple.com/videos/wwdc/2010/ • http://stackoverflow.com/questions/3496505/differences-between-uiwebview-and-mobile-safari ! (CSP) • https://www.owasp.org/images/2/2b/Oxdef_csp_poland.pdf & http://niebezpiecznik.pl/OWASP2013-Krakow-CSP.pdf • http://lists.w3.org/Archives/Public/public-webappsec/2012Mar/0043.html
  • 52.
  • 53.