Published on

URL Scheme Hijacking in iOS Apps: Understanding and Preventing the Vulnerability

Authors

URL Scheme Hijacking

Custom URL schemes in iOS apps enable deep linking and inter-app communication, offering a seamless user experience. However, if not securely implemented, they can expose vulnerabilities that malicious actors may exploit. One such vulnerability is URL Scheme Hijacking.

This tutorial will delve into what URL Scheme Hijacking is, demonstrate how it can occur with a practical example, and provide best practices to prevent it.


Table of Contents


Understanding URL Schemes

In iOS, a URL scheme allows apps to communicate with each other or be launched via specific URLs. For example, a custom URL scheme like myapp:// can be used to open an app and perform certain actions based on the URL's parameters.

What is URL Scheme Hijacking?

URL Scheme Hijacking occurs when a malicious app registers the same custom URL scheme as a legitimate app. This allows the malicious app to intercept URLs intended for the legitimate app, potentially capturing sensitive data or performing unauthorized actions.

Vulnerability Demonstration

Scenario Overview

Suppose we have a banking app called SecureBank that uses a custom URL scheme securebank:// for initiating money transfers via deep links. We'll demonstrate how a malicious app can hijack this URL scheme.

Implementing the Vulnerable Banking App

SecureBank registers the custom URL scheme in its Info.plist:

<!-- SecureBank's Info.plist -->
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>securebank</string>
        </array>
    </dict>
</array>

AppDelegate.swift implementation:

// AppDelegate.swift
func application(_ app: UIApplication,
                 open url: URL,
                 options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    if url.scheme == "securebank" {
        handleDeepLink(url: url)
        return true
    }
    return false
}

func handleDeepLink(url: URL) {
    // Parse the URL and extract parameters
    if let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
       let queryItems = components.queryItems {
        // Extract amount and recipient
        let amount = queryItems.first(where: { $0.name == "amount" })?.value
        let recipient = queryItems.first(where: { $0.name == "to" })?.value

        // Proceed with the transfer
        initiateTransfer(amount: amount, recipient: recipient)
    }
}

func initiateTransfer(amount: String?, recipient: String?) {
    guard let amount = amount, let recipient = recipient else { return }
    // Logic to initiate the transfer
    print("Transferring $\(amount) to \(recipient)")
}

Implementing the Malicious App

The malicious app also registers the securebank:// URL scheme:

<!-- Malicious App's Info.plist -->
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>securebank</string>
        </array>
    </dict>
</array>

Malicious AppDelegate.swift implementation:

// Malicious AppDelegate.swift
func application(_ app: UIApplication,
                 open url: URL,
                 options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    if url.scheme == "securebank" {
        // Capture the sensitive data
        logSensitiveData(url: url)
        return true
    }
    return false
}

func logSensitiveData(url: URL) {
    // Extract parameters
    if let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
       let queryItems = components.queryItems {
        let amount = queryItems.first(where: { $0.name == "amount" })?.value
        let recipient = queryItems.first(where: { $0.name == "to" })?.value
        // Log or send the data to a remote server
        print("Intercepted transfer of $\(amount ?? "N/A") to \(recipient ?? "N/A")")
    }
}

Outcome:

  • When a user clicks on a link like securebank://transfer?amount=100&to=JohnDoe, the malicious app may intercept the URL.
  • The malicious app captures sensitive information without the user's knowledge.

Preventing URL Scheme Hijacking

