OWASP カテゴリ: MASVS-CODE: コード品質
概要
ContentProvider のサブクラスである FileProvider は、あるアプリケーション(サーバー アプリケーション)が別のアプリケーション(クライアント アプリケーション)とファイルを共有するための安全な方法を提供します。ただし、サーバー アプリケーションが指定したファイル名をクライアント アプリケーションが適切に処理しない場合、攻撃者によって制御されるサーバー アプリケーションが不正な FileProvider を実装して、クライアント アプリケーションのアプリ固有のストレージにあるファイルを上書きできる可能性があります。
影響
攻撃者がアプリケーションのファイルを上書きできる場合、アプリケーションのコードを上書きして悪意のあるコードを実行したり、アプリケーションの共有設定やその他の構成ファイルを上書きしてアプリケーションの動作を変更したりできるおそれがあります。
リスクの軽減
ユーザーの入力を信頼しない
ファイル システムの呼び出しを使用する場合は、受信したファイルをストレージに書き込むときに一意のファイル名を生成するようにして、なるべくユーザー入力なしで処理します。
つまり、クライアント アプリケーションが受信したファイルをストレージに書き込む際に、サーバー アプリケーションから指定されたファイル名を無視して、代わりに内部で生成した一意の識別子をファイル名として使用します。
この例は、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;
}
指定されたファイル名をサニタイズする
受信したファイルをストレージに書き込むときに、指定されたファイル名をサニタイズします。
この緩和策は、考えられるすべてのケースを処理することは難しい可能性があるため、前述の緩和策ほど望ましくはありません。それでも、ユニークなファイル名を生成することが現実的でない場合は、指定されたファイル名をクライアント アプリケーションでサニタイズします。サニタイズには以下が含まれます。
- ファイル名に含まれるパス トラバーサル文字をサニタイズする
- 正規化を実行してパス トラバーサルがないことを確認する
次のサンプルコードは、ファイル情報の取得に関するガイダンスに基づいています。
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
}
執筆者: Microsoft Threat Intelligence、Dimitrios Valsamaras 氏および Michael Peck 氏
参考資料
- Dirty Stream Attack: Turning Android Share Targets Into Attack Vectors
- ファイルの共有
- 共有ファイルのリクエスト
- ファイル情報の取得
- FileProvider
- パス トラバーサル
- CWE-73 External Control of Filename or Path