ios-protocol-handlers.md (9736B)
1 --- 2 title: "WebView Protocol Handlers" 3 section: "Mobile" 4 sectionSlug: "mobile-pentesting" 5 sourcePath: "src/mobile-pentesting/ios-pentesting/ios-protocol-handlers.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/mobile-pentesting/ios-pentesting/ios-protocol-handlers.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # WebView Protocol Handlers 14 15 ## Basic Information 16 17 In this page, **protocol handlers** are the URL schemes or URL-like handoffs that make iOS leave the current web context or resolve content through a non-standard path. During a pentest, treat every transition from **web content** to **`UIApplication.open`**, **`canOpenURL`**, or a **`WKURLSchemeHandler`** as a trust boundary.<sup>[[1]](#references)</sup> 18 19 This page focuses on **WebView / browser-driven scheme abuse**. For app registration, deeplink hijacking, and callback stealing, see [iOS Custom URI Handlers / Deeplinks / Custom Schemes](/hacktricks/mobile-pentesting/ios-pentesting/ios-custom-uri-handlers-deeplinks-custom-schemes). For the file-origin / `loadFileURL:allowingReadAccessTo:` angle, see [iOS WebViews](/hacktricks/mobile-pentesting/ios-pentesting/ios-webviews). For claimed `https` handlers, see [iOS Universal Links](/hacktricks/mobile-pentesting/ios-pentesting/ios-universal-links). 20 21 Common protocol-handler surfaces: 22 23 - System schemes such as `tel:`, `sms:`, `mailto:`, and `facetime:`. 24 - App schemes such as `myapp://`, browser-internal schemes, and `x-callback-url` style callbacks. 25 - Custom resource schemes served from native code via `WKURLSchemeHandler` (for example `app://` or `resources://` inside `WKWebView`). 26 27 The key question is always: **can attacker-controlled content make the app open, resolve, or bounce to a URL whose scheme/host/path was not supposed to be reachable?** 28 29 ## High-value bug patterns 30 31 ### 1. Web content controls the next navigation 32 33 If a `WKWebView` renders attacker-controlled HTML or attacker-controlled data is injected into the DOM, you may get a **scheme pivot** without touching native code directly. Modern payloads do not need `<script>` tags; `meta refresh`, `onerror`, and `onload` handlers are often enough to force navigation. 34 35 ```html 36 <meta http-equiv="refresh" content="0; url=myapp://debug?action=test"> 37 <img src=x onerror="window.location='myapp://debug?action=test'"> 38 <svg onload="window.location='myapp://debug?action=test'"></svg> 39 ``` 40 41 This is especially interesting when the target WebView later forwards the navigation to `UIApplication.shared.open`, when the page is local/trusted, or when the navigation reaches a browser-internal scheme. 42 43 ### 2. `canOpenURL` used as if it were validation 44 45 A recurring anti-pattern is: 46 47 ```swift 48 if UIApplication.shared.canOpenURL(url) { 49 UIApplication.shared.open(url) 50 } 51 ``` 52 53 `canOpenURL` only answers **whether some app can handle the scheme**. It does **not** prove that the URL is expected, safe, or owned by the right app. If the attacker controls the URL, this code still turns untrusted web input into an external-app launch. 54 55 ### 3. `WKNavigationDelegate` or JS bridges open arbitrary URLs 56 57 Look for: 58 59 - `webView(_:decidePolicyFor:decisionHandler:)` 60 - `webView(_:createWebViewWith:for:windowFeatures:)` 61 - `WKScriptMessageHandler` methods receiving `url`, `target`, `redirect`, `openExternal`, `browser`, `share`, or `download` 62 - Helper methods that parse a web message and immediately call `UIApplication.shared.open` 63 64 A minimal dangerous pattern is: 65 66 ```swift 67 func webView(_ webView: WKWebView, decidePolicyFor action: WKNavigationAction, 68 decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { 69 guard let url = action.request.url else { return decisionHandler(.cancel) } 70 UIApplication.shared.open(url) 71 decisionHandler(.cancel) 72 } 73 ``` 74 75 Safer logic should parse the URL with `URLComponents`, allow only exact schemes/hosts/paths, and explicitly deny `javascript:`, `data:`, `file:`, browser-internal schemes, and unknown custom schemes unless they are a business requirement. 76 77 ### 4. Nested callback parameters re-open blocked schemes 78 79 Do not stop testing after a direct `myapp://` or `fido:/` launch is blocked. Recent research showed that **nested callbacks** such as `x-success`, `x-error`, and `x-cancel` can re-open a blocked scheme through an intermediate app. In practice, handler **A** may reject `fido:/` directly but still open `shortcuts://...&x-error=fido:/...` and let handler **B** perform the final launch.<sup>[[2]](#references)</sup> 80 81 This is why pure **blocklists** are weak. Try handler chaining, double-encoding, and browser/helper-app schemes that accept `url=`, `x-success=`, `x-error=`, or `redirect=` parameters. Recent iOS browser fixes are a good reminder that internal non-HTTP schemes reachable from web content or from another app can bypass safety checks or spoof what the user sees.<sup>[[2]](#references)</sup> 82 83 Recent technique-focused lessons from 2024-2025 research and advisories:<sup>[[2]](#references)</sup> 84 85 - Web content reaching a browser's **own internal deeplink scheme** can bypass safety checks that were only designed for normal `http(s)` navigation. 86 - Redirecting from a trusted-looking `https` page to a **non-HTTP/internal scheme** can desynchronize what the user sees from what is actually opened. 87 - Blocking a dangerous scheme directly is not enough if an intermediate handler can reopen it through **callback parameters** such as `x-error` or `x-cancel`. 88 89 ### 5. `WKURLSchemeHandler` turns native code into a private web server 90 91 If the app registers a custom resource scheme with `setURLSchemeHandler(_:forURLScheme:)`, every request for that scheme is served by native code. Treat it as a local attack surface: 92 93 - Path traversal / `%2e%2e/` into bundle or sandbox files 94 - Arbitrary network fetchers like `app://proxy?url=https://evil` 95 - Secret/config exposure under predictable paths such as `app://config` 96 - Remote pages referencing the internal scheme to reach privileged resources 97 - Origin assumptions that break once remote and local pages can both request the same custom scheme 98 99 When you see `WKURLSchemeHandler`, review the `start` / `stop` handler implementation with the same mindset you would use for an embedded HTTP server. 100 101 ## Static triage 102 103 If you have source code: 104 105 ```bash 106 rg -n 'UIApplication\.shared\.open|canOpenURL|setURLSchemeHandler|WKURLSchemeHandler|decidePolicyFor|createWebViewWith|WKScriptMessageHandler|x-success|x-error|x-cancel|redirect|openExternal' . 107 ``` 108 109 If you only have the IPA / app bundle: 110 111 ```bash 112 plutil -p Payload/App.app/Info.plist | rg 'CFBundleURLTypes|LSApplicationQueriesSchemes' 113 rabin2 -zzq Payload/App.app/AppBinary | \ 114 rg 'openURL|canOpenURL|decidePolicyForNavigationAction|createWebViewWith|WKURLSchemeHandler|setURLSchemeHandler|loadFileURL:allowingReadAccessToURL:|loadHTMLString:baseURL:|x-success|x-error|x-cancel|shortcuts://|firefox://|focus://' 115 ``` 116 117 Prioritize code paths where: 118 119 - A URL arrives from a WebView navigation, DOM message, query parameter, QR payload, push payload, or remote config. 120 - The code checks only a prefix like `hasPrefix("https")` or `contains("trusted.com")`. 121 - `canOpenURL` is immediately followed by `open`. 122 - A local HTML page or template can be influenced by user-controlled data. 123 - `WKURLSchemeHandler` maps request paths directly to files or backend fetches. 124 125 ## Dynamic analysis 126 127 Useful first passes: 128 129 ```bash 130 # Replay custom-scheme URLs on the simulator 131 xcrun simctl openurl booted 'myapp://debug?action=test' 132 133 # Trace common sinks 134 frida-trace -U 'TargetApp' \ 135 -m '*[UIApplication canOpenURL:*]' \ 136 -m '*[UIApplication openURL:*]' \ 137 -m '*[WKWebView *loadFileURL*]' \ 138 -m '*[WKWebView *loadHTMLString*]' 139 ``` 140 141 Minimal Frida hooks are often enough to identify which schemes really escape the WebView: 142 143 ```javascript 144 Interceptor.attach(ObjC.classes.UIApplication["- canOpenURL:"].implementation, { 145 onEnter(args) { console.log("[canOpenURL] " + new ObjC.Object(args[2]).absoluteString()); } 146 }); 147 Interceptor.attach(ObjC.classes.UIApplication["- openURL:options:completionHandler:"].implementation, { 148 onEnter(args) { console.log("[open] " + new ObjC.Object(args[2]).absoluteString()); } 149 }); 150 ``` 151 152 Good payload families: 153 154 - Direct launches: `tel:`, `sms:`, `mailto:`, `facetime:`, `myapp://...` 155 - Browser/helper-app chains: `shortcuts://x-callback-url/...&x-error=myapp://...` 156 - Nested redirects: `https://trusted.example/redirect?next=myapp://...` 157 - HTML-injection navigations: `meta refresh`, `<img onerror>`, `<svg onload>` 158 - Encoding tricks: mixed-case schemes, `%0a`, `%09`, double-encoded `%252f`, duplicated keys 159 160 If the app exposes a `WKURLSchemeHandler`, try requesting it from attacker-controlled HTML and watch for filesystem or network side effects. 161 162 ## What "good" looks like 163 164 A hardened implementation usually has these properties: 165 166 - Top-level WebView navigations are limited to a very small allowlist, ideally exact `https` origins. 167 - External launches are explicit exceptions (`tel`, `sms`, `mailto`, `facetime`, etc.), not the default path. 168 - `canOpenURL` is used only as availability logic, not as a security decision. 169 - Custom schemes are never used as bearer-token transports. 170 - `WKURLSchemeHandler` paths are canonicalized and strictly mapped to known resources. 171 - Unknown schemes, browser-internal schemes, and callback parameters are rejected by default. 172 173 ## References 174 175 - [1] [MASTG-TEST-0077: Testing WebView Protocol Handlers](https://mas.owasp.org/MASTG/tests/ios/MASVS-PLATFORM/MASTG-TEST-0077/) 176 - [2] [Bypass for CVE-2024-9956 in Safari on iOS](https://denniskniep.github.io/posts/13-bypass-cve-2024-9956/)