-
Notifications
You must be signed in to change notification settings - Fork 84
fix: zip cross directory attack vulnerability #312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -222,13 +222,34 @@ private static File convertMultipartFileToFile(MultipartFile multipartFile) thro | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static List<FileInfo> processZipEntries(ZipInputStream zis, File tempDir) throws IOException { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<FileInfo> fileInfoList = new ArrayList<>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ZipEntry zipEntry; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 将 tempDir 转为规范路径(例如解析符号链接、父目录等) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Path safeDir = tempDir.toPath().toRealPath(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.info("Created temporary directory at: {}, real path: {}", tempDir.getAbsolutePath(), safeDir); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while ((zipEntry = zis.getNextEntry()) != null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 获取 ZIP 条目中的路径(可能包含 ../ 或绝对路径) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String entryName = zipEntry.getName(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 拼接并规范化路径 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Path targetPath = safeDir.resolve(entryName).normalize(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.info("Processing ZIP entry: {}, target path: {}", entryName, targetPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 关键校验:确保目标路径仍在 safeDir 之下 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!targetPath.startsWith(safeDir)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new SecurityException("检测到跨目录攻击: " + entryName); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| File newFile = new File(tempDir, zipEntry.getName()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (zipEntry.isDirectory()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 创建目录(同时确保父目录存在) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Files.createDirectories(targetPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fileInfoList.add(new FileInfo(newFile.getName(), "", true)); // 添加目录 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 确保父目录存在 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if(targetPath.getParent() != null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Files.createDirectories(targetPath.getParent()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| extractFile(zis, newFile); // 解压文件 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fileInfoList.add(new FileInfo(newFile.getName(), readFileContent(newFile), false)); // 添加文件内容 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
242
to
254
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify path-validation path vs extraction path usage in processZipEntries.
rg -n 'Path targetPath = safeDir\.resolve\(entryName\)\.normalize\(\);|File newFile = new File\(tempDir, zipEntry\.getName\(\)\);|extractFile\(zis, newFile\);|readFileContent\(newFile\)' base/src/main/java/com/tinyengine/it/common/utils/Utils.javaRepository: opentiny/tiny-engine-backend-java Length of output: 392 🏁 Script executed: #!/bin/bash
# Get broader context around the issue area
sed -n '220,260p' base/src/main/java/com/tinyengine/it/common/utils/Utils.javaRepository: opentiny/tiny-engine-backend-java Length of output: 1695 🏁 Script executed: #!/bin/bash
# Find where safeDir is defined/initialized
rg -n 'safeDir\s*=' base/src/main/java/com/tinyengine/it/common/utils/Utils.java -B 2 -A 2Repository: opentiny/tiny-engine-backend-java Length of output: 386 🏁 Script executed: #!/bin/bash
# Find the method signature to understand the full context
rg -n 'void processZipEntries|Path.*processZipEntries|private.*processZipEntries' base/src/main/java/com/tinyengine/it/common/utils/Utils.java -A 15Repository: opentiny/tiny-engine-backend-java Length of output: 878 🏁 Script executed: #!/bin/bash
# Find extractFile and readFileContent method signatures
rg -n 'private.*extractFile\(|void extractFile\(|public.*extractFile\(' base/src/main/java/com/tinyengine/it/common/utils/Utils.java -A 3Repository: opentiny/tiny-engine-backend-java Length of output: 406 🏁 Script executed: #!/bin/bash
# Find readFileContent method
rg -n 'private.*readFileContent\(|String readFileContent\(' base/src/main/java/com/tinyengine/it/common/utils/Utils.java -A 3Repository: opentiny/tiny-engine-backend-java Length of output: 289 Use the validated You validate Suggested patch- File newFile = new File(tempDir, zipEntry.getName());
+ File newFile = targetPath.toFile();
if (zipEntry.isDirectory()) {
// 创建目录(同时确保父目录存在)
Files.createDirectories(targetPath);
fileInfoList.add(new FileInfo(newFile.getName(), "", true)); // 添加目录
} else {
// 确保父目录存在
- Files.createDirectories(targetPath.getParent());
+ Path parent = targetPath.getParent();
+ if (parent != null) {
+ Files.createDirectories(parent);
+ }
extractFile(zis, newFile); // 解压文件
fileInfoList.add(new FileInfo(newFile.getName(), readFileContent(newFile), false)); // 添加文件内容
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
补上测试用例