mirror of
https://github.com/carlospolop/hacktricks
synced 2024-11-26 22:52:06 +00:00
290 lines
17 KiB
Markdown
290 lines
17 KiB
Markdown
# iOS WebViews
|
|
|
|
## WebViews types
|
|
|
|
WebViews are in-app browser components for displaying interactive **web** **content**. They can be used to embed web content directly into an app's user interface. iOS WebViews **support** **JavaScript** execution **by default**, so script injection and Cross-Site Scripting attacks can affect them.
|
|
|
|
* \*\*\*\*[**UIWebView**](https://developer.apple.com/documentation/uikit/uiwebview)**:** UIWebView is deprecated starting on iOS 12 and should not be used. It shouldn't be used. JavaScript cannot be disabled.
|
|
* \*\*\*\*[**WKWebView**](https://developer.apple.com/documentation/webkit/wkwebview): This is the appropriate choice for extending app functionality, controlling displayed content.
|
|
* **JavaScript** is enabled by default but thanks to the **`javaScriptEnabled`** property of `WKWebView`, it **can be completely disabled**, preventing all script injection flaws.
|
|
* The **`JavaScriptCanOpenWindowsAutomatically`** can be used to **prevent** JavaScript from **opening new windows**, such as pop-ups.
|
|
* The **`hasOnlySecureContent`** property can be used to verify resources loaded by the WebView are retrieved through encrypted connections.
|
|
* `WKWebView` implements out-of-process rendering, so **memory corruption bugs won't affect** the main app process.
|
|
* \*\*\*\*[**SFSafariViewController**](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller)**:** It ****should be used to provide a **generalized web viewing experience**. These WebViews can be easily spotted as they have a characteristic layout which includes the following elements:
|
|
|
|
* A read-only address field with a security indicator.
|
|
* An Action \("**Share**"\) **button**.
|
|
* A **Done button**, back and forward navigation buttons, and a "Safari" button to open the page directly in Safari.
|
|
|
|
![](https://gblobscdn.gitbook.com/assets%2F-LH00RC4WVf3-6Ou4e0l%2F-Lf1APQHyCHdAvoJSvc_%2F-Lf1AQxr7FPsOyPFSGcs%2Fsfsafariviewcontroller.png?alt=media)
|
|
|
|
* **JavaScript cannot be disabled** in `SFSafariViewController` and this is one of the reasons why the usage of `WKWebView` is recommended when the goal is extending the app's user interface.
|
|
* `SFSafariViewController` also **shares cookies** and other website data with **Safari**.
|
|
* The user's activity and interaction with a `SFSafariViewController` are **not visible to the app**, which cannot access AutoFill data, browsing history, or website data.
|
|
* According to the App Store Review Guidelines, `SFSafariViewController`s **may not be hidden or obscured by other views or layers**.
|
|
|
|
## Discovering WebViews Configuration
|
|
|
|
### Static Analysis
|
|
|
|
**UIWebView**
|
|
|
|
```bash
|
|
$ rabin2 -zz ./WheresMyBrowser | egrep "UIWebView$"
|
|
489 0x0002fee9 0x10002fee9 9 10 (5.__TEXT.__cstring) ascii UIWebView
|
|
896 0x0003c813 0x0003c813 24 25 () ascii @_OBJC_CLASS_$_UIWebView
|
|
1754 0x00059599 0x00059599 23 24 () ascii _OBJC_CLASS_$_UIWebView
|
|
```
|
|
|
|
**WKWebView**
|
|
|
|
```bash
|
|
$ rabin2 -zz ./WheresMyBrowser | egrep "WKWebView$"
|
|
490 0x0002fef3 0x10002fef3 9 10 (5.__TEXT.__cstring) ascii WKWebView
|
|
625 0x00031670 0x100031670 17 18 (5.__TEXT.__cstring) ascii unwindToWKWebView
|
|
904 0x0003c960 0x0003c960 24 25 () ascii @_OBJC_CLASS_$_WKWebView
|
|
1757 0x000595e4 0x000595e4 23 24 () ascii _OBJC_CLASS_$_WKWebView
|
|
```
|
|
|
|
Alternatively you can also search for known methods of these WebView classes. For example, search for the method used to initialize a WKWebView \([`init(frame:configuration:)`](https://developer.apple.com/documentation/webkit/wkwebview/1414998-init)\):
|
|
|
|
```bash
|
|
$ rabin2 -zzq ./WheresMyBrowser | egrep "WKWebView.*frame"
|
|
0x5c3ac 77 76 __T0So9WKWebViewCABSC6CGRectV5frame_So0aB13ConfigurationC13configurationtcfC
|
|
0x5d97a 79 78 __T0So9WKWebViewCABSC6CGRectV5frame_So0aB13ConfigurationC13configurationtcfcTO
|
|
0x6b5d5 77 76 __T0So9WKWebViewCABSC6CGRectV5frame_So0aB13ConfigurationC13configurationtcfC
|
|
0x6c3fa 79 78 __T0So9WKWebViewCABSC6CGRectV5frame_So0aB13ConfigurationC13configurationtcfcTO
|
|
```
|
|
|
|
#### Testing JavaScript Configuration
|
|
|
|
For `WKWebView`s, as a best practice, JavaScript should be disabled unless it is explicitly required. To verify that JavaScript was properly disabled search the project for usages of `WKPreferences` and ensure that the [`javaScriptEnabled`](https://developer.apple.com/documentation/webkit/wkpreferences/1536203-javascriptenabled) property is set to `false`:
|
|
|
|
```text
|
|
let webPreferences = WKPreferences()
|
|
webPreferences.javaScriptEnabled = false
|
|
```
|
|
|
|
If only having the compiled binary you can search for this in it:
|
|
|
|
```bash
|
|
$ rabin2 -zz ./WheresMyBrowser | grep -i "javascriptenabled"
|
|
391 0x0002f2c7 0x10002f2c7 17 18 (4.__TEXT.__objc_methname) ascii javaScriptEnabled
|
|
392 0x0002f2d9 0x10002f2d9 21 22 (4.__TEXT.__objc_methname) ascii setJavaScriptEnabled
|
|
```
|
|
|
|
#### Testing OnlySecureContent
|
|
|
|
In contrast to `UIWebView`s, when using `WKWebView`s it is possible to detect [mixed content](https://developers.google.com/web/fundamentals/security/prevent-mixed-content/fixing-mixed-content?hl=en) \(HTTP content loaded from a HTTPS page\). By using the method [`hasOnlySecureContent`](https://developer.apple.com/documentation/webkit/wkwebview/1415002-hasonlysecurecontent) it can be verified whether all resources on the page have been loaded through securely encrypted connections.
|
|
In the compiled binary:
|
|
|
|
```bash
|
|
$ rabin2 -zz ./WheresMyBrowser | grep -i "hasonlysecurecontent"
|
|
```
|
|
|
|
You can also search in the source code or strings the string "http://". However, this doesn't necessary means that there is a mixed content issue. Learn more about mixed content in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content).
|
|
|
|
### Dynamic Analysis
|
|
|
|
It's possible to inspect the heap via `ObjC.choose()` to find instances of the different types of WebViews and also search for the properties `javaScriptEnabled` and `hasonlysecurecontent`:
|
|
|
|
{% code title="webviews\_inspector.js" %}
|
|
```javascript
|
|
ObjC.choose(ObjC.classes['UIWebView'], {
|
|
onMatch: function (ui) {
|
|
console.log('onMatch: ', ui);
|
|
console.log('URL: ', ui.request().toString());
|
|
},
|
|
onComplete: function () {
|
|
console.log('done for UIWebView!');
|
|
}
|
|
});
|
|
|
|
ObjC.choose(ObjC.classes['WKWebView'], {
|
|
onMatch: function (wk) {
|
|
console.log('onMatch: ', wk);
|
|
console.log('URL: ', wk.URL().toString());
|
|
},
|
|
onComplete: function () {
|
|
console.log('done for WKWebView!');
|
|
}
|
|
});
|
|
|
|
ObjC.choose(ObjC.classes['SFSafariViewController'], {
|
|
onMatch: function (sf) {
|
|
console.log('onMatch: ', sf);
|
|
},
|
|
onComplete: function () {
|
|
console.log('done for SFSafariViewController!');
|
|
}
|
|
});
|
|
|
|
ObjC.choose(ObjC.classes['WKWebView'], {
|
|
onMatch: function (wk) {
|
|
console.log('onMatch: ', wk);
|
|
console.log('javaScriptEnabled:', wk.configuration().preferences().javaScriptEnabled());
|
|
}
|
|
});
|
|
|
|
ObjC.choose(ObjC.classes['WKWebView'], {
|
|
onMatch: function (wk) {
|
|
console.log('onMatch: ', wk);
|
|
console.log('hasOnlySecureContent: ', wk.hasOnlySecureContent().toString());
|
|
}
|
|
});
|
|
```
|
|
{% endcode %}
|
|
|
|
Load it with:
|
|
|
|
```bash
|
|
frida -U com.authenticationfailure.WheresMyBrowser -l webviews_inspector.js
|
|
|
|
onMatch: <WKWebView: 0x1508b1200; frame = (0 0; 320 393); layer = <CALayer: 0x1c4238f20>>
|
|
|
|
hasOnlySecureContent: false
|
|
```
|
|
|
|
## WebView Protocol Handling
|
|
|
|
Several default schemes are available that are being interpreted in a WebView on iOS, for example:
|
|
|
|
* http\(s\)://
|
|
* file://
|
|
* tel://
|
|
|
|
WebViews can load remote content from an endpoint, but they can also load local content from the app data directory. If the local content is loaded, the user shouldn't be able to influence the filename or the path used to load the file, and users shouldn't be able to edit the loaded file.
|
|
|
|
### WebView content load
|
|
|
|
* **UIWebView**: It can use deprecated methods [`loadHTMLString:baseURL:`](https://developer.apple.com/documentation/uikit/uiwebview/1617979-loadhtmlstring?language=objc) or [`loadData:MIMEType:textEncodingName:baseURL:`](https://developer.apple.com/documentation/uikit/uiwebview/1617941-loaddata?language=objc)to load content.
|
|
* **WKWebView**: It can use the methods [`loadHTMLString:baseURL:`](https://developer.apple.com/documentation/webkit/wkwebview/1415004-loadhtmlstring?language=objc) or [`loadData:MIMEType:textEncodingName:baseURL:`](https://developer.apple.com/documentation/webkit/wkwebview/1415011-loaddata?language=objc) to load local HTML files and `loadRequest:` for web content. Typically, the local files are loaded in combination with methods including, among others: [`pathForResource:ofType:`](https://developer.apple.com/documentation/foundation/nsbundle/1410989-pathforresource), [`URLForResource:withExtension:`](https://developer.apple.com/documentation/foundation/nsbundle/1411540-urlforresource?language=objc) or [`init(contentsOf:encoding:)`](https://developer.apple.com/documentation/swift/string/3126736-init). In addition, you should also verify if the app is using the method [`loadFileURL:allowingReadAccessToURL:`](https://developer.apple.com/documentation/webkit/wkwebview/1414973-loadfileurl?language=objc). Its first parameter is `URL` and contains the URL to be loaded in the WebView, its second parameter `allowingReadAccessToURL` may contain a single file or a directory. If containing a single file, that file will be available to the WebView. However, if it contains a directory, all files on that **directory will be made available to the WebView**. Therefore, it is worth inspecting this and in case it is a directory, verifying that no sensitive data can be found inside it.
|
|
|
|
If you have the source code you can search for those methods. Having the **compiled** **binary** you can also search for these methods:
|
|
|
|
```bash
|
|
$ rabin2 -zz ./WheresMyBrowser | grep -i "loadHTMLString"
|
|
231 0x0002df6c 24 (4.__TEXT.__objc_methname) ascii loadHTMLString:baseURL:
|
|
```
|
|
|
|
### File Access
|
|
|
|
* **UIWebView:**
|
|
* The `file://` scheme is always enabled.
|
|
* File access from `file://` URLs is always enabled.
|
|
* Universal access from `file://` URLs is always enabled.
|
|
* If you retrieve the effective origin from a `UIWebView` where `baseURL` is also set to `nil` you will see that it is **not set to "null"**, instead you'll obtain something similar to the following: `applewebdata://5361016c-f4a0-4305-816b-65411fc1d78`0. This origin "applewebdata://" is similar to the "file://" origin as it **does not implement Same-Origin Policy** and allow access to local files and any web resources.
|
|
* **WKWebView**:
|
|
* `allowFileAccessFromFileURLs` \(`WKPreferences`, `false` by default\): it enables JavaScript running in the context of a `file://` scheme URL to access content from other `file://` scheme URLs.
|
|
* `allowUniversalAccessFromFileURLs` \(`WKWebViewConfiguration`, `false` by default\): it enables JavaScript running in the context of a `file://` scheme URL to access content from any origin.
|
|
|
|
You can search for those functions in the source code of the application or in the compiled binary.
|
|
Also, you can use the following frida script to find this information:
|
|
|
|
```bash
|
|
ObjC.choose(ObjC.classes['WKWebView'], {
|
|
onMatch: function (wk) {
|
|
console.log('onMatch: ', wk);
|
|
console.log('URL: ', wk.URL().toString());
|
|
console.log('javaScriptEnabled: ', wk.configuration().preferences().javaScriptEnabled());
|
|
console.log('allowFileAccessFromFileURLs: ',
|
|
wk.configuration().preferences().valueForKey_('allowFileAccessFromFileURLs').toString());
|
|
console.log('hasOnlySecureContent: ', wk.hasOnlySecureContent().toString());
|
|
console.log('allowUniversalAccessFromFileURLs: ',
|
|
wk.configuration().valueForKey_('allowUniversalAccessFromFileURLs').toString());
|
|
},
|
|
onComplete: function () {
|
|
console.log('done for WKWebView!');
|
|
}
|
|
});
|
|
```
|
|
|
|
```bash
|
|
frida -U -f com.authenticationfailure.WheresMyBrowser -l webviews_inspector.js
|
|
|
|
onMatch: <WKWebView: 0x1508b1200; frame = (0 0; 320 393); layer = <CALayer: 0x1c4238f20>>
|
|
URL: file:///var/mobile/Containers/Data/Application/A654D169-1DB7-429C-9DB9-A871389A8BAA/
|
|
Library/WKWebView/scenario1.html
|
|
javaScriptEnabled: true
|
|
allowFileAccessFromFileURLs: 0
|
|
hasOnlySecureContent: false
|
|
allowUniversalAccessFromFileURLs: 0
|
|
```
|
|
|
|
## Native Methods Exposed Through WebViews
|
|
|
|
Since iOS 7, Apple introduced APIs that allow **communication between the JavaScript runtime in the WebView and the native** Swift or Objective-C objects.
|
|
|
|
There are two fundamental ways of how native code and JavaScript can communicate:
|
|
|
|
* **JSContext**: When an Objective-C or Swift block is assigned to an identifier in a `JSContext`, JavaScriptCore automatically wraps the block in a JavaScript function.
|
|
* **JSExport protocol**: Properties, instance methods and class methods declared in a `JSExport`-inherited protocol are mapped to JavaScript objects that are available to all JavaScript code. Modifications of objects that are in the JavaScript environment are reflected in the native environment.
|
|
|
|
Note that **only class members defined in the `JSExport`** protocol are made accessible to JavaScript code.
|
|
Look out for code that maps native objects to the `JSContext` associated with a WebView and analyze what functionality it exposes, for example no sensitive data should be accessible and exposed to WebViews.
|
|
In Objective-C, the `JSContext` associated with a `UIWebView` is obtained as follows:
|
|
|
|
```objectivec
|
|
[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]
|
|
```
|
|
|
|
JavaScript code in a **`WKWebView` can still send messages back to the native app but in contrast to `UIWebView`, it is not possible to directly reference the `JSContext`** of a `WKWebView`. Instead, communication is implemented using a messaging system and using the `postMessage` function, which automatically serializes JavaScript objects into native Objective-C or Swift objects. Message handlers are configured using the method [`add(_ scriptMessageHandler:name:)`](https://developer.apple.com/documentation/webkit/wkusercontentcontroller/1537172-add).
|
|
|
|
### Enabling JavascriptBridge
|
|
|
|
```swift
|
|
func enableJavaScriptBridge(_ enabled: Bool) {
|
|
options_dict["javaScriptBridge"]?.value = enabled
|
|
let userContentController = wkWebViewConfiguration.userContentController
|
|
userContentController.removeScriptMessageHandler(forName: "javaScriptBridge")
|
|
|
|
if enabled {
|
|
let javaScriptBridgeMessageHandler = JavaScriptBridgeMessageHandler()
|
|
userContentController.add(javaScriptBridgeMessageHandler, name: "javaScriptBridge")
|
|
}
|
|
}
|
|
```
|
|
|
|
### Sending Message
|
|
|
|
Adding a script message handler with name `"name"` \(or `"javaScriptBridge"` in the example above\) causes the JavaScript function `window.webkit.messageHandlers.myJavaScriptMessageHandler.postMessage` to be defined in all frames in all web views that use the user content controller. It can be then [used from the HTML file like this](https://github.com/authenticationfailure/WheresMyBrowser.iOS/blob/d4e2d9efbde8841bf7e4a8800418dda6bb116ec6/WheresMyBrowser/web/WKWebView/scenario3.html#L33):
|
|
|
|
```javascript
|
|
function invokeNativeOperation() {
|
|
value1 = document.getElementById("value1").value
|
|
value2 = document.getElementById("value2").value
|
|
window.webkit.messageHandlers.javaScriptBridge.postMessage(["multiplyNumbers", value1, value2]);
|
|
}
|
|
```
|
|
|
|
### Called Function
|
|
|
|
The called function resides in [`JavaScriptBridgeMessageHandler.swift`](https://github.com/authenticationfailure/WheresMyBrowser.iOS/blob/b8d4abda4000aa509c7a5de79e5c90360d1d0849/WheresMyBrowser/JavaScriptBridgeMessageHandler.swift#L29):
|
|
|
|
```swift
|
|
class JavaScriptBridgeMessageHandler: NSObject, WKScriptMessageHandler {
|
|
|
|
//...
|
|
|
|
case "multiplyNumbers":
|
|
|
|
let arg1 = Double(messageArray[1])!
|
|
let arg2 = Double(messageArray[2])!
|
|
result = String(arg1 * arg2)
|
|
//...
|
|
|
|
let javaScriptCallBack = "javascriptBridgeCallBack('\(functionFromJS)','\(result)')"
|
|
message.webView?.evaluateJavaScript(javaScriptCallBack, completionHandler: nil)
|
|
```
|
|
|
|
### Testing
|
|
|
|
In order to test send a postMessage inside an application you can:
|
|
|
|
* Change the servers response \(MitM\)
|
|
* Perform a dynamic instrumentation and inject the JavaScript payload by using frameworks like Frida and the corresponding JavaScript evaluation functions available for the iOS WebViews \([`stringByEvaluatingJavaScriptFromString:`](https://developer.apple.com/documentation/uikit/uiwebview/1617963-stringbyevaluatingjavascriptfrom?language=objc) for `UIWebView` and [`evaluateJavaScript:completionHandler:`](https://developer.apple.com/documentation/webkit/wkwebview/1415017-evaluatejavascript?language=objc) for `WKWebView`\).
|
|
|
|
## References
|
|
|
|
* [https://mobile-security.gitbook.io/mobile-security-testing-guide/ios-testing-guide/0x06h-testing-platform-interaction\#testing-webview-protocol-handlers-mstg-platform-6](https://mobile-security.gitbook.io/mobile-security-testing-guide/ios-testing-guide/0x06h-testing-platform-interaction#testing-webview-protocol-handlers-mstg-platform-6)
|
|
|