iOS Universal Links
Universal links are another potential attack vector into an iOS app. Misconfigured URL parameters or malformed links could put the app data at risk. Additionally, overly permissive links could end up exposing more data than the developer anticipated. All universal links should be validated and tested for data leakage.
Universal links are used for deep linking within the iOS app. When an app is installed on the device, the system verifies the association through the Apple App Site Association (AASA) file on the domain. This eliminates the possibility of another app claiming the link scheme and redirecting the legitimate URLs.
When a user taps on a universal link, if the app that is associated with the link is installed (and validated) then they are seamlessly redirected to the proper app which will display the link. If the app is not installed on the device, it will then use the mobile Safari browser to redirect to the proper domain.
Universal links are not the same as URL schemes, and you may encounter both in an iOS app. Universal links will always use the https scheme, while URL schemes define their own. For example:
Universal link: **https://**domain.com/page-link
URL Schemes: **gd://**resolve?domain=domainname
Unlike custom URL schemes, universal links cannot be claimed by other apps since they are linking to the app’s website. By examining the defined universal links, and the associated domains entitlements, you can find additional paths to look at for sensitive data. App links are stored in a JSON file on the domain.
Static Analysis
App links are defined in the application entitlements which can be extracted using the jtool2 program.
% jtool2 --ent binary > entitlements.plist
% /usr/libexec/PlistBuddy -c 'Print :com.apple.developer.associated-domains' entitlements.plist
Array {
applinks:app.overhq.com
applinks:accept.overhq.com
applinks:over.onelink.me
applinks:over.godaddy.com
applinks:web.over.app
applinks:studio.godaddy.com
applinks:studio.click.godaddy.com
}
Using the applinks: values, attempt to pull the JSON file from the respective domains. This can be implemented one of two ways (or both):
- https://domain.com/apple-app-site-association
- https://domain.com/.well-known/apple-app-site-association
1st way: https://app.overhq.com/apple-app-site-association
% curl https://app.overhq.com/apple-app-site-association
{
"applinks": {
"apps": [],
"details": [
{
"appID": "7UMADG39Z9.com.gopotluck.over",
"paths": ["/i/*", "/password-reset/*", "/support", "/learn/video/*", "/team/**", "/pro/subscribe/*", "/pro/subscribe", "/home", "/templates/search/*"]
},
{
"appID": "7UMADG39Z9.com.gopotluck.over-beta",
"paths": ["/beta/i/*", "/beta/password-reset/*", "/support", "/learn/video/*", "/team/**", "/pro/subscribe/*", "/pro/subscribe", "/home", "/templates/search/*"]
}
]
}
}
2nd way (piped to json_pp for readability): https://app.overhq.com/.well-known/apple-app-site-association
% curl -s https://app.overhq.com/.well-known/apple-app-site-association | json_pp
{
"applinks" : {
"apps" : [],
"details" : [
{
"appID" : "7UMADG39Z9.com.gopotluck.over",
"paths" : [
"/i/*",
"/password-reset/*",
"/support",
"/learn/video/*",
"/team/**",
"/pro/subscribe/*",
"/pro/subscribe",
"/home",
"/templates/search/*"
]
},
{
"appID" : "7UMADG39Z9.com.gopotluck.over-beta",
"paths" : [
"/beta/i/*",
"/beta/password-reset/*",
"/support",
"/learn/video/*",
"/team/**",
"/pro/subscribe/*",
"/pro/subscribe",
"/home",
"/templates/search/*"
]
}
]
}
}
Apple App Site Association Validator
There is also an online validator that can be used to ensure that the AASA file is setup correctly.
https://branch.io/resources/aasa-validator/
Enter the domain(s) from the entitlements above.
Dynamic Analysis
Once the domains and links have been reviewed and validated, we should test the ‘link receiver method’ to evaluate how the app handles the links. A universal link should be opened with the application:continueUserActivity:restorationHandler:
method. If the app opens the universal link with the openURL:options:completionHandler:
method, it won’t open in the app and will instead open in mobile Safari.
Check the application binary for these methods using rabin2:
% rabin2 -zq binary| grep restorationHan
0x100733152 53 52 application:continueUserActivity:restorationHandler:
0x100733330 41 40 continueUserActivity:restorationHandler:
% rabin2 -zq binary| grep openURL
0x1007271fe 35 34 openURL:options:completionHandler:
0x10072fecb 29 28 application:openURL:options:
0x1007333bb 50 49 application:openURL:sourceApplication:annotation:
0x10074358b 17 16 openURL:options:
Using Frida, trace the use of these methods in the application to ensure that the proper method is handling the proper link (either URL Scheme or Universal Link).
% frida-trace -U -m '-[* application:continueUserActivity:restorationHandler:]' -f com.domain.app
% frida-trace -U -m '-[* openURL:options:completionHandler:]' -f com.domain.app
Once the trace is started, use the app in a manner that will cause the links to be accessed.
REFERENCES
- Supporting Associated Domains: https://developer.apple.com/documentation/xcode/supporting-associated-domains
- Apple Universal Links: https://developer.apple.com/ios/universal-links/
- Apple Supporting Universal Links in your App: https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app
- application:continueUserActivity:restorationHandler Method
- openURL:options:completionHandler: Method