Kategoria OWASP: MASVS-PLATFORM: Platform Interaction
Przegląd
Zgodnie z dokumentacją ContentResolver to „klasa, która zapewnia aplikacjom dostęp do modelu treści”. Klasy ContentResolver udostępniają metody interakcji, pobierania lub modyfikowania treści pochodzących z tych źródeł:
- Zainstalowane aplikacje (schemat URI
content://) - Systemy plików (schemat URI
file://) - Obsługa interfejsów API udostępnianych przez Androida (schemat URI
android.resource://).
Podsumowując, luki w zabezpieczeniach związane z ContentResolver należą do klasy confused deputy, ponieważ osoba przeprowadzająca atak może wykorzystać uprawnienia podatnej na ataki aplikacji, aby uzyskać dostęp do chronionych treści.
Ryzyko: nadużycie na podstawie niezaufanego identyfikatora URI file://
Nadużycie ContentResolver przy użyciu luki w zabezpieczeniach identyfikatora URI file:// wykorzystuje możliwość zwracania przez ContentResolver deskryptorów plików opisanych przez identyfikator URI. Ta luka w zabezpieczeniach dotyczy funkcji takich jak openFile(), openFileDescriptor(), openInputStream(), openOutputStream() lub openAssetFileDescriptor() z ContentResolver API. Luka w zabezpieczeniach może zostać wykorzystana za pomocą w pełni lub częściowo kontrolowanego przez atakującego file:// identyfikatora URI, aby wymusić na aplikacji dostęp do plików, które nie powinny być dostępne, np. wewnętrznych baz danych lub ustawień udostępnionych.
Jednym z możliwych scenariuszy ataku jest utworzenie złośliwej galerii lub okna wyboru plików, które w przypadku użycia przez podatną na ataki aplikację zwraca złośliwy identyfikator URI.
Istnieje kilka wariantów tego ataku:
- W pełni kontrolowany przez atakującego
file://identyfikator URI, który wskazuje wewnętrzne pliki aplikacji. - Część identyfikatora URI
file://jest kontrolowana przez atakującego, co sprawia, że jest podatna na ataki typu path traversal. file://Identyfikator URI kierujący na kontrolowany przez atakującego link symboliczny (symlink), który wskazuje wewnętrzne pliki aplikacji.- Podobnie jak w przypadku poprzedniego wariantu, ale tutaj atakujący wielokrotnie zamienia cel dowiązania symbolicznego z prawidłowego celu na wewnętrzne pliki aplikacji. Celem jest wykorzystanie sytuacji wyścigu między potencjalnym sprawdzeniem zabezpieczeń a użyciem ścieżki pliku.
Wpływ
Skutki wykorzystania tej luki w zabezpieczeniach zależą od tego, do czego służy ContentResolver. W wielu przypadkach może to prowadzić do wycieku chronionych danych aplikacji lub ich modyfikacji przez nieuprawnione osoby.
Środki ograniczające ryzyko
Aby ograniczyć tę lukę w zabezpieczeniach, użyj poniższego algorytmu do sprawdzenia poprawności deskryptora pliku. Po pomyślnym przejściu weryfikacji deskryptor pliku można bezpiecznie używać.
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.
}
Ryzyko: nadużycie na podstawie identyfikatora URI treści://
Nadużycie ContentResolver za pomocą luki w zabezpieczeniach URI content:// występuje, gdy w interfejsach API ContentResolver używany jest w całości lub częściowo kontrolowany przez osobę przeprowadzającą atak adres URI, aby działać na treściach, które nie miały być dostępne.
W przypadku tego ataku istnieją 2 główne scenariusze:
- Aplikacja działa na podstawie własnych treści wewnętrznych. Na przykład po otrzymaniu identyfikatora URI od osoby atakującej aplikacja poczty e-mail dołącza dane z własnego wewnętrznego dostawcy treści zamiast zewnętrznego zdjęcia.
- Aplikacja działa jako serwer proxy, a następnie uzyskuje dostęp do danych innej aplikacji na potrzeby atakującego. Przykład: aplikacja poczty dołącza dane z aplikacji X, które są chronione uprawnieniami, które normalnie uniemożliwiają atakującemu wyświetlenie tego konkretnego załącznika. Jest on dostępny dla aplikacji, która dołącza plik, ale nie od razu, co umożliwia przekazanie tej treści atakującemu.
Jednym z możliwych scenariuszy ataku jest utworzenie złośliwej galerii lub okna wyboru plików, który w przypadku użycia przez podatną na ataki aplikację zwraca złośliwy identyfikator URI.
Wpływ
Skutki wykorzystania tej luki w zabezpieczeniach zależą od kontekstu powiązanego z klasą ContentResolver. Może to spowodować wyciek chronionych danych aplikacji lub ich modyfikację przez nieuprawnione osoby.
Środki ograniczające ryzyko
Ogólne
Sprawdzanie przychodzących identyfikatorów URI. Za dobrą praktykę uznaje się na przykład korzystanie z listy dozwolonych oczekiwanych organów.
Identyfikator URI kieruje na dostawcę treści, który nie jest eksportowany lub jest chroniony uprawnieniami i należy do aplikacji podatnej na ataki
Sprawdź, czy identyfikator URI jest kierowany na Twoją aplikację:
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);
}
Lub jeśli wyeksportowano dostawcę docelowego:
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;
}
lub jeśli użytkownik przyznał wyraźne uprawnienia do identyfikatora URI – to sprawdzenie opiera się na założeniu, że jeśli użytkownik przyznał wyraźne uprawnienia do dostępu do danych, identyfikator URI nie jest złośliwy:
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;
}
Identyfikator URI jest kierowany na chronionego uprawnieniami dostawcę treści należącego do innej aplikacji, która ufa aplikacji podatnej na ataki.
Ten atak jest istotny w tych sytuacjach:
- Ekosystemy aplikacji, w których aplikacje definiują i używają uprawnień niestandardowych lub innych mechanizmów uwierzytelniania.
- Ataki z użyciem serwera proxy uprawnień, w których atakujący wykorzystuje podatną na ataki aplikację z uprawnieniami w czasie działania (aplikacji), np. READ_CONTACTS, do pobierania danych od dostawcy systemowego.
Sprawdź, czy uprawnienia do identyfikatora URI zostały przyznane:
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;
}
Jeśli korzystanie z innych dostawców treści nie wymaga przyznania uprawnień (np. gdy aplikacja zezwala wszystkim aplikacjom z ekosystemu na dostęp do wszystkich danych), wyraźnie zabroń korzystania z tych uprawnień.