Skip to content

Commit 929d99e

Browse files
author
HackTricks News Bot
committed
Add content from: Research Update Enhanced src/mobile-pentesting/ios-pentestin...
1 parent 9705180 commit 929d99e

1 file changed

Lines changed: 159 additions & 0 deletions

File tree

src/mobile-pentesting/ios-pentesting/ios-protocol-handlers.md

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,166 @@
22

33
{{#include ../../banners/hacktricks-training.md}}
44

5+
## Basic Information
56

7+
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.
68

9+
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](ios-custom-uri-handlers-deeplinks-custom-schemes.md). For the file-origin / `loadFileURL:allowingReadAccessTo:` angle, see [iOS WebViews](ios-webviews.md). For claimed `https` handlers, see [iOS Universal Links](ios-universal-links.md).
710

11+
Common protocol-handler surfaces:
812

13+
- System schemes such as `tel:`, `sms:`, `mailto:`, and `facetime:`.
14+
- App schemes such as `myapp://`, browser-internal schemes, and `x-callback-url` style callbacks.
15+
- Custom resource schemes served from native code via `WKURLSchemeHandler` (for example `app://` or `resources://` inside `WKWebView`).
16+
17+
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?**
18+
19+
## High-value bug patterns
20+
21+
### 1. Web content controls the next navigation
22+
23+
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.
24+
25+
```html
26+
<meta http-equiv="refresh" content="0; url=myapp://debug?action=test">
27+
<img src=x onerror="window.location='myapp://debug?action=test'">
28+
<svg onload="window.location='myapp://debug?action=test'"></svg>
29+
```
30+
31+
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.
32+
33+
### 2. `canOpenURL` used as if it were validation
34+
35+
A recurring anti-pattern is:
36+
37+
```swift
38+
if UIApplication.shared.canOpenURL(url) {
39+
UIApplication.shared.open(url)
40+
}
41+
```
42+
43+
`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.
44+
45+
### 3. `WKNavigationDelegate` or JS bridges open arbitrary URLs
46+
47+
Look for:
48+
49+
- `webView(_:decidePolicyFor:decisionHandler:)`
50+
- `webView(_:createWebViewWith:for:windowFeatures:)`
51+
- `WKScriptMessageHandler` methods receiving `url`, `target`, `redirect`, `openExternal`, `browser`, `share`, or `download`
52+
- Helper methods that parse a web message and immediately call `UIApplication.shared.open`
53+
54+
A minimal dangerous pattern is:
55+
56+
```swift
57+
func webView(_ webView: WKWebView, decidePolicyFor action: WKNavigationAction,
58+
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
59+
guard let url = action.request.url else { return decisionHandler(.cancel) }
60+
UIApplication.shared.open(url)
61+
decisionHandler(.cancel)
62+
}
63+
```
64+
65+
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.
66+
67+
### 4. Nested callback parameters re-open blocked schemes
68+
69+
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.
70+
71+
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.
72+
73+
Recent technique-focused lessons from 2024-2025 research and advisories:
74+
75+
- Web content reaching a browser's **own internal deeplink scheme** can bypass safety checks that were only designed for normal `http(s)` navigation.
76+
- Redirecting from a trusted-looking `https` page to a **non-HTTP/internal scheme** can desynchronize what the user sees from what is actually opened.
77+
- 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`.
78+
79+
### 5. `WKURLSchemeHandler` turns native code into a private web server
80+
81+
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:
82+
83+
- Path traversal / `%2e%2e/` into bundle or sandbox files
84+
- Arbitrary network fetchers like `app://proxy?url=https://evil`
85+
- Secret/config exposure under predictable paths such as `app://config`
86+
- Remote pages referencing the internal scheme to reach privileged resources
87+
- Origin assumptions that break once remote and local pages can both request the same custom scheme
88+
89+
When you see `WKURLSchemeHandler`, review the `start` / `stop` handler implementation with the same mindset you would use for an embedded HTTP server.
90+
91+
## Static triage
92+
93+
If you have source code:
94+
95+
```bash
96+
rg -n 'UIApplication\.shared\.open|canOpenURL|setURLSchemeHandler|WKURLSchemeHandler|decidePolicyFor|createWebViewWith|WKScriptMessageHandler|x-success|x-error|x-cancel|redirect|openExternal' .
97+
```
98+
99+
If you only have the IPA / app bundle:
100+
101+
```bash
102+
plutil -p Payload/App.app/Info.plist | rg 'CFBundleURLTypes|LSApplicationQueriesSchemes'
103+
rabin2 -zzq Payload/App.app/AppBinary | \
104+
rg 'openURL|canOpenURL|decidePolicyForNavigationAction|createWebViewWith|WKURLSchemeHandler|setURLSchemeHandler|loadFileURL:allowingReadAccessToURL:|loadHTMLString:baseURL:|x-success|x-error|x-cancel|shortcuts://|firefox://|focus://'
105+
```
106+
107+
Prioritize code paths where:
108+
109+
- A URL arrives from a WebView navigation, DOM message, query parameter, QR payload, push payload, or remote config.
110+
- The code checks only a prefix like `hasPrefix("https")` or `contains("trusted.com")`.
111+
- `canOpenURL` is immediately followed by `open`.
112+
- A local HTML page or template can be influenced by user-controlled data.
113+
- `WKURLSchemeHandler` maps request paths directly to files or backend fetches.
114+
115+
## Dynamic analysis
116+
117+
Useful first passes:
118+
119+
```bash
120+
# Replay custom-scheme URLs on the simulator
121+
xcrun simctl openurl booted 'myapp://debug?action=test'
122+
123+
# Trace common sinks
124+
frida-trace -U 'TargetApp' \
125+
-m '*[UIApplication canOpenURL:*]' \
126+
-m '*[UIApplication openURL:*]' \
127+
-m '*[WKWebView *loadFileURL*]' \
128+
-m '*[WKWebView *loadHTMLString*]'
129+
```
130+
131+
Minimal Frida hooks are often enough to identify which schemes really escape the WebView:
132+
133+
```javascript
134+
Interceptor.attach(ObjC.classes.UIApplication["- canOpenURL:"].implementation, {
135+
onEnter(args) { console.log("[canOpenURL] " + new ObjC.Object(args[2]).absoluteString()); }
136+
});
137+
Interceptor.attach(ObjC.classes.UIApplication["- openURL:options:completionHandler:"].implementation, {
138+
onEnter(args) { console.log("[open] " + new ObjC.Object(args[2]).absoluteString()); }
139+
});
140+
```
141+
142+
Good payload families:
143+
144+
- Direct launches: `tel:`, `sms:`, `mailto:`, `facetime:`, `myapp://...`
145+
- Browser/helper-app chains: `shortcuts://x-callback-url/...&x-error=myapp://...`
146+
- Nested redirects: `https://trusted.example/redirect?next=myapp://...`
147+
- HTML-injection navigations: `meta refresh`, `<img onerror>`, `<svg onload>`
148+
- Encoding tricks: mixed-case schemes, `%0a`, `%09`, double-encoded `%252f`, duplicated keys
149+
150+
If the app exposes a `WKURLSchemeHandler`, try requesting it from attacker-controlled HTML and watch for filesystem or network side effects.
151+
152+
## What "good" looks like
153+
154+
A hardened implementation usually has these properties:
155+
156+
- Top-level WebView navigations are limited to a very small allowlist, ideally exact `https` origins.
157+
- External launches are explicit exceptions (`tel`, `sms`, `mailto`, `facetime`, etc.), not the default path.
158+
- `canOpenURL` is used only as availability logic, not as a security decision.
159+
- Custom schemes are never used as bearer-token transports.
160+
- `WKURLSchemeHandler` paths are canonicalized and strictly mapped to known resources.
161+
- Unknown schemes, browser-internal schemes, and callback parameters are rejected by default.
162+
163+
## References
164+
165+
- [https://mas.owasp.org/MASTG/tests/ios/MASVS-PLATFORM/MASTG-TEST-0077/](https://mas.owasp.org/MASTG/tests/ios/MASVS-PLATFORM/MASTG-TEST-0077/)
166+
- [https://denniskniep.github.io/posts/13-bypass-cve-2024-9956/](https://denniskniep.github.io/posts/13-bypass-cve-2024-9956/)
167+
{{#include ../../banners/hacktricks-training.md}}

0 commit comments

Comments
 (0)