Kategoria OWASP: MASVS-CODE: jakość kodu
Omówienie
FileProvider, podklasa ContentProvider, ma na celu zapewnienie bezpiecznej metody udostępniania plików przez aplikację („aplikację serwera”) innej aplikacji („aplikacji klienta”). Jeśli jednak aplikacja kliencka nieprawidłowo obsługuje nazwę pliku podaną przez aplikację serwera, aplikacja serwera kontrolowana przez atakującego może zaimplementować własny złośliwy FileProvider, aby zastąpić pliki w pamięci aplikacji klienckiej.
Wpływ
Jeśli atakujący może nadpisać pliki aplikacji, może to doprowadzić do wykonania złośliwego kodu (przez nadpisanie kodu aplikacji) lub umożliwić modyfikowanie zachowania aplikacji (np. przez nadpisanie preferencji udostępnionych aplikacji lub innych plików konfiguracyjnych).
Środki ograniczające ryzyko
Nie ufaj danym wejściowym użytkownika
Podczas korzystania z wywołań systemu plików preferuj pracę bez interwencji użytkownika, generując unikalną nazwę pliku podczas zapisywania otrzymanego pliku w pamięci.
Innymi słowy: gdy aplikacja kliencka zapisuje otrzymany plik w pamięci, powinna zignorować nazwę pliku podaną przez aplikację serwera i zamiast tego użyć własnego, wygenerowanego wewnętrznie unikalnego identyfikatora jako nazwy pliku.
Ten przykład jest oparty na kodzie dostępnym na stronie https://developer.android.com/training/secure-file-sharing/request-file:
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;
}
Oczyszczanie podanych nazw plików
Podczas zapisywania otrzymanego pliku w pamięci masowej należy oczyścić podaną nazwę pliku.
To rozwiązanie jest mniej pożądane niż poprzednie, ponieważ obsługa wszystkich potencjalnych przypadków może być trudna. Jeśli jednak wygenerowanie unikalnej nazwy pliku nie jest praktyczne, aplikacja kliencka powinna oczyścić podaną nazwę pliku. Sanityzacja obejmuje:
- Usuwanie znaków przemierzania ścieżki w nazwie pliku
- Przeprowadzanie kanonizacji w celu potwierdzenia, że nie ma przechodzenia po ścieżkach
Ten przykładowy kod jest oparty na wskazówkach dotyczących pobierania informacji o pliku:
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
}
Współtwórcy: Dimitrios Valsamaras i Michael Peck z Microsoft Threat Intelligence
Materiały
- Dirty Stream Attack: Turning Android Share Targets Into Attack Vectors
- Bezpieczne udostępnianie plików
- Prośba o dokumentację dotyczącą udostępnionego pliku
- Pobierz informacje
- FileProvider
- Przemierzanie ścieżki
- CWE-73 External Control of Filename or Path