Unsachgemäße Autorisierung im Handler für benutzerdefinierte URL-Schemata

Beschreibung

Unsachgemäße Autorisierung im Handler für benutzerdefinierte URL-Schemata tritt auf, wenn Anwendungen, die benutzerdefinierte URL-Schemata verarbeiten (wie myapp://, customscheme://), nicht ordnungsgemäß einschränken, welche Akteure sie aufrufen können. Mobile Plattformen wie iOS und Android verwenden benutzerdefinierte URL-Schemata, um Inter-Anwendungs-Kommunikation und Deep Linking zu ermöglichen. Wenn Handler für diese Schemata die Quelle der Anfrage nicht verifizieren oder ordnungsgemäße Autorisierungsprüfungen fehlen, kann jede Anwendung oder Website potenziell gefährliche Funktionalität auslösen, indem URLs mit dem benutzerdefinierten Schema erstellt werden.

Risiko

Diese Schwachstelle kann schwerwiegende Folgen haben, abhängig von der Funktionalität, die durch das benutzerdefinierte URL-Schema offengelegt wird. Angreifer können Dateimodifikations- oder -löschoperationen aufrufen, sensible Aktionen ohne Benutzereinwilligung auslösen, auf interne APIs zugreifen, die für spezifische vertrauenswürdige Anwendungen bestimmt sind, Datenlecks durch Aufruf von Export- oder Freigabefunktionen verursachen, Käufe oder Finanztransaktionen auslösen und Anwendungseinstellungen oder Benutzereinstellungen modifizieren. Der Angriff ist besonders gefährlich, weil er von jeder Anwendung auf dem Gerät oder von Webseiten, die der Benutzer besucht, initiiert werden kann.

Lösung

Implementieren Sie Autorisierungsprüfungen in URL-Schema-Handlern, um die Identität und Berechtigungen des Aufrufers zu verifizieren. Verwenden Sie sichere Plattformmechanismen für Inter-App-Kommunikation, wo möglich. Erfordern Sie Benutzerbestätigung für sensible Operationen, die über URL-Schemata ausgelöst werden. Validieren und sanitisieren Sie alle Parameter, die über URL-Schemata übergeben werden. Erwägen Sie die Verwendung von Universal Links (iOS) oder App Links (Android), die sicherere Alternativen bieten. Beschränken Sie die Funktionalität, die über URL-Schemata offengelegt wird, auf nicht-sensible Operationen. Protokollieren Sie URL-Schema-Aufrufe zur Sicherheitsüberwachung.

Häufige Auswirkungen

AuswirkungDetails
ZugriffskontrolleBereich: Zugriffskontrolle

Privilegien erlangen oder Identität annehmen - Angreifer können auf Funktionalität zugreifen, die versehentlich über URL-Schemata offengelegt wurde, und Privilegien erlangen, die für autorisierte Aufrufer bestimmt sind.
ZugriffskontrolleBereich: Zugriffskontrolle

Schutzmechanismus umgehen - URL-Schema-Handler können normale Authentifizierungs- oder Autorisierungsabläufe umgehen.
IntegritätBereich: Integrität

Anwendungsdaten modifizieren - Handler, die Daten ohne Autorisierungsprüfungen modifizieren, ermöglichen Angreifern, den Anwendungsstatus zu ändern.

Beispielcode

Anfälliger Code

// Anfällig: iOS URL-Schema-Handler ohne Autorisierung
@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
        openURL:(NSURL *)url
        options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {

    NSString *scheme = [url scheme];
    NSString *action = [url host];
    NSDictionary *params = [self parseQueryParams:[url query]];

    // Anfällig: Keine Verifizierung der Quelle, keine Autorisierungsprüfung
    if ([action isEqualToString:@"replaceFileText"]) {
        NSString *fileName = params[@"filename"];
        NSString *newText = params[@"newtext"];

        // Jede App kann Dateien modifizieren!
        [self replaceTextInFile:fileName withText:newText];
        return YES;
    }

    if ([action isEqualToString:@"deleteFile"]) {
        NSString *fileName = params[@"filename"];
        // Jede App kann Dateien löschen!
        [self deleteFile:fileName];
        return YES;
    }

    return NO;
}

@end

// Angriff: URL "myapp://replaceFileText?filename=config.txt&newtext=malicious" öffnen
// Anfällig: Android URL-Schema-Handler ohne Prüfungen
public class VulnerableDeepLinkActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Intent intent = getIntent();
        Uri data = intent.getData();

        if (data != null && "myapp".equals(data.getScheme())) {
            String action = data.getHost();

            // Anfällig: Keine Autorisierungsprüfung
            if ("transferFunds".equals(action)) {
                String amount = data.getQueryParameter("amount");
                String recipient = data.getQueryParameter("to");
                // Jede App kann Überweisungen initiieren!
                transferFunds(amount, recipient);
            }

            if ("exportData".equals(action)) {
                String destination = data.getQueryParameter("dest");
                // Jede App kann Benutzerdaten exportieren!
                exportAllDataTo(destination);
            }
        }
    }
}
// Anfällig: WebView mit ungeschützter JavaScript-Bridge
class VulnerableWebViewController: UIViewController, WKNavigationDelegate {

