Skip to content

Commit 56c1f3a

Browse files
authored
fix: zip cross directory attack vulnerability (opentiny#312)
1 parent b7bf798 commit 56c1f3a

2 files changed

Lines changed: 1318 additions & 16 deletions

File tree

base/src/main/java/com/tinyengine/it/common/utils/Utils.java

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ public class Utils {
7474
*/
7575
// 泛型去重方法
7676
public static <T> List<T> removeDuplicates(List<T> list) {
77+
if(list == null) {
78+
return new ArrayList<>();
79+
}
7780
// 使用 Set 去重
7881
Set<T> set = new LinkedHashSet<>(list);
7982
// 返回去重后的 List
@@ -191,7 +194,7 @@ public static List<FileInfo> unzip(MultipartFile multipartFile) throws IOExcepti
191194
* @return File the File
192195
* @throws IOException IOException
193196
*/
194-
private static File createTempDirectory() throws IOException {
197+
static File createTempDirectory() throws IOException {
195198
return Files.createTempDirectory("unzip").toFile();
196199
}
197200

@@ -202,7 +205,7 @@ private static File createTempDirectory() throws IOException {
202205
* @return File the File
203206
* @throws IOException IOException
204207
*/
205-
private static File convertMultipartFileToFile(MultipartFile multipartFile) throws IOException {
208+
static File convertMultipartFileToFile(MultipartFile multipartFile) throws IOException {
206209
File tempFile = File.createTempFile("temp", null);
207210
tempFile.deleteOnExit();
208211
try (FileOutputStream fos = new FileOutputStream(tempFile)) {
@@ -219,18 +222,46 @@ private static File convertMultipartFileToFile(MultipartFile multipartFile) thro
219222
* @return List<FileInfo> the List<FileInfo>
220223
* @throws IOException IOException
221224
*/
222-
private static List<FileInfo> processZipEntries(ZipInputStream zis, File tempDir) throws IOException {
225+
static List<FileInfo> processZipEntries(ZipInputStream zis, File tempDir) throws IOException {
223226
List<FileInfo> fileInfoList = new ArrayList<>();
224227
ZipEntry zipEntry;
228+
// 将 tempDir 转为规范路径(例如解析符号链接、父目录等)
229+
Path safeDir = tempDir.toPath().toRealPath();
230+
log.info("Created temporary directory at: {}, real path: {}", tempDir.getAbsolutePath(), safeDir);
225231

226232
while ((zipEntry = zis.getNextEntry()) != null) {
227-
File newFile = new File(tempDir, zipEntry.getName());
233+
// 获取 ZIP 条目中的路径(可能包含 ../ 或绝对路径)
234+
String entryName = zipEntry.getName();
235+
236+
// 拼接并规范化路径
237+
Path targetPath = safeDir.resolve(entryName).normalize();
238+
239+
log.info("Processing ZIP entry: {}, target path: {}", entryName, targetPath);
240+
241+
// 关键校验:确保目标路径仍在 safeDir 之下
242+
if (!targetPath.startsWith(safeDir)) {
243+
throw new SecurityException("检测到跨目录攻击: " + entryName);
244+
}
228245

229246
if (zipEntry.isDirectory()) {
230-
fileInfoList.add(new FileInfo(newFile.getName(), "", true)); // 添加目录
247+
// 创建目录(同时确保父目录存在)
248+
Files.createDirectories(targetPath);
249+
// 存储目录信息(使用最后一级名称,保持与原行为一致)
250+
String dirName = targetPath.getFileName().toString();
251+
fileInfoList.add(new FileInfo(dirName, "", true));
231252
} else {
232-
extractFile(zis, newFile); // 解压文件
233-
fileInfoList.add(new FileInfo(newFile.getName(), readFileContent(newFile), false)); // 添加文件内容
253+
// 确保父目录存在
254+
Path parent = targetPath.getParent();
255+
if (parent != null) {
256+
Files.createDirectories(parent);
257+
}
258+
// 解压文件到目标路径(使用已验证的 targetPath)
259+
extractFile(zis, targetPath.toFile());
260+
// 读取文件内容(同样使用已验证的路径)
261+
String content = readFileContent(targetPath.toFile());
262+
// 存储文件信息(使用最后一级文件名)
263+
String fileName = targetPath.getFileName().toString();
264+
fileInfoList.add(new FileInfo(fileName, content, false));
234265
}
235266
zis.closeEntry();
236267
}
@@ -273,7 +304,7 @@ public static String readFileContent(File file) {
273304
}
274305

275306
// 清理临时文件和目录
276-
private static void cleanUp(File zipFile, File tempDir) {
307+
static void cleanUp(File zipFile, File tempDir) {
277308
// 删除临时的 zip 文件
278309
if (zipFile.exists()) {
279310
if (!zipFile.delete()) {

0 commit comments

Comments
 (0)