OWASP-Kategorie:MASVS-PLATFORM: Platform Interaction
Übersicht
Gemäß der Dokumentation ist ContentResolver eine Klasse, die Anwendungen Zugriff auf das Inhaltsmodell bietet. ContentResolver stellen Methoden zum Interagieren, Abrufen oder Ändern von Inhalten bereit, die von Folgendem bereitgestellt werden:
- Installierte Apps (
content://-URI-Schema) - Dateisysteme (
file://-URI-Schema) - Unterstützung von APIs, die von Android bereitgestellt werden (
android.resource://-URI-Schema).
Zusammenfassend lässt sich sagen, dass Sicherheitslücken im Zusammenhang mit ContentResolver zur Klasse Confused Deputy gehören, da der Angreifer die Berechtigungen einer anfälligen Anwendung verwenden kann, um auf geschützte Inhalte zuzugreifen.
Risiko: Missbrauch aufgrund eines nicht vertrauenswürdigen file://-URI
Der Missbrauch von ContentResolver über die file://-URI-Sicherheitslücke nutzt die Fähigkeit von ContentResolver, die durch den URI beschriebenen Dateideskriptoren zurückzugeben. Diese Sicherheitslücke betrifft Funktionen wie openFile(), openFileDescriptor(), openInputStream(), openOutputStream() oder openAssetFileDescriptor() aus der ContentResolver API. Die Sicherheitslücke kann mit einer vollständig oder teilweise vom Angreifer kontrollierten file://-URI ausgenutzt werden, um die Anwendung zum Zugriff auf Dateien zu zwingen, die nicht zugänglich sein sollten, z. B. interne Datenbanken oder gemeinsame Einstellungen.
Ein mögliches Angriffsszenario wäre, eine schädliche Galerie oder Dateiauswahl zu erstellen, die bei Verwendung durch eine anfällige App einen schädlichen URI zurückgibt.
Es gibt verschiedene Varianten dieses Angriffs:
- Vollständig vom Angreifer kontrollierter
file://-URI, der auf interne Dateien einer App verweist - Ein Teil des
file://-URI wird vom Angreifer kontrolliert, wodurch er anfällig für Path Traversal ist. file://URI, der auf einen von einem Angreifer kontrollierten symbolischen Link (Symlink) verweist, der auf die internen Dateien der App verweist- Ähnlich wie bei der vorherigen Variante tauscht der Angreifer hier das Symlink-Ziel wiederholt von einem legitimen Ziel zu den internen Dateien einer App. Ziel ist es, eine Race-Bedingung zwischen einer potenziellen Sicherheitsprüfung und der Verwendung des Dateipfads auszunutzen.
Auswirkungen
Die Auswirkungen der Ausnutzung dieser Sicherheitslücke variieren je nachdem, wofür der ContentResolver verwendet wird. In vielen Fällen kann dies dazu führen, dass die geschützten Daten einer App exfiltriert oder von Unbefugten geändert werden.
Gegenmaßnahmen
Um diese Sicherheitslücke zu schließen, verwenden Sie den folgenden Algorithmus, um den Dateideskriptor zu validieren. Nach der Validierung kann der Dateideskriptor sicher verwendet werden.
Kotlin
fun isValidFile(ctx: Context, pfd: ParcelFileDescriptor, fileUri: Uri): Boolean {
// Canonicalize to resolve symlinks and path traversals.
val fdCanonical = File(fileUri.path!!).canonicalPath
val pfdStat: StructStat = Os.fstat(pfd.fileDescriptor)
// Lstat doesn't follow the symlink.
val canonicalFileStat: StructStat = Os.lstat(fdCanonical)
// Since we canonicalized (followed the links) the path already,
// the path shouldn't point to symlink unless it was changed in the
// meantime.
if (OsConstants.S_ISLNK(canonicalFileStat.st_mode)) {
return false
}
val sameFile =
pfdStat.st_dev == canonicalFileStat.st_dev &&
pfdStat.st_ino == canonicalFileStat.st_ino
if (!sameFile) {
return false
}
return !isBlockedPath(ctx, fdCanonical)
}
fun isBlockedPath(ctx: Context, fdCanonical: String): Boolean {
// Paths that should rarely be exposed
if (fdCanonical.startsWith("/proc/") ||
fdCanonical.startsWith("/data/misc/")) {
return true
}
// Implement logic to block desired directories. For example, specify
// the entire app data/ directory to block all access.
}
Java
boolean isValidFile(Context ctx, ParcelFileDescriptor pfd, Uri fileUri) {
// Canonicalize to resolve symlinks and path traversals
String fdCanonical = new File(fileUri.getPath()).getCanonicalPath();
StructStat pfdStat = Os.fstat(pfd.getFileDescriptor());
// Lstat doesn't follow the symlink.
StructStat canonicalFileStat = Os.lstat(fdCanonical);
// Since we canonicalized (followed the links) the path already,
// the path shouldn't point to symlink unless it was changed in the meantime
if (OsConstants.S_ISLNK(canonicalFileStat.st_mode)) {
return false;
}
boolean sameFile =
pfdStat.stDev == canonicalFileStat.stDev && pfdStat.stIno == canonicalFileStat.stIno;
if (!sameFile) {
return false;
}
return !isBlockedPath(ctx, fdCanonical);
}
boolean isBlockedPath(Context ctx, String fdCanonical) {
// Paths that should rarely be exposed
if (fdCanonical.startsWith("/proc/") || fdCanonical.startsWith("/data/misc/")) {
return true;
}
// Implement logic to block desired directories. For example, specify
// the entire app data/ directory to block all access.
}
Risiko: Missbrauch basierend auf nicht vertrauenswürdigem content://-URI
Der Missbrauch einer ContentResolver über eine content://-URI-Sicherheitslücke tritt auf, wenn eine vollständig oder teilweise von Angreifern kontrollierte URI an ContentResolver-APIs übergeben wird, um auf Inhalte zuzugreifen, die nicht zugänglich sein sollten.
Für diesen Angriff gibt es zwei Hauptszenarien:
- Die App verwendet eigene, interne Inhalte. Beispiel: Nachdem die E‑Mail-App einen URI von einem Angreifer erhalten hat, hängt sie Daten von ihrem eigenen internen Inhaltsanbieter an, anstatt ein externes Foto.
- Die App fungiert als Proxy und greift dann für den Angreifer auf die Daten einer anderen Anwendung zu. Beispiel: Die E‑Mail-Anwendung hängt Daten aus App X an, die durch eine Berechtigung geschützt sind, die es dem Angreifer normalerweise nicht erlauben würde, diesen Anhang zu sehen. Sie ist für die Anwendung verfügbar, die die Anlage vornimmt, aber nicht sofort, sodass diese Inhalte an den Angreifer weitergeleitet werden.
Ein mögliches Angriffsszenario besteht darin, eine schädliche Galerie oder Dateiauswahl zu erstellen, die bei Verwendung durch eine anfällige App einen schädlichen URI zurückgibt.
Auswirkungen
Die Auswirkungen der Ausnutzung dieser Sicherheitslücke variieren je nach Kontext, der mit dem ContentResolver verknüpft ist. Dies kann dazu führen, dass die geschützten Daten einer App exfiltriert oder von Unbefugten geändert werden.
Gegenmaßnahmen
Allgemein
Eingehende URIs validieren. Es empfiehlt sich beispielsweise, eine Zulassungsliste mit erwarteten Behörden zu verwenden.
URI zielt auf einen nicht exportierten oder berechtigungsgeschützten Contentanbieter ab, der zu einer anfälligen App gehört
Prüfen Sie, ob der URI auf Ihre App ausgerichtet ist:
Kotlin
fun belongsToCurrentApplication(ctx: Context, uri: Uri): Boolean {
val authority: String = uri.authority.toString()
val info: ProviderInfo =
ctx.packageManager.resolveContentProvider(authority, 0)!!
return ctx.packageName.equals(info.packageName)
}
Java
boolean belongsToCurrentApplication(Context ctx, Uri uri){
String authority = uri.getAuthority();
ProviderInfo info = ctx.getPackageManager().resolveContentProvider(authority, 0);
return ctx.getPackageName().equals(info.packageName);
}
Oder wenn der Zielanbieter exportiert wird:
Kotlin
fun isExported(ctx: Context, uri: Uri): Boolean {
val authority = uri.authority.toString()
val info: ProviderInfo =
ctx.packageManager.resolveContentProvider(authority, 0)!!
return info.exported
}
Java
boolean isExported(Context ctx, Uri uri){
String authority = uri.getAuthority();
ProviderInfo info = ctx.getPackageManager().resolveContentProvider(authority, 0);
return info.exported;
}
Oder wenn die explizite Berechtigung für den URI erteilt wurde. Diese Prüfung basiert auf der Annahme, dass der URI nicht schädlich ist, wenn die explizite Berechtigung für den Zugriff auf die Daten erteilt wurde:
Kotlin
// grantFlag is one of: FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION
fun wasGrantedPermission(ctx: Context, uri: Uri?, grantFlag: Int): Boolean {
val pid: Int = Process.myPid()
val uid: Int = Process.myUid()
return ctx.checkUriPermission(uri, pid, uid, grantFlag) ==
PackageManager.PERMISSION_GRANTED
}
Java
// grantFlag is one of: FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION
boolean wasGrantedPermission(Context ctx, Uri uri, int grantFlag){
int pid = Process.myPid();
int uid = Process.myUid();
return ctx.checkUriPermission(uri, pid, uid, grantFlag) == PackageManager.PERMISSION_GRANTED;
}
Der URI verweist auf einen berechtigungsgeschützten ContentProvider, der zu einer anderen App gehört, die der anfälligen App vertraut.
Diese Art von Angriff ist in den folgenden Situationen relevant:
- Ökosysteme von Anwendungen, in denen Apps benutzerdefinierte Berechtigungen oder andere Authentifizierungsmechanismen definieren und verwenden.
- Berechtigungs-Proxy-Angriffe, bei denen ein Angreifer eine anfällige App mit einer Laufzeitberechtigung wie READ_CONTACTS missbraucht, um Daten von einem Systemanbieter abzurufen.
Prüfen Sie, ob die URI-Berechtigung erteilt wurde:
Kotlin
// grantFlag is one of: FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION
fun wasGrantedPermission(ctx: Context, uri: Uri?, grantFlag: Int): Boolean {
val pid: Int = Process.myPid()
val uid: Int = Process.myUid()
return ctx.checkUriPermission(uri, pid, uid, grantFlag) ==
PackageManager.PERMISSION_GRANTED
}
Java
// grantFlag is one of: FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION
boolean wasGrantedPermission(Context ctx, Uri uri, int grantFlag){
int pid = Process.myPid();
int uid = Process.myUid();
return ctx.checkUriPermission(uri, pid, uid, grantFlag) == PackageManager.PERMISSION_GRANTED;
}
Wenn für die Nutzung anderer Contentanbieter keine Berechtigung erforderlich ist, z. B. wenn die App allen Apps im Ökosystem den Zugriff auf alle Daten erlaubt, müssen Sie die Nutzung dieser Berechtigungen explizit verbieten.