Kategori OWASP: MASVS-STORAGE: Penyimpanan
Ringkasan
Kerentanan Zip Path Traversal, yang juga dikenal sebagai ZipSlip, berkaitan dengan penanganan arsip terkompresi. Di halaman ini, kami mendemonstrasikan kerentanan ini menggunakan format ZIP sebagai contoh, tetapi masalah serupa dapat muncul di library yang menangani format lain, seperti TAR, RAR, atau 7z.
Alasan yang mendasari masalah ini adalah bahwa di dalam arsip ZIP, setiap file yang dipaketkan disimpan dengan nama yang sepenuhnya memenuhi syarat, sehingga memungkinkan karakter khusus seperti garis miring dan titik. Library default dari paket java.util.zip
tidak memeriksa nama entri arsip untuk karakter traversal direktori (../
) sehingga Anda harus sangat berhati-hati saat menggabungkan nama yang diekstrak dari arsip dengan jalur direktori yang ditargetkan.
Sangat penting untuk memvalidasi library atau cuplikan kode pengekstrak ZIP apa pun dari sumber eksternal. Ada banyak library seperti ini yang rentan terhadap Zip Path Traversal.
Dampak
Kerentanan Zip Path Traversal dapat digunakan untuk mencapai penimpaan file arbitrer. Bergantung pada kondisinya, dampaknya dapat bervariasi, tetapi dalam banyak kasus, kerentanan ini dapat menyebabkan masalah keamanan besar seperti eksekusi kode.
Mitigasi
Untuk mengurangi masalah ini, sebelum mengekstrak setiap entri, Anda harus selalu memastikan bahwa jalur target adalah turunan dari direktori tujuan. Kode di bawah ini menganggap direktori tujuan aman, hanya dapat ditulis oleh aplikasi Anda dan tidak berada di bawah kontrol penyerang. Jika tidak, aplikasi Anda bisa menjadi rentan terhadap kerentanan lainnya, seperti serangan 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;
}
Agar tidak menimpa file yang ada secara tidak sengaja, Anda juga harus memastikan direktori tujuan kosong sebelum memulai proses ekstraksi. Jika tidak, aplikasi Anda berisiko mengalami error, atau pada kasus ekstrem, aplikasi akan disusupi.
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);
…
}
}
}
Referensi
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Path traversal