Unsachgemäßer Export von Android-Anwendungskomponenten

Beschreibung

Unsachgemäßer Export von Android-Anwendungskomponenten tritt auf, wenn eine Android-Anwendung Activities, Services, Content Provider oder Broadcast Receiver exportiert, ohne ordnungsgemäß einzuschränken, welche Anwendungen darauf zugreifen können. Standardmäßig werden Komponenten mit Intent-Filtern exportiert (android:exported="true"), was sie für andere Anwendungen auf dem Gerät zugänglich macht. Wenn Komponenten, die sensible Operationen verarbeiten, ohne ordnungsgemäße Zugriffskontrollen exportiert werden, können bösartige Anwendungen diese Komponenten starten, um auf sensible Daten zuzugreifen, unautorisierte Aktionen auszulösen oder den Anwendungsstatus zu beschädigen.

Risiko

Exportierte Komponenten ohne ordnungsgemäße Zugriffskontrollen schaffen erhebliche Sicherheitsrisiken. Exportierte Activities können sensible Benutzeroberflächen offenlegen oder Authentifizierungsabläufe überspringen, wenn sie direkt gestartet werden. Exportierte Services können von bösartigen Anwendungen gestartet und gebunden werden, was ihnen ermöglicht, unautorisierte Operationen durchzuführen oder auf interne Funktionalität zuzugreifen. Exportierte Content Provider (besonders auf Android-Versionen vor 4.2, wo sie standardmäßig exportiert werden) können sensible Daten durchsickern lassen oder unautorisierte Änderungen ermöglichen. Exportierte Broadcast Receiver können mit bösartigen Intents ausgelöst werden. Das Risiko wird verstärkt, wenn diese Komponenten sensible Daten verarbeiten oder privilegierte Operationen durchführen.

Lösung

Setzen Sie android:exported="false" explizit für Komponenten, die nicht von anderen Anwendungen zugegriffen werden müssen. Für Komponenten, die exportiert werden müssen, verwenden Sie Berechtigungen auf Signaturebene, um den Zugriff auf Anwendungen zu beschränken, die mit demselben Schlüssel signiert sind. Implementieren Sie ordnungsgemäße Autorisierungsprüfungen innerhalb exportierter Komponenten. Verwenden Sie Intent-Validierung, um die Quelle und den Inhalt eingehender Intents zu verifizieren. Für Content Provider verwenden Sie ordnungsgemäße Berechtigungen mit Lese/Schreib-Unterscheidung. Erwägen Sie die Verwendung von android:permission-Attributen, um zu verlangen, dass Aufrufer bestimmte Berechtigungen besitzen. Prüfen Sie alle Komponenten im Manifest, um angemessene Export-Einstellungen sicherzustellen.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitBereich: Vertraulichkeit

Anwendungsdaten lesen - Exportierte Content Provider oder Activities können sensible Daten für unautorisierte Anwendungen offenlegen.
IntegritätBereich: Integrität

Anwendungsdaten modifizieren - Bösartige Apps können Daten über exportierte Content Provider modifizieren oder den Status über exportierte Services beschädigen.
ZugriffskontrolleBereich: Zugriffskontrolle

Schutzmechanismus umgehen - Direktes Starten exportierter Activities kann Authentifizierungs- oder Autorisierungsbildschirme umgehen.

Beispielcode

Anfälliger Code

<!-- Anfälliges AndroidManifest.xml: Exportierte Activity -->
<activity android:name="com.example.app.AdminActivity">
    <!-- Anfällig: Intent-Filter macht dies standardmäßig exportiert -->
    <intent-filter>
        <action android:name="com.example.app.ADMIN_ACTION"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>

<!-- Jede App kann diese Admin-Activity starten! -->
<!-- Anfällig: Exportierter Service ohne Schutz -->
<service android:name="com.example.app.DataSyncService"
         android:exported="true">
    <intent-filter>
        <action android:name="com.example.app.SYNC_DATA"/>
    </intent-filter>
</service>

<!-- Bösartige Apps können sich an diesen Service binden -->
<!-- Anfällig: Content Provider standardmäßig exportiert (pre-4.2) -->
<provider android:name="com.example.app.UserDataProvider"
          android:authorities="com.example.app.userdata">
    <!-- Auf Android < 4.2 ist dies standardmäßig exportiert! -->
