ios-universal-links.md (12680B)
1 --- 2 title: "iOS Universal Links" 3 section: "Mobile" 4 sectionSlug: "mobile-pentesting" 5 sourcePath: "src/mobile-pentesting/ios-pentesting/ios-universal-links.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/mobile-pentesting/ios-pentesting/ios-universal-links.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # iOS Universal Links 14 15 ## Introduction 16 17 Universal links offer a **seamless redirection** experience to users by directly opening content in the app, bypassing the need for Safari redirection. These links are **unique** and secure, as they cannot be claimed by other apps. This is ensured by hosting a `apple-app-site-association` JSON file on the website's root directory, establishing a verifiable link between the website and the app. In cases where the app is not installed, Safari will take over and direct the user to the webpage, maintaining the app's presence. 18 19 For penetration testers, the `apple-app-site-association` file is of particular interest as it may reveal **sensitive paths**, potentially including ones related to unreleased features. 20 21 ### **Analyzing the Associated Domains Entitlement** 22 23 Developers enable Universal Links by configuring the **Associated Domains** in Xcode's Capabilities tab or by inspecting the `.entitlements` file. Each domain is prefixed with `applinks:`. For example, Telegram's configuration might appear as follows: 24 25 ```xml 26 <key>com.apple.developer.associated-domains</key> 27 <array> 28 <string>applinks:telegram.me</string> 29 <string>applinks:t.me</string> 30 </array> 31 ``` 32 33 For more comprehensive insights, refer to the [archived Apple Developer Documentation](https://developer.apple.com/library/archive/documentation/General/Conceptual/AppSearch/UniversalLinks.html#//apple_ref/doc/uid/TP40016308-CH12-SW2). 34 35 If working with a compiled application, entitlements can be extracted as outlined in [this guide](/hacktricks/mobile-pentesting/ios-pentesting/extracting-entitlements-from-compiled-application). 36 37 ### **Retrieving the Apple App Site Association File** 38 39 The `apple-app-site-association` file should be retrieved from the server using the domains specified in the entitlements.<sup>[[2]](#references)</sup> Ensure the file is accessible via HTTPS directly at `https://<domain>/apple-app-site-association` (or `/.well-known/apple-app-site-association`). Tools like the [Apple App Site Association (AASA) Validator](https://branch.io/resources/aasa-validator/) can aid in this process. 40 41 > **Quick enumeration from a macOS/Linux shell** 42 > 43 > ```bash 44 > # assuming you have extracted the entitlements to ent.xml 45 > doms=$(plutil -extract com.apple.developer.associated-domains xml1 -o - ent.xml | \ 46 > grep -oE 'applinks:[^<]+' | cut -d':' -f2) 47 > for d in $doms; do 48 > echo "[+] Fetching AASA for $d"; 49 > curl -sk "https://$d/.well-known/apple-app-site-association" | jq '.' 50 > done 51 > ``` 52 53 ### **AASA Triage on Modern iOS** 54 55 Since **iOS 14**, associated-domain metadata is commonly delivered to devices through **Apple's CDN**. Therefore, when links unexpectedly open in Safari, don't stop after checking the origin file on the target domain: also check the cached CDN copy and whether the device marked the association as usable.<sup>[[1]](#references)</sup> 56 57 ```bash 58 for d in $doms; do 59 echo "=== $d ===" 60 for u in \ 61 "https://$d/.well-known/apple-app-site-association" \ 62 "https://$d/apple-app-site-association" \ 63 "https://app-site-association.cdn-apple.com/a/v1/$d" 64 do 65 echo "[*] $u" 66 curl -skI "$u" | sed -n '1p;/content-type/ip;/location/ip' 67 done 68 done 69 70 # On a connected Mac or device shell, verify the actual on-device association state 71 swcutil dl 72 ``` 73 74 Useful checks during triage: 75 76 - The origin must serve the file over **HTTPS**, with **`application/json`**, and **without redirects**. 77 - The CDN response should expose the same **`appIDs`** / **`components`** (or legacy **`paths`**) that you saw on the origin. 78 - `swcutil dl` should show the `applinks` association as effectively verified; if not, iOS will keep falling back to Safari even if the JSON itself looks correct. 79 80 ### **Handling Universal Links in the App** 81 82 The app must implement specific methods to handle universal links correctly. The primary method to look for is [`application:continueUserActivity:restorationHandler:`](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623072-application). It's crucial that the scheme of URLs handled is HTTP or HTTPS, as others will not be supported. 83 84 In modern targets, don't stop at `UIApplicationDelegate`. Universal links may also be routed through **scene-based** or **SwiftUI** entry points such as `scene(_:continue:)`, `scene(_:willConnectTo:options:)`, `.onOpenURL`, or `.onContinueUserActivity(...)`. Review every lifecycle entry point because some apps validate URLs in one path and blindly route them in another. 85 86 ```swift 87 func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { 88 guard userActivity.activityType == NSUserActivityTypeBrowsingWeb, 89 let url = userActivity.webpageURL else { return } 90 route(url) 91 } 92 ``` 93 94 #### **Validating the Data Handler Method** 95 96 When a universal link opens an app, an `NSUserActivity` object is passed to the app with the URL. Before processing this URL, it's essential to validate and sanitize it to prevent security risks. Here's an example in Swift that demonstrates the process: 97 98 ```swift 99 func application(_ application: UIApplication, continue userActivity: NSUserActivity, 100 restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { 101 // Check for web browsing activity and valid URL 102 if userActivity.activityType == NSUserActivityTypeBrowsingWeb, let url = userActivity.webpageURL { 103 application.open(url, options: [:], completionHandler: nil) 104 } 105 106 return true 107 } 108 ``` 109 110 URLs should be carefully parsed and validated, especially if they include parameters, to guard against potential spoofing or malformed data. The `NSURLComponents` API is useful for this purpose, as demonstrated below: 111 112 ```swift 113 func application(_ application: UIApplication, 114 continue userActivity: NSUserActivity, 115 restorationHandler: @escaping ([Any]?) -> Void) -> Bool { 116 guard userActivity.activityType == NSUserActivityTypeBrowsingWeb, 117 let incomingURL = userActivity.webpageURL, 118 let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true), 119 let path = components.path, 120 let params = components.queryItems else { 121 return false 122 } 123 124 if let albumName = params.first(where: { $0.name == "albumname" })?.value, 125 let photoIndex = params.first(where: { $0.name == "index" })?.value { 126 // Process the URL with album name and photo index 127 128 return true 129 130 } else { 131 // Handle invalid or missing parameters 132 133 return false 134 } 135 } 136 ``` 137 138 Through **diligent configuration and validation**, developers can ensure that universal links enhance user experience while maintaining security and privacy standards. 139 140 ### **Dynamic Testing & Runtime Tracing** 141 142 Universal links are easy to **mis-test**: 143 144 - Typing the URL directly into Safari's address bar usually **won't** exercise the app handoff. 145 - If the app opens its own `https://` URL with `openURL:options:completionHandler:`, the request does **not** re-enter the universal-link receiver as if the user had tapped it externally. 146 147 A practical workflow is to paste the URL into **Notes** or **Messages**, long-press it, and confirm whether iOS offers to open the app. While triggering the link, trace both the receiver and the next routing layer:<sup>[[3]](#references)</sup> 148 149 ```bash 150 frida-trace -U "TargetApp" -m "*[* *continueUserActivity*]" -i "*open*Url*" 151 ``` 152 153 This is especially useful in closed-source targets: inspect the delivered `webpageURL`, verify the `activityType` is `NSUserActivityTypeBrowsingWeb`, and check whether the handler later forwards the URL into a WebView, browser helper, or another internal router. 154 155 ## Common Vulnerabilities & Pentesting Checks 156 157 | # | Weakness | How to test | Exploitation / Impact | 158 |---|----------|------------|-----------------------| 159 | 1 | **Over-broad `paths` / `components`** in the AASA file (e.g. `"/": "*"` or wildcards such as `"/a/*"`). | • Inspect the downloaded AASA and look for `*`, trailing slashes, or `{"?": …}` rules.<br>• Try to request unknown resources that still match the rule (`https://domain.com/a/evil?_p_dp=1`). | Universal-link hijacking: a malicious iOS app that registers the same domain could claim all those links and present phishing UI. A real-world example is the May 2025 Temu.com bug-bounty report where an attacker could redirect any `/a/*` path to their own app.<sup>[[4]](#references)</sup> | 160 | 2 | **Missing server-side validation** of deep-link paths. | After identifying the allowed paths, issue `curl`/Burp requests to non-existing resources and observe HTTP status codes. Anything other than `404` (e.g. 200/302) is suspicious. | An attacker can host arbitrary content behind an allowed path and serve it via the legitimate domain, increasing the success rate of phishing or session-token theft. | 161 | 3 | **App-side URL handling without scheme/host whitelisting** (CVE-2024-10474 – Mozilla Focus < 132). | Look for direct `openURL:`/`open(_:options:)` calls or JavaScript bridges that forward arbitrary URLs. | Internal pages can smuggle `myapp://` or `https://` URLs that bypass the browser’s URL-bar safety checks, leading to spoofing or unintended privileged actions.<sup>[[5]](#references)</sup> | 162 | 4 | **Dangerous `components` ordering / ineffective `exclude` rules**. | If the AASA uses `components`, test excluded paths and query-parameter edge cases after every broad allow rule (for example `/*` placed before an `exclude` rule). | `components` are evaluated **in order** and the **first match wins**, so a generic allow rule can silently defeat later deny rules and expose more routing surface than intended.<sup>[[1]](#references)</sup> | 163 | 5 | **Use of wildcard sub-domains** (`*.example.com`) in the entitlement. | `grep` for `*.` in the entitlements. | If any sub-domain is taken over (e.g. via an unused S3 bucket), the attacker automatically gains the Universal Link binding. | 164 165 ### Quick Checklist 166 167 * [ ] Extract entitlements and enumerate every `applinks:` entry. 168 * [ ] Download AASA for each entry and audit for wildcards. 169 * [ ] Verify the web server returns **404** for undefined paths. 170 * [ ] In the binary, confirm that **only** trusted hosts/schemes are handled. 171 * [ ] If the app uses the newer `components` syntax (iOS 11+), fuzz query-parameter rules (`{"?":{…}}`). 172 * [ ] Verify that `exclude` rules appear **before** broad allow rules in `components`. 173 * [ ] Compare the origin AASA with Apple's CDN copy and inspect `swcutil dl` if links still open in Safari. 174 175 ## Tools 176 177 - [GetUniversal.link](https://getuniversal.link/): Helps simplify the testing and management of your app's Universal Links and AASA file. Simply enter your domain to verify AASA file integrity or use the custom dashboard to easily test link behavior. This tool also helps you determine when Apple will next index your AASA file. 178 - [Knil](https://github.com/ethanhuang13/knil): Open-source iOS utility that fetches, parses and lets you **tap-test** every Universal Link declared by a domain directly on device. 179 - [universal-link-validator](https://github.com/urbangems/universal-link-validator): CLI / web validator that performs strict AASA conformance checks and highlights dangerous wildcards. 180 - `swcutil`: Built-in Apple utility that shows the **actual associated-domain verification state** stored on the device. 181 - `frida-trace`: Fast way to instrument `continueUserActivity` handlers and downstream URL-routing code in closed-source apps. 182 183 ## References 184 185 - [1] [TN3155: Debugging universal links - Apple Developer Documentation](https://developer.apple.com/documentation/technotes/tn3155-debugging-universal-links) 186 - [2] [MASTG-TECH-0175: Verifying Universal Link Domain Association](https://mas.owasp.org/MASTG/techniques/ios/MASTG-TECH-0175/) 187 - [3] [MASTG-TECH-0176: Monitoring Universal Link Handlers at Runtime](https://mas.owasp.org/MASTG/techniques/ios/MASTG-TECH-0176/) 188 - [4] [Universal Link hijacking via misconfigured AASA file on Temu.com](https://medium.com/@m.habibgpi/universal-link-hijacking-via-misconfigured-aasa-file-on-temu-com-eadfcb745e4e) 189 - [5] [CVE-2024-10474 detail](https://nvd.nist.gov/vuln/detail/CVE-2024-10474)