ContentProvider tarafından sağlanan dosya adına yanlış güvenme

OWASP kategorisi: MASVS-CODE: Code Quality (MASVS-CODE: Kod Kalitesi)

Genel Bakış

ContentProvider'ın bir alt sınıfı olan FileProvider, bir uygulamanın ("sunucu uygulaması") başka bir uygulamayla dosya paylaşması ("istemci uygulaması") için güvenli bir yöntem sağlamayı amaçlar. Ancak istemci uygulaması, sunucu uygulaması tarafından sağlanan dosya adını düzgün şekilde işlemezse saldırgan tarafından kontrol edilen bir sunucu uygulaması, istemci uygulamasının uygulamaya özel depolama alanındaki dosyaların üzerine yazmak için kendi kötü amaçlı FileProvider'ını uygulayabilir.

Etki

Bir saldırganın uygulamanın dosyalarının üzerine yazabilmesi, kötü amaçlı kod yürütülmesine (uygulamanın kodunun üzerine yazarak) veya uygulamanın davranışının başka şekilde değiştirilmesine (örneğin, uygulamanın paylaşılan tercihlerinin veya diğer yapılandırma dosyalarının üzerine yazarak) yol açabilir.

Risk azaltma önlemleri

Kullanıcı Girişine Güvenmeyin

Alınan dosyayı depolama alanına yazarken benzersiz bir dosya adı oluşturarak dosya sistemi çağrılarını kullanırken kullanıcı girişi olmadan çalışmayı tercih edin.

Diğer bir deyişle: İstemci uygulaması, alınan dosyayı depolama alanına yazarken sunucu uygulaması tarafından sağlanan dosya adını yoksaymalı ve bunun yerine dosya adı olarak kendi içinde oluşturulan benzersiz tanımlayıcıyı kullanmalıdır.

Bu örnek, https://developer.android.com/training/secure-file-sharing/request-file adresindeki kod üzerine kurulmuştur:

Kotlin

// Code in
// https://developer.android.com/training/secure-file-sharing/request-file#OpenFile
// used to obtain file descriptor (fd)

try {
    val inputStream = FileInputStream(fd)
    val tempFile = File.createTempFile("temp", null, cacheDir)
    val outputStream = FileOutputStream(tempFile)
    val buf = ByteArray(1024)
    var len: Int
    len = inputStream.read(buf)
    while (len > 0) {
        if (len != -1) {
            outputStream.write(buf, 0, len)
            len = inputStream.read(buf)
        }
    }
    inputStream.close()
    outputStream.close()
} catch (e: IOException) {
    e.printStackTrace()
    Log.e("MainActivity", "File copy error.")
    return
}

Java

// Code in
// https://developer.android.com/training/secure-file-sharing/request-file#OpenFile
// used to obtain file descriptor (fd)

FileInputStream inputStream = new FileInputStream(fd);

// Create a temporary file
File tempFile = File.createTempFile("temp", null, getCacheDir());

// Copy the contents of the file to the temporary file
try {
    OutputStream outputStream = new FileOutputStream(tempFile))
    byte[] buffer = new byte[1024];
    int length;
    while ((length = inputStream.read(buffer)) > 0) {
        outputStream.write(buffer, 0, length);
    }
} catch (IOException e) {
    e.printStackTrace();
    Log.e("MainActivity", "File copy error.");
    return;
}

Sağlanan Dosya Adlarını Temizleme

Alınan dosyayı depolama alanına yazarken sağlanan dosya adını temizleyin.

Bu azaltma, tüm olası durumları ele almak zor olabileceğinden önceki azaltma kadar tercih edilmez. Bununla birlikte: Benzersiz bir dosya adı oluşturmak pratik değilse istemci uygulaması, sağlanan dosya adını temizlemelidir. Temizleme şunları içerir:

  • Dosya adındaki yol geçişi karakterlerini temizleme
  • Yol geçişi olmadığını doğrulamak için kanonikleştirme gerçekleştirme

Bu örnek kod, dosya bilgilerini alma ile ilgili kılavuzda yer alan bilgilerden yararlanır:

Kotlin

protected fun sanitizeFilename(displayName: String): String {
    val badCharacters = arrayOf("..", "/")
    val segments = displayName.split("/")
    var fileName = segments[segments.size - 1]
    for (suspString in badCharacters) {
        fileName = fileName.replace(suspString, "_")
    }
    return fileName
}

val displayName = returnCursor.getString(nameIndex)
val fileName = sanitizeFilename(displayName)
val filePath = File(context.filesDir, fileName).path

// saferOpenFile defined in Android developer documentation
val outputFile = saferOpenFile(filePath, context.filesDir.canonicalPath)

// fd obtained using Requesting a shared file from Android developer
// documentation

val inputStream = FileInputStream(fd)

// Copy the contents of the file to the new file
try {
    val outputStream = FileOutputStream(outputFile)
    val buffer = ByteArray(1024)
    var length: Int
    while (inputStream.read(buffer).also { length = it } > 0) {
        outputStream.write(buffer, 0, length)
    }
} catch (e: IOException) {
    // Handle exception
}

Java

protected String sanitizeFilename(String displayName) {
    String[] badCharacters = new String[] { "..", "/" };
    String[] segments = displayName.split("/");
    String fileName = segments[segments.length - 1];
    for (String suspString : badCharacters) {
        fileName = fileName.replace(suspString, "_");
    }
    return fileName;
}

String displayName = returnCursor.getString(nameIndex);
String fileName = sanitizeFilename(displayName);
String filePath = new File(context.getFilesDir(), fileName).getPath();

// saferOpenFile defined in Android developer documentation

File outputFile = saferOpenFile(filePath,
    context.getFilesDir().getCanonicalPath());

// fd obtained using Requesting a shared file from Android developer
// documentation

FileInputStream inputStream = new FileInputStream(fd);

// Copy the contents of the file to the new file
try {
    OutputStream outputStream = new FileOutputStream(outputFile))
    byte[] buffer = new byte[1024];
    int length;
    while ((length = inputStream.read(buffer)) > 0) {
        outputStream.write(buffer, 0, length);
    }
} catch (IOException e) {
    // Handle exception
}

Katkıda bulunanlar: Microsoft Threat Intelligence'dan Dimitrios Valsamaras ve Michael Peck

Kaynaklar