Skip to content

Commit 7d41386

Browse files
zhou9584Le Zhou
andauthored
[Feature] Scan zip file before uploading to avoid ZipBomb attack (#700)
<!-- Please provide brief information about the PR, what it contains & its purpose, new behaviors after the change. And let us know here if you need any help: https://github.com/microsoft/HydraLab/issues/new --> ## Description <!-- A few words to explain your changes --> ### Linked GitHub issue ID: # ## Pull Request Checklist <!-- Put an x in the boxes that apply. This is simply a reminder of what we are going to look for before merging your code. --> - [ ] Tests for the changes have been added (for bug fixes / features) - [x] Code compiles correctly with all tests are passed. - [x] I've read the [contributing guide](https://github.com/microsoft/HydraLab/blob/main/CONTRIBUTING.md#making-changes-to-the-code) and followed the recommended practices. - [ ] [Wikis](https://github.com/microsoft/HydraLab/wiki) or [README](https://github.com/microsoft/HydraLab/blob/main/README.md) have been reviewed and added / updated if needed (for bug fixes / features) ### Does this introduce a breaking change? *If this introduces a breaking change for Hydra Lab users, please describe the impact and migration path.* - [ ] Yes - [ ] No ## How you tested it *Please make sure the change is tested, you can test it by adding UTs, do local test and share the screenshots, etc.* Please check the type of change your PR introduces: - [ ] Bugfix - [x] Feature - [ ] Technical design - [ ] Build related changes - [ ] Refactoring (no functional changes, no api changes) - [ ] Code style update (formatting, renaming) or Documentation content changes - [ ] Other (please describe): ### Feature UI screenshots or Technical design diagrams *If this is a relatively large or complex change, kick it off by drawing the tech design with PlantUML and explaining why you chose the solution you did and what alternatives you considered, etc...* --------- Co-authored-by: Le Zhou <[email protected]>
1 parent e09faf3 commit 7d41386

File tree

2 files changed

+80
-0
lines changed

2 files changed

+80
-0
lines changed

common/src/main/java/com/microsoft/hydralab/common/util/PkgUtil.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ public static JSONObject analysisFile(File file, EntityType entityType) {
4444
} else if (file.getName().endsWith(FILE_SUFFIX.IPA_FILE)) {
4545
res = analysisIpaFile(file);
4646
} else if (file.getName().endsWith(FILE_SUFFIX.ZIP_FILE)) {
47+
// check for zip bomb attacks
48+
if (ZipBombChecker.isZipBomb(file)) {
49+
throw new HydraLabRuntimeException("Zip file is too large, possible zip bomb attack.");
50+
}
4751
res = analysisZipFile(file);
4852
}
4953

@@ -112,11 +116,16 @@ private static JSONObject analysisIpaFile(File ipa) {
112116
try {
113117
String name, pkgName, version;
114118
File zipFile = convertToZipFile(ipa, FILE_SUFFIX.IPA_FILE);
119+
if (ZipBombChecker.isZipBomb(zipFile)) {
120+
throw new HydraLabRuntimeException("Zip file is too large, possible zip bomb attack.");
121+
}
115122
Assert.notNull(zipFile, "Convert .ipa file to .zip file failed.");
116123
File file = getPlistFromZip(zipFile, zipFile.getParent());
117124
//Need third-party jar package dd-plist
118125
Assert.notNull(file, "Analysis .ipa file failed.");
119126
analysisPlist(file, res);
127+
} catch (HydraLabRuntimeException ex) {
128+
throw ex;
120129
} catch (Exception e) {
121130
e.printStackTrace();
122131
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
package com.microsoft.hydralab.common.util;
5+
6+
import java.io.ByteArrayOutputStream;
7+
import java.io.File;
8+
import java.io.FileInputStream;
9+
import java.io.FileOutputStream;
10+
import java.util.zip.ZipEntry;
11+
import java.util.zip.ZipInputStream;
12+
13+
public class ZipBombChecker {
14+
private static final long MAX_UNCOMPRESSED_SIZE = 1024 * 1024 * 1024; // 1024 MB
15+
private static final int MAX_ENTRIES = 10000;
16+
private static final int MAX_NESTING_DEPTH = 5;
17+
18+
public static boolean isZipBomb(File file) {
19+
return isZipBomb(file, 0);
20+
}
21+
22+
private static boolean isZipBomb(File file, int depth) {
23+
if (depth > MAX_NESTING_DEPTH) {
24+
return true;
25+
}
26+
27+
long totalUncompressedSize = 0;
28+
int entryCount = 0;
29+
30+
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(file))) {
31+
ZipEntry entry;
32+
byte[] buffer = new byte[8192];
33+
34+
while ((entry = zis.getNextEntry()) != null) {
35+
entryCount++;
36+
if (entryCount > MAX_ENTRIES) {
37+
return true;
38+
}
39+
40+
if (!entry.isDirectory()) {
41+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
42+
int read;
43+
while ((read = zis.read(buffer)) != -1) {
44+
baos.write(buffer, 0, read);
45+
totalUncompressedSize += read;
46+
if (totalUncompressedSize > MAX_UNCOMPRESSED_SIZE) {
47+
return true;
48+
}
49+
}
50+
// check if the entry is a nested zip file
51+
if (entry.getName().toLowerCase().endsWith(".zip")) {
52+
byte[] nestedZipBytes = baos.toByteArray();
53+
File tempZip = File.createTempFile("nested", ".zip");
54+
try (FileOutputStream fos = new FileOutputStream(tempZip)) {
55+
fos.write(nestedZipBytes);
56+
}
57+
boolean nestedBomb = isZipBomb(tempZip, depth + 1);
58+
tempZip.delete();
59+
if (nestedBomb) {
60+
return true;
61+
}
62+
}
63+
}
64+
zis.closeEntry();
65+
}
66+
} catch (Exception e) {
67+
return true; // If there's an error reading the zip, treat it as a potential zip bomb
68+
}
69+
return false;
70+
}
71+
}

0 commit comments

Comments
 (0)