</provider>
// Anfällig: Exportierte Activity mit sensiblen Daten
public class VulnerableAdminActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.admin_layout);

        // Anfällig: Keine Verifizierung, dass Aufrufer autorisiert ist
        // Zeigt Admin-Steuerelemente jeder App, die diese Activity startet
        displayAdminDashboard();
        showUserDatabase();
    }
}
// Anfällig: Exportierter Service führt privilegierte Operationen durch
public class VulnerableDataService extends Service {

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        String action = intent.getAction();

        // Anfällig: Jede App kann diese Aktionen auslösen
        if ("DELETE_ALL_DATA".equals(action)) {
            deleteAllUserData();  // Gefährlich!
        } else if ("EXPORT_DATA".equals(action)) {
            exportSensitiveData();  // Datenleck!
        }

        return START_NOT_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        // Anfällig: Gibt Binder an jede App zurück
        return new DataServiceBinder();
    }
}
// Bösartige App nutzt exportierte Komponenten aus
public class MaliciousApp {

    public void exploitExportedActivity(Context context) {
        // Admin-Activity von bösartiger App starten
        Intent intent = new Intent();
        intent.setComponent(new ComponentName(
            "com.example.vulnerableapp",
            "com.example.vulnerableapp.AdminActivity"
        ));
        context.startActivity(intent);
        // Erlangt Zugang zur Admin-UI!
    }

    public void exploitExportedService(Context context) {
        // Gefährliche Service-Aktion auslösen
        Intent intent = new Intent("DELETE_ALL_DATA");
        intent.setPackage("com.example.vulnerableapp");
        context.startService(intent);
        // Löscht Benutzerdaten!
    }

    public void exploitExportedProvider(Context context) {
        // Daten von exportiertem Provider lesen
        Uri uri = Uri.parse("content://com.example.app.userdata/users");
        Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
        // Stiehlt alle Benutzerdaten!
    }
}

Korrigierter Code

<!-- Korrigiertes AndroidManifest.xml: Geschützte Komponenten -->

<!-- Korrigiert: Explizit nicht exportiert -->
<activity android:name="com.example.app.AdminActivity"
          android:exported="false">
    <!-- Kein Intent-Filter für interne Activities erforderlich -->
</activity>

<!-- Korrigiert: Mit Signatur-Berechtigung geschützt -->
<permission android:name="com.example.app.permission.ADMIN_ACCESS"
            android:protectionLevel="signature"/>

<activity android:name="com.example.app.ExternalAdminActivity"
          android:exported="true"
          android:permission="com.example.app.permission.ADMIN_ACCESS">
    <intent-filter>
        <action android:name="com.example.app.ADMIN_ACTION"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>
<!-- Korrigiert: Service mit ordnungsgemäßem Schutz -->
<service android:name="com.example.app.DataSyncService"
         android:exported="false">
    <!-- Interner Service, nicht für andere Apps zugänglich -->
</service>

<!-- Wenn Service exportiert werden muss -->
<service android:name="com.example.app.ExternalService"
         android:exported="true"
         android:permission="com.example.app.permission.USE_SERVICE">
    <intent-filter>
        <action android:name="com.example.app.EXTERNAL_ACTION"/>
    </intent-filter>
</service>
<!-- Korrigiert: Content Provider mit ordnungsgemäßen Berechtigungen -->
<provider android:name="com.example.app.UserDataProvider"
          android:authorities="com.example.app.userdata"
          android:exported="false">
    <!-- Nicht für andere Apps zugänglich -->
</provider>

<!-- Wenn Provider exportiert werden muss, Berechtigungen verwenden -->
<provider android:name="com.example.app.PublicDataProvider"
          android:authorities="com.example.app.publicdata"
          android:exported="true"
          android:readPermission="com.example.app.permission.READ_DATA"
          android:writePermission="com.example.app.permission.WRITE_DATA">
</provider>
// Korrigiert: Activity mit Autorisierungsprüfungen
public class FixedAdminActivity extends Activity {

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

        // Korrigiert: Aufrufer-Autorisierung auch bei Export verifizieren
        if (!isCallerAuthorized()) {
            Log.w(TAG, "Unautorisierter Zugriffsversuch auf Admin-Activity");
            finish();
            return;
        }

