daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

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