Rozwiązania do rozpoznawania treści

Kategoria OWASP: MASVS-PLATFORM: Platform Interaction

Omówienie

Zgodnie z dokumentacją ContentResolver to „klasa, która zapewnia aplikacjom dostęp do modelu treści”. ContentResolver udostępnia metody umożliwiające interakcję z treścią, jej pobieranie i modyfikowanie. Dostęp do treści zapewniają:

  • Zainstalowane aplikacje (schemat URI content://)
  • Systemy plików (schemat URI file://)
  • Obsługa interfejsów API zgodnie z Androidem (schemat identyfikatora URI android.resource://).

Podsumowując, luki związane z ContentResolver należą do klasy zdezorientowanego zastępcy, ponieważ atakujący może używać uprawnień podatnej aplikacji do uzyskiwania dostępu do chronionych treści.

Zagrożenie: nadużycia związane z niezaufanymi identyfikatorami URI typu file://

Nadużycie ContentResolver przy użyciu luki w zabezpieczeniach w identyfikatorze URI file:// wykorzystuje możliwość ContentResolver do zwracania opisów plików opisanych przez identyfikator URI. Ta luka w zabezpieczeniach dotyczy funkcji openFile(), openFileDescriptor(), openInputStream(), openOutputStream() lub openAssetFileDescriptor() z interfejsu ContentResolver API. W przypadku tej podatności można użyć identyfikatora URI file://, który jest w pełni lub częściowo kontrolowany przez atakującego, aby zmusić aplikację do uzyskania dostępu do plików, które nie powinny być dostępne, takich jak wewnętrzne bazy danych lub udostępnione ustawienia.

Jednym z możliwych scenariuszy ataku jest utworzenie szkodliwej galerii lub selektora plików, który w przypadku użycia przez podatną aplikację zwróci szkodliwy identyfikator URI.

Istnieje kilka wariantów tego ataku:

  • file:// URI kontrolowany przez atakującego i odwołujący się do plików wewnętrznych aplikacji
  • Część identyfikatora URI file:// jest kontrolowana przez atakującego, co powoduje, że jest on podatny na przeszukiwanie ścieżek.
  • file:// URI kierujący na link symboliczny (skrót) kontrolowany przez osobę przeprowadzającą atak, który wskazuje na wewnętrzne pliki aplikacji
  • Podobnie jak w poprzednim wariancie, ale w tym przypadku atakujący wielokrotnie zamienia docelowy skrót symboliczny z uprawnionego miejsca docelowego na wewnętrzne pliki aplikacji. Celem jest wykorzystanie warunku wyścigu między potencjalnym sprawdzeniem zabezpieczeń a używaniem ścieżki pliku.

Wpływ

Skutki wykorzystania tej luki w zabezpieczeniach różnią się w zależności od tego, do czego służy ContentResolver. W wielu przypadkach może to spowodować wydostanie się chronionych danych aplikacji lub ich zmodyfikowanie przez nieupoważnione osoby.

Środki zaradcze

Aby ograniczyć tę lukę w zabezpieczeniach, użyj algorytmu poniżej do sprawdzenia deskryptora pliku. Po pomyślnym przejściu weryfikacji można bezpiecznie używać deskryptora pliku.

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.
}


Zagrożenie: nadużycie polegające na wykorzystaniu nieznanego identyfikatora URI treści://

Nadużycie ContentResolver za pomocą luki w zabezpieczeniach w adresie URI content:// występuje, gdy do interfejsów API ContentResolver przekazywany jest adres URI kontrolowany w pełni lub częściowo przez osobę przeprowadzającą atak, aby działać na treściach, które nie powinny być dostępne.

Istnieją 2 główne scenariusze tego ataku:

  • Aplikacja działa na podstawie własnych, wewnętrznych treści. Na przykład: po otrzymaniu identyfikatora URI od osoby atakującej aplikacja pocztowa zamiast zewnętrznego zdjęcia dołącza dane od własnego wewnętrznego dostawcy treści.
  • Aplikacja działa jako serwer pośredniczący i uzyskiwanie dostępu do danych innej aplikacji w celu ich przekazania atakującemu. Na przykład: aplikacja pocztowa załącza dane z aplikacji X, które są chronione przez uprawnienie, które normalnie uniemożliwiałoby atakującemu wyświetlenie tego konkretnego załącznika. Jest on dostępny dla aplikacji, która przesyła załącznik, ale nie jest początkowo przekazywany atakującemu.

Jednym z możliwych scenariuszy ataku jest utworzenie złośliwej galerii lub selektora plików, który w przypadku użycia przez podatną aplikację zwróci złośliwy identyfikator URI.

Wpływ

Wpływ wykorzystania tej luki w zabezpieczeniach zależy od kontekstu związanego z ContentResolver. Może to spowodować wydostanie się chronionych danych aplikacji lub ich zmodyfikowanie przez osoby nieupoważnione.

Środki zaradcze

Ogólne

weryfikować przychodzące identyfikatory URI; Dobrą praktyką jest na przykład korzystanie z listy dozwolonych instytucji.

Identyfikator URI kieruje na dostawcę treści, który nie został wyeksportowany lub nie jest chroniony uprawnieniami i należy do podatnej na ataki aplikacji

Sprawdź, czy identyfikator URI kieruje 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);
}

Jeśli eksportujesz dane z dostawcy 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;
}

Albo jeśli przyznano wyraźne uprawnienia do identyfikatora URI – ta kontrola opiera się na założeniu, że jeśli przyznano 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 kieruje na chroniony przez uprawnienia komponent ContentProvider należący do innej aplikacji, która ufa podatnej aplikacji.

Ten atak jest istotny w tych sytuacjach:

  • ekosystemy aplikacji, w których aplikacje definiują i korzystają z niestandardowych uprawnień lub innych mechanizmów uwierzytelniania;
  • ataki za pomocą proxy uprawnień, w których atakujący wykorzystuje podatną na ataki aplikację, która ma uprawnienia w czasie działania, np. READ_CONTACTS, aby pobrać dane od dostawcy systemu;

Sprawdź, czy przyznano uprawnienia do identyfikatora URI:

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 usług innych dostawców treści nie wymaga udzielenia uprawnień (np. gdy aplikacja zezwala wszystkim aplikacjom z ekosystemu na dostęp do wszystkich danych), wyraźnie zabroń używania tych uprawnień.