İçerik çözücüler

OWASP kategorisi: MASVS-PLATFORM: Platform Etkileşimi

Genel Bakış

Belgelere göre ContentResolver, "uygulamalara içerik modeline erişim sağlayan bir sınıf"tır. ContentResolver'lar, aşağıdaki kaynaklardan sağlanan içerikle etkileşim kurma, içerik getirme veya içerik değiştirme yöntemlerini gösterir:

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

Özetlemek gerekirse, saldırgan korumalı içeriğe erişmek için güvenlik açığı olan bir uygulamanın ayrıcalıklarını kullanabildiğinden, ContentResolver ile ilgili güvenlik açıkları şaşırmış vekil sınıfına aittir.

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

file:// URI güvenlik açığını kullanarak ContentResolver'ün kötüye kullanılması, ContentResolver'ün URI tarafından açıklanan dosya tanımlayıcılarını döndürme özelliğinden yararlanır. Bu güvenlik açığı, ContentResolver API'deki openFile(), openFileDescriptor(), openInputStream(), openOutputStream() veya openAssetFileDescriptor() gibi işlevleri etkiler. Bu güvenlik açığı, uygulamayı erişimi amaçlanmayan dosyalara (ör. dahili veritabanları veya paylaşılan tercihler) erişmeye zorlamak için saldırgan tarafından tamamen veya kısmen kontrol edilen bir file:// URI ile kötüye kullanılabilir.

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

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

  • Bir uygulamanın dahili dosyalarını işaret eden, tamamen saldırgan tarafından kontrol edilen file:// URI
  • file:// URI'sinin bir kısmı saldırgan tarafından kontrol edildiğinden, yol geçişlerine açıktır
  • file:// Uygulamanın dahili dosyalarına yönlendiren, saldırgan tarafından kontrol edilen bir sembolik bağlantıyı (sembolik bağlantı) hedefleyen URI
  • Önceki varyanta benzer ancak burada saldırgan, sembolik bağlantı hedefini meşru bir hedeften uygulamanın dahili dosyalarıyla tekrar tekrar değiştirir. Amaç, olası bir güvenlik kontrolü ile dosya yolu kullanımı arasındaki yarış koşulundan 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. Bu durum, çoğu durumda bir uygulamanın korunan verilerinin sızdırılmasına veya yetkisiz taraflar tarafından korunan verilerin 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 işlemini geçtikten sonra dosya tanımlayıcısı 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üvenilir olmayan content:// URI'sine dayalı kötüye kullanım

content:// URI güvenlik açığı kullanılarak ContentResolver'ün kötüye kullanılması, tamamen veya kısmen saldırgan tarafından kontrol edilen bir URI'nin, erişilebilir olması amaçlanmayan içerik üzerinde işlem yapması için ContentResolver API'lerine iletilmesiyle gerçekleşir.

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

  • Uygulama, kendi dahili içeriğiyle çalışır. Örneğin: E-posta uygulaması, bir saldırgandan URI aldıktan sonra harici bir fotoğraf yerine kendi dahili içerik sağlayıcısından veri ekler.
  • Uygulama, proxy görevi görür ve ardından saldırgan için başka bir uygulamanın verilerine erişir. Örneğin: Posta uygulaması, X uygulamasından normalde saldırganın söz konusu eki görmesine izin vermeyecek bir izinle korunan verileri ekler. Ekleme yapan uygulama tarafından kullanılabilir ancak başlangıçta bu içerik saldırgana iletilmez.

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

Etki

Bu güvenlik açığından yararlanmanı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 yetkisiz tarafların korunan verilerde değişiklik yapmasına neden olabilir.

Çözümler

Genel

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

URI, güvenlik açığı olan uygulamaya ait dışa aktarılmamış veya izin korumalı 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ıldıysa:

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

Veya URI'ye açık izin verilmişse (bu kontrol, verilere erişmek için açık izin verildiğinde URI'nin kötü amaçlı olmadığı varsayımını temel alı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ımlayıp kullandığı uygulama ekosistemleri.
  • Saldırganın, bir sistem sağlayıcıdan veri almak için READ_CONTACTS gibi bir çalışma zamanında izine sahip olan savunmasız bir uygulamayı kötüye kullandığı izin proxy saldırıları.

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ı izin verilmesini gerektirmiyorsa (ör. uygulama, ekosistemdeki tüm uygulamaların tüm verilere erişmesine izin veriyorsa) bu yetkilerin kullanımını açıkça yasaklayın.