zip パス トラバーサル
コレクションでコンテンツを整理
必要に応じて、コンテンツの保存と分類を行います。
OWASP カテゴリ: MASVS-STORAGE: ストレージ
概要
zip パス トラバーサルの脆弱性(ZipSlip とも呼ばれます)は、圧縮されたアーカイブの処理に関連します。ここでは、ZIP 形式を例としてこの脆弱性を説明しますが、TAR、RAR、7z などの他の形式を扱うライブラリでも同様の問題が発生する可能性があります。
この問題の背景にある原因は、ZIP アーカイブ内にパックされた各ファイルが完全修飾名で保存されているため、スラッシュやドットなどの特殊文字を使用できることです。java.util.zip
パッケージのデフォルト ライブラリは、アーカイブ エントリの名前にディレクトリ トラバーサル文字(../
)があるかどうかをチェックしないため、ターゲットのディレクトリ パスにアーカイブから抽出した名前を連結するときには特別な注意が必要です。
外部ソースから取得した ZIP からの抽出を行うコード スニペットやライブラリを検証することが非常に重要です。このようなライブラリの多くは、zip パス トラバーサルに対して脆弱です。
影響
zip パス トラバーサルの脆弱性を使用すると、任意のファイルを上書きできます。影響は状況によって異なりますが、多くの場合、コードの実行など、セキュリティ上の重大な問題につながる可能性があります。
リスクの軽減
この問題を軽減するには、各エントリを抽出する前に、ターゲットパスが宛先ディレクトリの子であることを常に確認する必要があります。以下のコードは、宛先ディレクトリが安全である(デベロッパーのアプリのみが書き込み可能で、攻撃者の管理下にはない)ことを前提としています。そうでない場合、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 がオフになっている場合はリンクテキストが表示されます
- パス トラバーサル
このページのコンテンツやコードサンプルは、コンテンツ ライセンスに記載のライセンスに従います。Java および OpenJDK は Oracle および関連会社の商標または登録商標です。
最終更新日 2024-02-23 UTC。
[null,null,["最終更新日 2024-02-23 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)"]]