압축 파일 경로 순회
컬렉션을 사용해 정리하기
내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.
OWASP 카테고리: MASVS-STORAGE: 저장소
개요
ZipSlip이라고도 하는 압축 파일 경로 순회 취약점은 압축된 보관 파일을 처리하는 것과 관련이 있습니다. 이 페이지에서는 이 취약점에 관해 ZIP 형식을 예로 들어 설명하나, TAR, RAR, 7z 등의 다른 형식을 처리하는 라이브러리에서도 이와 비슷한 문제가 발생할 수 있습니다.
이 문제가 발생하는 주된 이유는 각 패킹된 파일이 ZIP 파일 내부에 저장될 때 슬래시나 점과 같은 특수문자 사용이 허용되는 정규화된 이름으로 저장되기 때문입니다. java.util.zip
패키지의 기본 라이브러리는 보관 파일 항목의 이름에서 디렉터리 순회 문자(../
)를 검사하지 않으므로 보관 파일에서 추출된 이름을 타겟팅된 디렉터리 경로와 연결할 때 면밀한 주의를 기울여야 합니다.
ZIP 파일을 추출하는 코드 스니펫이나 라이브러리를 외부 소스에서 가져온 경우 유효성을 검사하는 것이 매우 중요합니다. 이러한 라이브러리는 많은 경우 압축 파일 경로 순회에 취약합니다.
영향
압축 파일 경로 순회 취약점은 임의의 파일을 덮어쓰는 데 사용될 수 있습니다. 영향은 조건에 따라 달라질 수 있으나, 많은 경우 이 취약점으로 인해 코드 실행과 같은 심각한 보안 문제가 발생할 수 있습니다.
완화 조치
이 문제를 완화하려면 각 항목을 추출하기 전에 타겟 경로가 대상 디렉터리의 하위 요소인지 항상 확인해야 합니다. 아래의 코드는 대상 디렉터리가 안전하다고(앱에서만 기록할 수 있고 공격자가 제어하지 않고 있음) 가정합니다. 안전하지 않은 경우 앱이 심볼릭 링크 공격과 같은 다른 취약점에 노출되기 쉽습니다.
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
}
}
자바
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가 사용 중지되어 있으면 링크 텍스트가 표시됩니다.
- 경로 순회
이 페이지에 나와 있는 콘텐츠와 코드 샘플에는 콘텐츠 라이선스에서 설명하는 라이선스가 적용됩니다. 자바 및 OpenJDK는 Oracle 및 Oracle 계열사의 상표 또는 등록 상표입니다.
최종 업데이트: 2023-12-08(UTC)
[null,null,["최종 업데이트: 2023-12-08(UTC)"],[],[],null,["# Zip Path Traversal\n\n\u003cbr /\u003e\n\n**OWASP category:** [MASVS-STORAGE: Storage](https://mas.owasp.org/MASVS/05-MASVS-STORAGE)\n\nOverview\n--------\n\nThe Zip Path Traversal vulnerability, also known as ZipSlip, is related to handling compressed archives. On this page, we demonstrate this vulnerability using the ZIP format as an example, but similar problems can arise in libraries handling other formats, like TAR, RAR, or 7z.\n\nThe underlying reason for this problem is that inside ZIP archives, each packed file is stored with a fully qualified name, which allows special characters such as slashes and dots. The default library from the `java.util.zip` package doesn't check the names of the archive entries for directory traversal characters (`../`), so special care must be taken when concatenating the name extracted from the archive with the targeted directory path.\n\nIt's very important to validate any ZIP-extracting code snippets or libraries from external sources. **Many such libraries are vulnerable to Zip Path Traversals.**\n\nImpact\n------\n\nThe Zip Path Traversal vulnerability can be used to achieve arbitrary file overwrite. Depending on conditions, the impact might vary, but in many cases this vulnerability can lead to major security issues such as code execution.\n\nMitigations\n-----------\n\nTo mitigate this issue, before extracting each entry, you should always verify that the target path is a child of the destination directory. The code below assumes that the destination directory is safe -- writable by your app only and not under attacker control -- otherwise your app could be prone to other vulnerabilities such as symlink attacks. \n\n### Kotlin\n\n companion object {\n @Throws(IOException::class)\n fun newFile(targetPath: File, zipEntry: ZipEntry): File {\n val name: String = zipEntry.name\n val f = File(targetPath, name)\n val canonicalPath = f.canonicalPath\n if (!canonicalPath.startsWith(\n targetPath.canonicalPath + File.separator)) {\n throw ZipException(\"Illegal name: $name\")\n }\n return f\n }\n }\n\n### Java\n\n public static File newFile(File targetPath, ZipEntry zipEntry) throws IOException {\n String name = zipEntry.getName();\n File f = new File(targetPath, name);\n String canonicalPath = f.getCanonicalPath();\n if (!canonicalPath.startsWith(targetPath.getCanonicalPath() + File.separator)) {\n throw new ZipException(\"Illegal name: \" + name);\n }\n return f;\n }\n\nTo avoid accidentally overwriting existing files, you should also make sure that the destination directory is empty before starting the extraction process. Otherwise you risk potential app crashes, or in extreme cases, an application compromise. \n\n### Kotlin\n\n @Throws(IOException::class)\n fun unzip(inputStream: InputStream?, destinationDir: File) {\n if (!destinationDir.isDirectory) {\n throw IOException(\"Destination is not a directory.\")\n }\n val files = destinationDir.list()\n if (files != null && files.isNotEmpty()) {\n throw IOException(\"Destination directory is not empty.\")\n }\n ZipInputStream(inputStream).use { zipInputStream -\u003e\n var zipEntry: ZipEntry\n while (zipInputStream.nextEntry.also { zipEntry = it } != null) {\n val targetFile = File(destinationDir, zipEntry.name)\n // ...\n }\n }\n }\n\n### Java\n\n void unzip(final InputStream inputStream, File destinationDir)\n throws IOException {\n if(!destinationDir.isDirectory()) { \n throw IOException(\"Destination is not a directory.\");\n }\n\n String[] files = destinationDir.list();\n if(files != null && files.length != 0) { \n throw IOException(\"Destination directory is not empty.\");\n }\n\n try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {\n ZipEntry zipEntry;\n while ((zipEntry = zipInputStream.getNextEntry()) != null) {\n final File targetFile = new File(destinationDir, zipEntry);\n ...\n }\n }\n }\n\nResources\n---------\n\n- [Zip Slip Vulnerability](https://snyk.io/research/zip-slip-vulnerability)\n\nRecommended for you\n-------------------\n\n- Note: link text is displayed when JavaScript is off\n- [Path traversal](/topic/security/risks/path-traversal)"]]