Universal Links are standard HTTPS links that direct users to content in your app or website. They are more secure because they rely on domain ownership verification.

  1. Set Up Associated Domains:

    • In your Xcode project, navigate to Signing & Capabilities.
    • Add Associated Domains with your domain prefixed by applinks: (e.g., applinks:securebank.com).
  2. Configure the Apple App Site Association File:

    • Host an apple-app-site-association (AASA) file at https://securebank.com/apple-app-site-association.

    • The file should contain:

      {
        "applinks": {
          "apps": [],
          "details": [
            {
              "appID": "<TeamID>.com.securebank.app",
              "paths": ["/transfer/*"]
            }
          ]
        }
      }
      
  3. Modify the App to Handle Universal Links:

    SceneDelegate.swift

    // SceneDelegate.swift
    func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
        if userActivity.activityType == NSUserActivityTypeBrowsingWeb,
           let url = userActivity.webpageURL {
            handleUniversalLink(url: url)
        }
    }
    
    func handleUniversalLink(_ url: URL) {
        // Validate and process the URL
        guard url.host == "securebank.com" else { return }
        // Parse path and query parameters
        print("Handling Universal Link: \(url.absoluteString)")
    }
    

    AppDelegate.swift (for iOS versions without SceneDelegate)

    // AppDelegate.swift
    func application(_ application: UIApplication,
                     continue userActivity: NSUserActivity,
                     restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
        if userActivity.activityType == NSUserActivityTypeBrowsingWeb,
           let url = userActivity.webpageURL {
            handleUniversalLink(url: url)
            return true
        }
        return false
    }
    
  4. Update Web Links to Use HTTPS URLs:

    • Replace securebank://transfer?amount=100&to=JohnDoe with https://securebank.com/transfer?amount=100&to=JohnDoe.

Benefits of Using Universal Links:

  • Security: Only apps associated with the domain can handle the links.
  • User Experience: Seamlessly fallback to the website if the app is not installed.
  • No Prompts: Users are not prompted to choose an app, reducing confusion.

Validate URL Inputs

Regardless of using Universal Links or custom URL schemes, always validate and sanitize input data from URLs.

func handleUniversalLink(_ url: URL) {
    // Ensure the URL is from a trusted source
    guard url.host == "securebank.com" else { return }

    if let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
       let path = components.path, path.starts(with: "/transfer"),
       let queryItems = components.queryItems {
        // Extract and validate parameters
        if let amountString = queryItems.first(where: { $0.name == "amount" })?.value,
           let amount = Double(amountString),
           let recipient = queryItems.first(where: { $0.name == "to" })?.value {
            // Proceed with secure transfer
            initiateSecureTransfer(amount: amount, recipient: recipient)
        }
    }
}

func initiateSecureTransfer(amount: Double, recipient: String) {
    // Additional security checks and transfer logic
    print("Securely transferring $\(amount) to \(recipient)")
}
  • Verify the Host and Path: Ensure the URL is from your domain and the path is valid.
  • Validate Parameters: Check that parameters are in the expected format and range.

Check the Source Application

From iOS 16 onwards, you can identify the source application of a URL open request.

func application(_ app: UIApplication,
                 open url: URL,
                 options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    if let sourceBundleID = options[.sourceApplication] as? String {
        let trustedApps = ["com.apple.mobilesafari", "com.apple.SafariViewService"]
        if trustedApps.contains(sourceBundleID) {
            handleDeepLink(url: url)
            return true
        } else {
            // Handle untrusted source
            print("Untrusted source application: \(sourceBundleID)")
            return false
        }
    }
    return false
}
  • Limit to Trusted Sources: Only accept URLs from trusted apps like Safari.
  • Enhance Security: Prevent malicious apps from initiating requests.

Key Takeaways

  • Avoid Custom URL Schemes for Sensitive Actions:

    • Use Universal Links instead of custom URL schemes for deep linking, especially when handling sensitive data or actions.
  • Implement Domain Verification:

    • Ensure your app and website are properly associated to prevent unauthorized apps from claiming your Universal Links.
  • Validate All Inputs:

    • Never trust incoming data blindly. Always validate and sanitize data extracted from URLs.
  • Stay Updated with Security Practices:

    • Keep abreast of the latest security features provided by iOS, such as checking the source application.
  • Educate Users:

    • Inform users about the importance of downloading apps from trusted sources to minimize the risk of malicious apps on their devices.

References


By understanding the risks associated with URL Scheme Hijacking and implementing the recommended security measures, developers can significantly enhance the security of their iOS apps, protecting both the app and its users from potential threats.