หมวดหมู่ OWASP: MASVS-CODE: คุณภาพของโค้ด
ภาพรวม
FileProvider ซึ่งเป็นคลาสย่อยของ ContentProvider มีไว้เพื่อ จัดหาวิธีที่ปลอดภัยสำหรับแอปพลิเคชัน ("แอปพลิเคชันเซิร์ฟเวอร์") ในการแชร์ ไฟล์กับแอปพลิเคชันอื่น ("แอปพลิเคชันไคลเอ็นต์") อย่างไรก็ตาม หาก แอปพลิเคชันไคลเอ็นต์จัดการชื่อไฟล์ที่แอปพลิเคชันเซิร์ฟเวอร์ระบุอย่างไม่ถูกต้อง แอปพลิเคชันเซิร์ฟเวอร์ที่ผู้โจมตีควบคุมอาจสามารถใช้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;
}
ล้างข้อมูลชื่อไฟล์ที่ระบุ
ล้างข้อมูลชื่อไฟล์ที่ระบุเมื่อเขียนไฟล์ที่ได้รับไปยังที่เก็บข้อมูล
การลดความเสี่ยงนี้ไม่เป็นที่ต้องการเท่าการลดความเสี่ยงก่อนหน้าเนื่องจากอาจ จัดการกรณีที่อาจเกิดขึ้นทั้งหมดได้ยาก อย่างไรก็ตาม หากการสร้างชื่อไฟล์ที่ไม่ซ้ำกันไม่สะดวก แอปพลิเคชันไคลเอ็นต์ควรล้างชื่อไฟล์ที่ระบุ การล้างข้อมูลรวมถึง
- การล้างอักขระของ Path Traversal ในชื่อไฟล์
- ดำเนินการ Canonicalization เพื่อยืนยันว่าไม่มีการข้ามเส้นทาง
โค้ดตัวอย่างนี้สร้างขึ้นตามคำแนะนำเกี่ยวกับการดึงข้อมูลไฟล์
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
}
ผู้ร่วมให้ข้อมูล: Dimitrios Valsamaras และ Michael Peck จาก Microsoft Threat Intelligence
แหล่งข้อมูล
- การโจมตีแบบ Dirty Stream: การเปลี่ยนเป้าหมายการแชร์ของ Android ให้เป็นเวกเตอร์การโจมตี
- การแชร์ไฟล์อย่างปลอดภัย
- เอกสารประกอบการขอไฟล์ที่แชร์
- ดึงข้อมูล
- FileProvider
- Path Traversal
- CWE-73 การควบคุมชื่อไฟล์หรือเส้นทางจากภายนอก