You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A lot of current iOS targets are not pure UIKit anymore, so custom-scheme handling may be split across native and JS/router layers:
74
74
75
75
-**SwiftUI** apps can receive deeplinks inside `.onOpenURL { url in ... }` attached to a `WindowGroup` or another `Scene`. Review every scene, not only `AppDelegate`, because a secondary scene may parse URLs differently.
76
-
-**React Native / Expo** apps often forward the URL through `RCTLinkingManager`, `RCTOpenURLNotification`, `Linking.getInitialURL()`, and runtime `url` events. In Expo-managed apps, if the developer does not define an explicit custom scheme, the generated iOS bundle identifier commonly becomes a reachable default scheme.
76
+
-**React Native / Expo** apps often forward the URL through `RCTLinkingManager`, `RCTOpenURLNotification`, `Linking.getInitialURL()`, and runtime `url` events. In Expo-managed apps, if the developer does not define an explicit custom scheme, the generated iOS bundle identifier commonly becomes a reachable default scheme. Development builds may also expose `exp://` or an extra Expo dev-client-generated scheme, so test builds often have more reachable handlers than production.
77
77
-**Capacitor** apps commonly expose deeplinks through `App.getLaunchUrl()` and `App.addListener('appUrlOpen', ...)`.
78
78
79
79
From a pentest perspective, the interesting question is whether the framework router later turns attacker-controlled path/query data into navigation, auth state changes, feature flags, or WebView destinations. If the same URL is later reused in a browser/WebView sink, continue with [iOS protocol handlers](ios-protocol-handlers.md).
@@ -105,6 +108,25 @@ Interesting findings during static analysis:
105
108
-`x-success`, `x-error`, or `x-cancel` callback parameters accepted from untrusted sources and then reopened without strict validation.
106
109
- Framework-generated or framework-forwarded handlers (for example bundle-ID schemes, JS bridge events, or router adapters) that developers may not realize are still part of the external attack surface.
107
110
111
+
### Source-application validation
112
+
113
+
Custom schemes are an **inter-app IPC** boundary, so check whether the handler makes security decisions from the routing context (`sourceApplication`, scene options, callback state, or whether the app was cold-started) instead of from server-side proof or user re-authentication. In modern scene-based apps, the same information is reachable via `UIOpenURLContext`:
// test whether privileged routes still work when sourceApp is nil or unexpected
121
+
}
122
+
```
123
+
124
+
From a pentest perspective, interesting bugs are:
125
+
126
+
- Sensitive routes that execute even when `sourceApplication` is missing, unexpected, or obviously attacker-controlled.
127
+
- Code paths that trust the incoming app identity more than the **contents** of the URL (for example, they skip auth checks for password-reset, wallet, or account-linking routes).
128
+
- Fallback logic where the app logs an untrusted origin but still continues routing the deeplink.
129
+
108
130
### Testing URL Requests to Other Apps
109
131
110
132
Methods like `openURL:options:completionHandler:` are crucial for opening URLs to interact with other apps. Identifying usage of such methods in the app's source code is key for understanding external communications.
@@ -134,6 +156,22 @@ When instrumenting the target, hook both inbound handlers and outbound launches:
134
156
-`+[RCTLinkingManager application:openURL:options:]` in React Native builds
135
157
-`openURL:options:completionHandler:` to see which third-party apps or callbacks the target invokes
136
158
159
+
For React Native / Expo targets, notification-level hooks are also useful because the JS router may consume the URL after the native handler returns:
if (name.indexOf('RCTOpenURLNotification') !==-1) console.log('[NSNotification] '+ name);
171
+
}
172
+
});
173
+
```
174
+
137
175
If the app is hybrid, compare **cold start** and **warm start** behaviour separately: `getInitialURL()` / `getLaunchUrl()` often parses the launch URL through a different code path than runtime URL events, and bugs sometimes appear in only one of them.
138
176
139
177
### Fuzzing URL Schemes
@@ -184,6 +222,12 @@ The attack flow is:
184
222
4. If the victim is already authenticated, the authorization server redirects with an authorization code (or another secret) to the victim's custom scheme.
185
223
5. Because the attacker's app also registered that scheme and owns the active `ASWebAuthenticationSession`, the attacker receives the callback and can exchange the code.
186
224
225
+
Practical triage shortcuts for this family of bugs:
226
+
227
+
- Search for redirect URIs such as `com.example.app:/oauth2redirect/...`, `myapp://oauth/callback`, or `bundle.id://callback` and verify whether the app validates only the scheme or also enforces the expected host/path.
228
+
- Grep for `state`, `nonce`, `code_verifier`, `callbackURLScheme`, `redirect_uri`, `redirectSystemPath`, `magic`, and `invite` to find non-obvious flows that reuse the same custom scheme transport.
229
+
- Try the same interception idea against password-reset, email-verification, invite, and magic-link flows; these are often easier to exploit than full OAuth because the app treats the deeplink itself as sufficient proof.
230
+
187
231
This is important because **PKCE alone does not save the flow** when the attacker originates the entire OAuth request and chooses its own `code_challenge` / `code_verifier`. RFC 8252 still allows private-use URI schemes for native apps, but explicitly calls out that multiple apps can register the same scheme and recommends app-claimed `https` redirects where the platform supports them. In practice, that means custom schemes are still common, but they should be treated as an attacker-reachable IPC boundary and not as proof of app identity. See also [this other page about Universal Links](ios-universal-links.md).
188
232
189
233
## References
@@ -193,6 +237,8 @@ This is important because **PKCE alone does not save the flow** when the attacke
0 commit comments