    var webView: WKWebView!

    func webView(_ webView: WKWebView,
                 decidePolicyFor navigationAction: WKNavigationAction,
                 decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {

        if let url = navigationAction.request.url,
           url.scheme == "myapp" {

            // Anfällig: Keine Quellenverifizierung
            // Jede Website kann native Funktionen aufrufen
            handleCustomURL(url)
            decisionHandler(.cancel)
            return
        }

        decisionHandler(.allow)
    }

    func handleCustomURL(_ url: URL) {
        let action = url.host ?? ""

        // Anfällig: Native APIs an Web-Inhalte offenlegen
        if action == "getContacts" {
            // Bösartige Website kann Kontakte stehlen
            let contacts = fetchAllContacts()
            injectToWebView(contacts)
        }

        if action == "sendSMS" {
            // Bösartige Website kann SMS senden
            let number = url.queryParam("number")
            let message = url.queryParam("message")
            sendSMS(to: number, message: message)
        }
    }
}

Korrigierter Code

// Korrigiert: iOS URL-Schema-Handler mit Autorisierung
@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
        openURL:(NSURL *)url
        options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {

    NSString *scheme = [url scheme];
    NSString *action = [url host];
    NSDictionary *params = [self parseQueryParams:[url query]];

    // Korrigiert: Quellanwendung erhalten
    NSString *sourceApp = options[UIApplicationOpenURLOptionsSourceApplicationKey];

    // Korrigiert: Prüfen, ob Quelle autorisiert ist
    if (![self isAuthorizedSource:sourceApp forAction:action]) {
        NSLog(@"URL von unautorisierter Quelle abgelehnt: %@", sourceApp);
        return NO;
    }

    if ([action isEqualToString:@"replaceFileText"]) {
        NSString *fileName = params[@"filename"];
        NSString *newText = params[@"newtext"];

        // Korrigiert: Benutzerbestätigung für sensible Operationen erforderlich
        [self confirmAction:@"Text ersetzen"
                   message:[NSString stringWithFormat:@"%@ erlauben, %@ zu modifizieren?", sourceApp, fileName]
                completion:^(BOOL confirmed) {
            if (confirmed) {
                [self replaceTextInFile:fileName withText:newText];
            }
        }];
        return YES;
    }

    return NO;
}

- (BOOL)isAuthorizedSource:(NSString *)sourceApp forAction:(NSString *)action {
    // Autorisierte Aufrufer pro Aktion definieren
    NSDictionary *authorizedApps = @{
        @"viewDocument": @[@"com.trusted.app1", @"com.trusted.app2"],
        @"replaceFileText": @[@"com.trusted.app1"],  // Nur spezifische App
    };

    NSArray *allowedApps = authorizedApps[action];
    if (!allowedApps) {
        return NO;  // Unbekannte Aktion
    }

    return [allowedApps containsObject:sourceApp];
}

- (void)confirmAction:(NSString *)title
              message:(NSString *)message
           completion:(void (^)(BOOL confirmed))completion {

    UIAlertController *alert = [UIAlertController
        alertControllerWithTitle:title
        message:message
        preferredStyle:UIAlertControllerStyleAlert];

    [alert addAction:[UIAlertAction actionWithTitle:@"Abbrechen"
        style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
            completion(NO);
        }]];

    [alert addAction:[UIAlertAction actionWithTitle:@"Erlauben"
        style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
            completion(YES);
        }]];

    [self.window.rootViewController presentViewController:alert animated:YES completion:nil];
}

@end
// Korrigiert: Android URL-Schema-Handler mit Autorisierung
public class FixedDeepLinkActivity extends Activity {

    private static final Set<String> TRUSTED_PACKAGES = new HashSet<>(Arrays.asList(
        "com.trusted.partner",
        "com.mycompany.otherapp"
    ));

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Intent intent = getIntent();
        Uri data = intent.getData();

        if (data == null || !"myapp".equals(data.getScheme())) {
            finish();
            return;
        }

