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
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Bereich: Vertraulichkeit Anwendungsdaten lesen - Exportierte Content Provider oder Activities können sensible Daten für unautorisierte Anwendungen offenlegen. |
| Integrität | Bereich: Integrität Anwendungsdaten modifizieren - Bösartige Apps können Daten über exportierte Content Provider modifizieren oder den Status über exportierte Services beschädigen. |
| Zugriffskontrolle | Bereich: 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
- MITRE Corporation. "CWE-926: Improper Export of Android Application Components." https://cwe.mitre.org/data/definitions/926.html
- Android Developers. "App security best practices." https://developer.android.com/topic/security/best-practices
- OWASP. "Mobile Top 10." https://owasp.org/www-project-mobile-top-10/