หมวดหมู่ OWASP: MASVS-STORAGE: Storage
ภาพรวม
ช่องโหว่ Zip Path Traversal หรือที่เรียกว่า ZipSlip เกี่ยวข้องกับการจัดการไฟล์เก็บถาวรที่บีบอัด ในหน้านี้ เราจะสาธิตช่องโหว่นี้โดยใช้รูปแบบ ZIP เป็นตัวอย่าง แต่ปัญหาที่คล้ายกันอาจเกิดขึ้นในไลบรารีที่จัดการรูปแบบอื่นๆ เช่น TAR, RAR หรือ 7z
สาเหตุพื้นฐานของปัญหานี้คือภายในไฟล์ ZIP ที่เก็บถาวร ระบบจะจัดเก็บไฟล์ที่แพ็กแต่ละไฟล์พร้อมชื่อที่สมบูรณ์ ซึ่งอนุญาตให้ใช้สัญลักษณ์พิเศษ เช่น เครื่องหมายทับและจุด ไลบรารีเริ่มต้นจากแพ็กเกจ java.util.zip
ไม่ได้ตรวจสอบชื่อของรายการที่เก็บถาวรสำหรับอักขระการข้ามไดเรกทอรี (../
) ดังนั้นจึงต้องใช้ความระมัดระวังเป็นพิเศษเมื่อต่อชื่อที่ดึงมาจากที่เก็บถาวรกับเส้นทางไดเรกทอรีเป้าหมาย
การตรวจสอบข้อมูลโค้ดหรือไลบรารีการแยก ZIP จากแหล่งที่มาภายนอกเป็นสิ่งสำคัญอย่างยิ่ง ไลบรารีจำนวนมากดังกล่าวมีช่องโหว่ที่ทำให้เกิดการข้ามเส้นทางของไฟล์ ZIP
ผลกระทบ
ช่องโหว่ Zip Path Traversal สามารถใช้เพื่อเขียนทับไฟล์ที่กำหนดเองได้ ผลกระทบอาจแตกต่างกันไปตามเงื่อนไข แต่ในหลายกรณี ช่องโหว่นี้อาจนำไปสู่ปัญหาด้านความปลอดภัยที่สำคัญ เช่น การเรียกใช้โค้ด
การลดปัญหา
หากต้องการลดปัญหานี้ คุณควรตรวจสอบเสมอว่าเส้นทางเป้าหมายเป็นโฟลเดอร์ย่อยของไดเรกทอรีปลายทางก่อนที่จะแตกไฟล์แต่ละรายการ โค้ดด้านล่างนี้ถือว่าไดเรกทอรีปลายทางปลอดภัย ซึ่งแอปของคุณเขียนได้เท่านั้นและไม่ได้อยู่ภายใต้การควบคุมของผู้โจมตี ไม่เช่นนั้นแอปของคุณอาจเสี่ยงต่อช่องโหว่อื่นๆ เช่น การโจมตีด้วย Symlink
Kotlin
companion object {
@Throws(IOException::class)
fun newFile(targetPath: File, zipEntry: ZipEntry): File {
val name: String = zipEntry.name
val f = File(targetPath, name)
val canonicalPath = f.canonicalPath
if (!canonicalPath.startsWith(
targetPath.canonicalPath + File.separator)) {
throw ZipException("Illegal name: $name")
}
return f
}
}
Java
public static File newFile(File targetPath, ZipEntry zipEntry) throws IOException {
String name = zipEntry.getName();
File f = new File(targetPath, name);
String canonicalPath = f.getCanonicalPath();
if (!canonicalPath.startsWith(targetPath.getCanonicalPath() + File.separator)) {
throw new ZipException("Illegal name: " + name);
}
return f;
}
นอกจากนี้ คุณควรตรวจสอบว่าไดเรกทอรีปลายทางว่างเปล่าก่อนเริ่มกระบวนการแยกเพื่อหลีกเลี่ยงการเขียนทับไฟล์ที่มีอยู่โดยไม่ตั้งใจ มิฉะนั้น คุณอาจเสี่ยงต่อการเกิดข้อขัดข้องของแอป หรือในกรณีที่ร้ายแรงที่สุด แอปพลิเคชันอาจถูกบุกรุก
Kotlin
@Throws(IOException::class)
fun unzip(inputStream: InputStream?, destinationDir: File) {
if (!destinationDir.isDirectory) {
throw IOException("Destination is not a directory.")
}
val files = destinationDir.list()
if (files != null && files.isNotEmpty()) {
throw IOException("Destination directory is not empty.")
}
ZipInputStream(inputStream).use { zipInputStream ->
var zipEntry: ZipEntry
while (zipInputStream.nextEntry.also { zipEntry = it } != null) {
val targetFile = File(destinationDir, zipEntry.name)
// ...
}
}
}
Java
void unzip(final InputStream inputStream, File destinationDir)
throws IOException {
if(!destinationDir.isDirectory()) {
throw IOException("Destination is not a directory.");
}
String[] files = destinationDir.list();
if(files != null && files.length != 0) {
throw IOException("Destination directory is not empty.");
}
try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
ZipEntry zipEntry;
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
final File targetFile = new File(destinationDir, zipEntry);
…
}
}
}
แหล่งข้อมูล
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- การข้ามผ่านเส้นทาง