        // Korrigiert: Verweispaket erhalten
        String referrer = getReferrer() != null ? getReferrer().getHost() : null;

        String action = data.getHost();

        // Korrigiert: Autorisierung prüfen
        if (!isActionAllowed(action, referrer)) {
            Log.w(TAG, "Unautorisierter Zugriffsversuch von: " + referrer);
            Toast.makeText(this, "Unautorisierter Zugriff", Toast.LENGTH_SHORT).show();
            finish();
            return;
        }

        if ("transferFunds".equals(action)) {
            // Korrigiert: Bestätigungsdialog anzeigen
            String amount = data.getQueryParameter("amount");
            String recipient = data.getQueryParameter("to");
            confirmAndTransfer(amount, recipient, referrer);
        }

        if ("viewDocument".equals(action)) {
            // Nur-Lese-Operationen benötigen möglicherweise keine Bestätigung
            String docId = data.getQueryParameter("id");
            viewDocument(docId);
        }
    }

    private boolean isActionAllowed(String action, String referrer) {
        // Aktionsberechtigungen definieren
        Map<String, Boolean> publicActions = new HashMap<>();
        publicActions.put("viewDocument", true);  // Öffentlich
        publicActions.put("transferFunds", false);  // Erfordert vertrauenswürdige Quelle

        Boolean isPublic = publicActions.get(action);
        if (isPublic == null) {
            return false;  // Unbekannte Aktion
        }

        if (isPublic) {
            return true;
        }

        // Private Aktion - Quelle verifizieren
        return TRUSTED_PACKAGES.contains(referrer);
    }

    private void confirmAndTransfer(String amount, String recipient, String source) {
        new AlertDialog.Builder(this)
            .setTitle("Überweisung bestätigen")
            .setMessage("Überweisen Sie " + amount + " an " + recipient + "?\nAngefordert von: " + source)
            .setPositiveButton("Bestätigen", (dialog, which) -> {
                // Mit Überweisung fortfahren
                transferFunds(amount, recipient);
            })
            .setNegativeButton("Abbrechen", null)
            .show();
    }
}
// Korrigiert: WebView mit geschützter JavaScript-Bridge
class FixedWebViewController: UIViewController, WKNavigationDelegate {

    var webView: WKWebView!

    // Korrigiert: Whitelist vertrauenswürdiger Domains
    private let trustedDomains = Set(["www.trustedsite.com", "api.trustedsite.com"])

    func webView(_ webView: WKWebView,
                 decidePolicyFor navigationAction: WKNavigationAction,
                 decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {

        if let url = navigationAction.request.url,
           url.scheme == "myapp" {

            // Korrigiert: Verifizieren, dass Quelle vertrauenswürdig ist
            if let sourceURL = webView.url,
               let sourceHost = sourceURL.host,
               trustedDomains.contains(sourceHost) {

                handleCustomURL(url)
            } else {
                print("Benutzerdefinierte URL von nicht vertrauenswürdiger Quelle abgelehnt: \(webView.url?.host ?? "unknown")")
            }

            decisionHandler(.cancel)
            return
        }

        decisionHandler(.allow)
    }

    func handleCustomURL(_ url: URL) {
        let action = url.host ?? ""

        // Korrigiert: Nur sichere, begrenzte Funktionalität offenlegen
        switch action {
        case "shareContent":
            // Nur-Lese, für Benutzer sichtbare Aktion
            if let content = url.queryParam("content") {
                showShareSheet(for: content)
            }

        case "openSettings":
            // Öffnet App-Einstellungen - sicher
            openAppSettings()

        default:
            print("Unbekannte Aktion: \(action)")
        }

        // Korrigiert: Sensible Operationen aus URL-Schema-Handler entfernt
        // Authentifizierte API-Aufrufe stattdessen verwenden
    }
}

CVE-Beispiele

  • CVE-2013-5725: URL-Schema-Handler ermöglichte entfernten Angreifern, Aktionen ohne Benutzeraufforderung durchzuführen.
  • CVE-2013-5726: URL-Schema-Handler könnte Benutzer zu unerwünschtem Verhalten zwingen.

Verwandte CWEs

  • CWE-862: Missing Authorization (Eltern)
  • CWE-940: Improper Verification of Source of a Communication Channel (Eltern)
  • CWE-925: Improper Verification of Intent by Broadcast Receiver (verwandt)

Referenzen

  1. MITRE Corporation. "CWE-939: Improper Authorization in Handler for Custom URL Scheme." https://cwe.mitre.org/data/definitions/939.html
  2. Apple Developer Documentation. "Defining a Custom URL Scheme for Your App."
  3. Android Developers. "Create Deep Links to App Content."