hacktricks/mobile-apps-pentesting/ios-pentesting/ios-custom-uri-handlers-deeplinks-custom-schemes.md

181 lines
11 KiB
Markdown
Raw Normal View History

2021-05-21 16:38:18 +00:00
# iOS Custom URI Handlers / Deeplinks / Custom Schemes
Custom URL schemes [allow apps to communicate via a custom protocol](https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/Inter-AppCommunication/Inter-AppCommunication.html#//apple_ref/doc/uid/TP40007072-CH6-SW1). An app must declare support for the schemes and handle incoming URLs that use those schemes.
> URL schemes offer a potential attack vector into your app, so make sure to **validate all URL parameters** and **discard any malformed URLs**. In addition, limit the available **actions** to those that d**o not risk the users data**.
For example, the URI: `myapp://hostname?data=123876123` will **invoke** the **application** mydata \(the one that has **register** the scheme `mydata`\) to the **action** related to the **hostname** `hostname` sending the **parameter** `data` with value `123876123`
One vulnerable example is the following [bug in the Skype Mobile app](http://www.dhanjani.com/blog/2010/11/insecure-handling-of-url-schemes-in-apples-ios.html), discovered in 2010: The Skype app registered the `skype://` protocol handler, which **allowed other apps to trigger calls to other Skype users and phone numbers**. Unfortunately, Skype didn't ask users for permission before placing the calls, so any app could call arbitrary numbers without the user's knowledge. Attackers exploited this vulnerability by putting an invisible `<iframe src="skype://xxx?call"></iframe>` \(where `xxx` was replaced by a premium number\), so any Skype user who inadvertently visited a malicious website called the premium number.
You can find the **schemes registered by an application** in the app's **`Info.plist`** file searching for **`CFBundleURLTypes`** \(example from [iGoat-Swift](https://github.com/OWASP/iGoat-Swift)\):
```markup
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.iGoat.myCompany</string>
<key>CFBundleURLSchemes</key>
<array>
<string>iGoat</string>
</array>
</dict>
</array>
```
However, note that **malicious applications can re-register URIs** already registered by applications. So, if you are sending **sensitive information via URIs** \(myapp://hostname?password=123456\) a **malicious** application can **intercept** the URI with the **sensitive** **information**.
Also, the input of these URIs **should be checked and sanitised,** as it can be coming from **malicious** **origins** trying to exploit SQLInjections, XSS, CSRF, Path Traversals, or other possible vulnerabilities.
### Application Query Schemes Registration
Apps can call [`canOpenURL:`](https://developer.apple.com/documentation/uikit/uiapplication/1622952-canopenurl?language=objc) to verify that the **target app is available**. However, as this method was being used by malicious app as a way to **enumerate installed apps**, [from iOS 9.0 the URL schemes passed to it must be also declared](https://developer.apple.com/documentation/uikit/uiapplication/1622952-canopenurl?language=objc#discussion) by adding the `LSApplicationQueriesSchemes` key to the app's `Info.plist` file and an array of **up to 50 URL schemes**.
```markup
<key>LSApplicationQueriesSchemes</key>
<array>
<string>url_scheme1</string>
<string>url_scheme2</string>
</array>
```
`canOpenURL` will always return `NO` for undeclared schemes, whether or not an appropriate app is installed. However, this restriction only applies to `canOpenURL`.
### Testing URL Handling and Validation
In order to determine how a URL path is built and validated, if you have the original source code, you can **search for the following methods**:
* `application:didFinishLaunchingWithOptions:` method or `application:will-FinishLaunchingWithOptions:`: verify how the decision is made and how the information about the URL is retrieved.
* [`application:openURL:options:`](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623112-application?language=objc): verify how the resource is being opened, i.e. how the data is being parsed, verify the [options](https://developer.apple.com/documentation/uikit/uiapplication/openurloptionskey), especially if access by the calling app \([`sourceApplication`](https://developer.apple.com/documentation/uikit/uiapplication/openurloptionskey/1623128-sourceapplication)\) should be allowed or denied. The app might also need user permission when using the custom URL scheme.
In Telegram you will [find four different methods being used](https://github.com/peter-iakovlev/Telegram-iOS/blob/87e0a33ac438c1d702f2a0b75bf21f26866e346f/Telegram-iOS/AppDelegate.swift#L1250):
```swift
func application(_ application: UIApplication, open url: URL, sourceApplication: String?) -> Bool {
self.openUrl(url: url)
return true
}
func application(_ application: UIApplication, open url: URL, sourceApplication: String?,
annotation: Any) -> Bool {
self.openUrl(url: url)
return true
}
func application(_ app: UIApplication, open url: URL,
options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
self.openUrl(url: url)
return true
}
func application(_ application: UIApplication, handleOpen url: URL) -> Bool {
self.openUrl(url: url)
return true
}
```
### Testing URL Requests to Other Apps
The method [`openURL:options:completionHandler:`](https://developer.apple.com/documentation/uikit/uiapplication/1648685-openurl?language=objc) and the [deprecated `openURL:` method of `UIApplication`](https://developer.apple.com/documentation/uikit/uiapplication/1622961-openurl?language=objc) are responsible for **opening URLs** \(i.e. to send requests / make queries to other apps\) that may be local to the current app or it may be one that must be provided by a different app. If you have the original source code you can search directly for usages of those methods.
Additionally, if you are interested into knowing if the app is querying specific services or apps, and if the app is well-known, you can also search for common URL schemes online and include them in your **greps \(l**[**ist of iOS app schemes**](https://ios.gadgethacks.com/how-to/always-updated-list-ios-app-url-scheme-names-paths-for-shortcuts-0184033/)**\)**.
```bash
egrep -nr "open.*options.*completionHandler" ./Telegram-iOS/
egrep -nr "openURL\(" ./Telegram-iOS/
egrep -nr "mt-encrypted-file://" ./Telegram-iOS/
egrep -nr "://" ./Telegram-iOS/
```
### Testing for Deprecated Methods
Search for deprecated methods like:
* [`application:handleOpenURL:`](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622964-application?language=objc)
* [`openURL:`](https://developer.apple.com/documentation/uikit/uiapplication/1622961-openurl?language=objc)
* [`application:openURL:sourceApplication:annotation:`](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623073-application)
For example, here we find those three:
```bash
$ rabin2 -zzq Telegram\ X.app/Telegram\ X | grep -i "openurl"
0x1000d9e90 31 30 UIApplicationOpenURLOptionsKey
0x1000dee3f 50 49 application:openURL:sourceApplication:annotation:
0x1000dee71 29 28 application:openURL:options:
0x1000dee8e 27 26 application:handleOpenURL:
0x1000df2c9 9 8 openURL:
0x1000df766 12 11 canOpenURL:
0x1000df772 35 34 openURL:options:completionHandler:
...
```
### Calling arbitrary URLs
* **Safari**: To quickly test one URL scheme you can open the URLs on Safari and observe how the app behaves. For example, if you write `tel://123456789` safari will try to start calling the number.
* **Notes App**: Long press the links you've written in order to test custom URL schemes. Remember to exit the editing mode in order to be able to open them. Note that you can click or long press links including custom URL schemes only if the app is installed, if not they won't be highlighted as _clickable links_.
* [**IDB**](https://github.com/facebook/idb):
* Start IDB, connect to your device and select the target app. You can find details in the [IDB documentation](https://www.idbtool.com/documentation/setup.html).
* Go to the **URL Handlers** section. In **URL schemes**, click **Refresh**, and on the left you'll find a list of all custom schemes defined in the app being tested. You can load these schemes by clicking **Open**, on the right side. By simply opening a blank URI scheme \(e.g., opening `myURLscheme://`\), you can discover hidden functionality \(e.g., a debug window\) and bypass local authentication.
* **Frida**:
If you simply want to open the URL scheme you can do it using Frida:
```javascript
$ frida -U iGoat-Swift
[iPhone::iGoat-Swift]-> function openURL(url) {
var UIApplication = ObjC.classes.UIApplication.sharedApplication();
var toOpen = ObjC.classes.NSURL.URLWithString_(url);
return UIApplication.openURL_(toOpen);
}
[iPhone::iGoat-Swift]-> openURL("tel://234234234")
true
```
In this example from [Frida CodeShare](https://codeshare.frida.re/@dki/ios-url-scheme-fuzzing/) the author uses the non-public API `LSApplicationWorkspace.openSensitiveURL:withOptions:` to open the URLs \(from the SpringBoard app\):
```javascript
function openURL(url) {
var w = ObjC.classes.LSApplicationWorkspace.defaultWorkspace();
var toOpen = ObjC.classes.NSURL.URLWithString_(url);
return w.openSensitiveURL_withOptions_(toOpen, null);
}
```
> Note that the use of non-public APIs is not permitted on the App Store, that's why we don't even test these but we are allowed to use them for our dynamic analysis.
### Fuzzing URL Schemes
If the app parses parts of the URL, you can also perform input fuzzing to detect memory corruption bugs.
What we have learned above can be now used to build your own fuzzer on the language of your choice, e.g. in Python and call the `openURL` using [Frida's RPC](https://www.frida.re/docs/javascript-api/#rpc). That fuzzer should do the following:
* Generate payloads.
* For each of them call `openURL`.
* Check if the app generates a crash report \(`.ips`\) in `/private/var/mobile/Library/Logs/CrashReporter`.
The [FuzzDB](https://github.com/fuzzdb-project/fuzzdb) project offers fuzzing dictionaries that you can use as payloads.
### **Fuzzing Using Frida**
Doing this with Frida is pretty easy, you can refer to this [blog post](https://grepharder.github.io/blog/0x03_learning_about_universal_links_and_fuzzing_url_schemes_on_ios_with_frida.html) to see an example that fuzzes the iGoat-Swift app \(working on iOS 11.1.2\).
Before running the fuzzer we need the URL schemes as inputs. From the static analysis we know that the iGoat-Swift app supports the following URL scheme and parameters: `iGoat://?contactNumber={0}&message={0}`.
```bash
$ frida -U SpringBoard -l ios-url-scheme-fuzzing.js
[iPhone::SpringBoard]-> fuzz("iGoat", "iGoat://?contactNumber={0}&message={0}")
Watching for crashes from iGoat...
No logs were moved.
Opened URL: iGoat://?contactNumber=0&message=0
```
## References
{% embed url="https://mobile-security.gitbook.io/mobile-security-testing-guide/ios-testing-guide/0x06h-testing-platform-interaction\#testing-object-persistence-mstg-platform-8" %}