        setContentView(R.layout.admin_layout);
        displayAdminDashboard();
    }

    private boolean isCallerAuthorized() {
        // Prüfen, ob Aufrufer erforderliche Berechtigung hat
        String callingPackage = getCallingPackage();
        if (callingPackage == null) {
            // Intern gestartet
            return true;
        }

        // Verifizieren, dass Aufrufer Signatur-Berechtigung hat
        int permission = checkCallingPermission(
            "com.example.app.permission.ADMIN_ACCESS");
        return permission == PackageManager.PERMISSION_GRANTED;
    }
}
// Korrigiert: Service mit Aktionsvalidierung und Autorisierung
public class FixedDataService extends Service {

    private static final Set<String> ALLOWED_ACTIONS = new HashSet<>(Arrays.asList(
        "SYNC_DATA",
        "CHECK_STATUS"
    ));

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // Korrigiert: Aufrufer validieren
        if (!isCallerAuthorized()) {
            Log.w(TAG, "Unautorisierter Service-Start-Versuch");
            return START_NOT_STICKY;
        }

        String action = intent.getAction();

        // Korrigiert: Whitelist erlaubter Aktionen
        if (!ALLOWED_ACTIONS.contains(action)) {
            Log.w(TAG, "Unbekannte Aktion abgelehnt: " + action);
            return START_NOT_STICKY;
        }

        if ("SYNC_DATA".equals(action)) {
            syncData();
        } else if ("CHECK_STATUS".equals(action)) {
            checkStatus();
        }

        return START_NOT_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        // Korrigiert: Aufrufer vor Rückgabe des Binders verifizieren
        if (!isCallerAuthorized()) {
            Log.w(TAG, "Unautorisierter Bind-Versuch");
            return null;
        }

        return new DataServiceBinder();
    }

    private boolean isCallerAuthorized() {
        int callingUid = Binder.getCallingUid();
        int myUid = Process.myUid();

        // Erlauben, wenn gleiche App
        if (callingUid == myUid) {
            return true;
        }

        // Auf erforderliche Berechtigung prüfen
        int permission = checkCallingPermission(
            "com.example.app.permission.USE_SERVICE");
        return permission == PackageManager.PERMISSION_GRANTED;
    }
}
// Korrigiert: Content Provider mit ordnungsgemäßen Zugriffskontrollen
public class FixedUserDataProvider extends ContentProvider {

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {

        // Korrigiert: Leseberechtigung verifizieren
        if (getContext().checkCallingPermission(
                "com.example.app.permission.READ_DATA")
                != PackageManager.PERMISSION_GRANTED) {

            throw new SecurityException("Leseberechtigung erforderlich");
        }

        // Korrigiert: URI validieren und sanitisieren
        int match = uriMatcher.match(uri);
        switch (match) {
            case USERS:
                return queryUsers(projection, selection, selectionArgs, sortOrder);
            case USER_ID:
                return queryUser(uri.getLastPathSegment());
            default:
                throw new IllegalArgumentException("Unbekannte URI: " + uri);
        }
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // Korrigiert: Schreibberechtigung verifizieren
        if (getContext().checkCallingPermission(
                "com.example.app.permission.WRITE_DATA")
                != PackageManager.PERMISSION_GRANTED) {

            throw new SecurityException("Schreibberechtigung erforderlich");
        }

        // Korrigiert: Eingabedaten validieren
        validateContentValues(values);

        // Mit Einfügen fortfahren
        return doInsert(uri, values);
    }

    private void validateContentValues(ContentValues values) {
        // Sicherstellen, dass nur erlaubte Felder gesetzt werden
        Set<String> allowedFields = new HashSet<>(Arrays.asList(
            "name", "email", "phone"
        ));

        for (String key : values.keySet()) {
            if (!allowedFields.contains(key)) {
                throw new IllegalArgumentException("Ungültiges Feld: " + key);
            }
        }
    }
}

Verwandte CWEs

  • CWE-285: Improper Authorization (Eltern)
  • CWE-925: Improper Verification of Intent by Broadcast Receiver (verwandt)
  • CWE-927: Use of Implicit Intent for Sensitive Communication (verwandt)

Referenzen

  1. MITRE Corporation. "CWE-926: Improper Export of Android Application Components." https://cwe.mitre.org/data/definitions/926.html
  2. Android Developers. "App security best practices." https://developer.android.com/topic/security/best-practices
  3. OWASP. "Mobile Top 10." https://owasp.org/www-project-mobile-top-10/