İçerik çözücüler

OWASP kategorisi: MASVS-PLATFORM: Platform Interaction (Platform Etkileşimi)

Genel Bakış

Belgelere göre ContentResolver, "uygulamaların içerik modeline erişmesini sağlayan bir sınıftır". ContentResolver'lar, aşağıdakilerden sağlanan içeriklerle etkileşim kurmak, içerikleri getirmek veya değiştirmek için yöntemler sunar:

  • Yüklü uygulamalar (content:// URI şeması)
  • Dosya sistemleri (file:// URI şeması)
  • Android tarafından sağlanan API'leri (android.resource:// URI şeması) destekleme.

Özetlemek gerekirse, saldırganın korunan içeriğe erişmek için savunmasız bir uygulamanın ayrıcalıklarını kullanabilmesi nedeniyle ContentResolver ile ilgili güvenlik açıkları confused deputy sınıfına girer.

Risk: Güvenilmeyen file:// URI'sine dayalı kötüye kullanım

file:// URI güvenlik açığı kullanılarak ContentResolver öğesinin kötüye kullanılması, ContentResolver öğesinin URI tarafından açıklanan dosya tanımlayıcılarını döndürme özelliğini kötüye kullanır. Bu güvenlik açığı, ContentResolver API'sindeki openFile(), openFileDescriptor(), openInputStream(), openOutputStream() veya openAssetFileDescriptor() gibi işlevleri etkiler. Bu güvenlik açığı, saldırganın tam veya kısmi olarak kontrol ettiği file:// URI ile kötüye kullanılarak uygulamanın, erişilmesi amaçlanmayan dosyalara (ör. dahili veritabanları veya paylaşılan tercihler) erişmesi sağlanabilir.

Olası saldırı senaryolarından biri, savunmasız bir uygulama tarafından kullanıldığında kötü amaçlı bir URI döndüren kötü amaçlı bir galeri veya dosya seçici oluşturmaktır.

Bu saldırının birkaç varyantı vardır:

  • Saldırganın tamamen kontrol ettiği file:// URI'si (Uygulamanın dahili dosyalarını gösterir)
  • file:// URI'sinin bir kısmı saldırgan tarafından kontrol edildiğinden yol geçişlerine karşı savunmasızdır.
  • file:// Uygulamanın dahili dosyalarına yönlendiren, saldırgan tarafından kontrol edilen sembolik bağlantıyı (symlink) hedefleyen URI
  • Önceki varyanta benzer ancak burada saldırgan, sembolik bağlantı hedefini meşru bir hedeften uygulamanın dahili dosyalarına tekrar tekrar değiştirir. Amaç, olası bir güvenlik kontrolü ile dosya yolu kullanımı arasındaki yarış durumundan yararlanmaktır.

Etki

Bu güvenlik açığından yararlanmanın etkisi, ContentResolver'ın ne için kullanıldığına bağlı olarak değişir. Çoğu durumda, uygulamanın korunan verilerinin sızdırılmasına veya korunan verilerin yetkisiz taraflarca değiştirilmesine neden olabilir.

Çözümler

Bu güvenlik açığının oluşturduğu riskleri azaltmak için dosya tanımlayıcısını doğrulamak üzere aşağıdaki algoritmayı kullanın. Doğrulama başarılı olduktan sonra dosya tanımlayıcı güvenli bir şekilde kullanılabilir.

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


Risk: Güvenilmeyen content:// URI'sine dayalı kötüye kullanım

ContentResolver API'lerinin, erişilmesi amaçlanmayan içerik üzerinde çalışması için saldırgan tarafından tamamen veya kısmen kontrol edilen bir URI'nin ContentResolver API'lerine iletilmesi durumunda content:// URI güvenlik açığı kullanılarak ContentResolver API'leri kötüye kullanılır.

Bu saldırının iki ana senaryosu vardır:

  • Uygulama, kendi dahili içeriğiyle çalışıyor. Örneğin: Saldırgandan URI aldıktan sonra posta uygulaması, harici bir fotoğraf yerine kendi dahili içerik sağlayıcısından gelen verileri ekler.
  • Uygulama, proxy görevi görür ve saldırgan adına başka bir uygulamanın verilerine erişir. Örneğin: Posta uygulaması, saldırganın normalde bu eki görmesini engelleyecek bir izinle korunan X uygulamasındaki verileri ekler. Bu içerik, ekleme işlemini yapan uygulamaya sunulur ancak başlangıçta sunulmadığı için saldırgana iletilmez.

Olası bir saldırı senaryosu, savunmasız bir uygulama tarafından kullanıldığında kötü amaçlı bir URI döndüren kötü amaçlı bir galeri veya dosya seçici oluşturmaktır.

Etki

Bu güvenlik açığının kullanılmasının etkisi, ContentResolver ile ilişkili bağlama bağlı olarak değişir. Bu durum, bir uygulamanın korunan verilerinin sızdırılmasına veya korunan verilerde yetkisiz taraflarca değişiklik yapılmasına neden olabilir.

Çözümler

Genel

Gelen URI'leri doğrulayın. Örneğin, beklenen yetkililerin izin verilenler listesini kullanmak iyi bir uygulama olarak kabul edilir.

URI, savunmasız uygulamaya ait olan, dışa aktarılmamış veya izinle korunan içerik sağlayıcıyı hedefliyor

URI'nin uygulamanızı hedefleyip hedeflemediğini kontrol edin:

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);
}

Hedeflenen sağlayıcı dışa aktarılıyorsa:

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

Alternatif olarak, URI'ye açıkça izin verildiyse (bu kontrol, verilere erişim için açıkça izin verilmesi durumunda URI'nin kötü amaçlı olmadığı varsayımına dayanır):

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

URI, güvenlik açığı olan uygulamaya güvenen başka bir uygulamaya ait, izin korumalı bir ContentProvider'ı hedefliyor.

Bu saldırı aşağıdaki durumlarla ilgilidir:

  • Uygulamaların özel izinleri veya diğer kimlik doğrulama mekanizmalarını tanımladığı ve kullandığı uygulama ekosistemleri.
  • İzin proxy saldırıları: Saldırgan, sistem sağlayıcıdan veri almak için READ_CONTACTS gibi bir çalışma zamanında istenen izin tutan savunmasız bir uygulamayı kötüye kullanır.

URI izninin verilip verilmediğini test edin:

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

Diğer içerik sağlayıcıların kullanımı için izin verilmesi gerekmiyorsa (ör. uygulama, ekosistemdeki tüm uygulamaların tüm verilere erişmesine izin veriyorsa) bu yetkililerin kullanımını açıkça yasaklayın.