Skip to content

Commit 08b3cc2

Browse files
tomas-sexenianBeta Bot
authored andcommitted
Cherry pick branch 'genexuslabs:Compress' into beta
1 parent 75315f8 commit 08b3cc2

File tree

2 files changed

+146
-7
lines changed

2 files changed

+146
-7
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package com.genexus.compression;
2+
3+
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
4+
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
5+
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
6+
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
7+
8+
import java.io.File;
9+
import java.io.FileInputStream;
10+
import java.io.IOException;
11+
import java.util.Enumeration;
12+
import java.util.zip.ZipEntry;
13+
import java.util.zip.ZipFile;
14+
15+
import static org.apache.commons.io.FileUtils.getFile;
16+
import static org.apache.commons.io.FilenameUtils.getExtension;
17+
18+
public class CompressionUtils {
19+
public static int countArchiveEntries(File toCompress) throws IOException {
20+
String ext = getExtension(toCompress.getName()).toLowerCase();
21+
switch (ext) {
22+
case "zip":
23+
case "jar": {
24+
int count = 0;
25+
try (ZipFile zip = new ZipFile(toCompress)) {
26+
Enumeration<? extends ZipEntry> entries = zip.entries();
27+
while (entries.hasMoreElements()) {
28+
entries.nextElement();
29+
count++;
30+
}
31+
}
32+
return count;
33+
}
34+
case "tar": {
35+
int count = 0;
36+
try (FileInputStream fis = new FileInputStream(toCompress);
37+
TarArchiveInputStream tais = new TarArchiveInputStream(fis)) {
38+
while (tais.getNextEntry() != null) {
39+
count++;
40+
}
41+
}
42+
return count;
43+
}
44+
case "7z": {
45+
int count = 0;
46+
try (SevenZFile sevenZFile = getSevenZFile(toCompress.getAbsolutePath())) {
47+
while (sevenZFile.getNextEntry() != null) {
48+
count++;
49+
}
50+
}
51+
return count;
52+
}
53+
case "gz":
54+
return 1;
55+
}
56+
return 0;
57+
}
58+
public static boolean isArchiveSafe(File toCompress, String targetDir) throws IOException {
59+
String ext = getExtension(toCompress.getName()).toLowerCase();
60+
File target = new File(targetDir).getCanonicalFile();
61+
switch (ext) {
62+
case "zip":
63+
case "jar":
64+
try (ZipFile zip = new ZipFile(toCompress)) {
65+
Enumeration<? extends ZipEntry> entries = zip.entries();
66+
while (entries.hasMoreElements()) {
67+
ZipEntry entry = entries.nextElement();
68+
File entryFile = new File(target, entry.getName());
69+
if (!entryFile.getCanonicalPath().startsWith(target.getCanonicalPath() + File.separator)) {
70+
return false;
71+
}
72+
}
73+
}
74+
return true;
75+
case "tar":
76+
try (FileInputStream fis = new FileInputStream(toCompress);
77+
TarArchiveInputStream tais = new TarArchiveInputStream(fis)) {
78+
TarArchiveEntry entry;
79+
while ((entry = tais.getNextEntry()) != null) {
80+
File entryFile = new File(target, entry.getName());
81+
if (!entryFile.getCanonicalPath().startsWith(target.getCanonicalPath() + File.separator)) {
82+
return false;
83+
}
84+
}
85+
}
86+
return true;
87+
case "7z":
88+
try (SevenZFile sevenZFile = getSevenZFile(toCompress.getAbsolutePath())) {
89+
SevenZArchiveEntry entry;
90+
while ((entry = sevenZFile.getNextEntry()) != null) {
91+
File entryFile = new File(target, entry.getName());
92+
if (!entryFile.getCanonicalPath().startsWith(target.getCanonicalPath() + File.separator)) {
93+
return false;
94+
}
95+
}
96+
}
97+
return true;
98+
case "gz":
99+
return true;
100+
}
101+
return true;
102+
}
103+
104+
private static SevenZFile getSevenZFile(final String specialPath) throws IOException {
105+
return SevenZFile.builder().setFile(getFile(specialPath)).get();
106+
}
107+
}

gxcompress/src/main/java/com/genexus/compression/GXCompressor.java

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ public class GXCompressor implements IGXCompressor {
3535
private static final String UNSUPPORTED_FORMAT = " is an unsupported format. Supported formats are zip, 7z, tar, gz and jar.";
3636
private static final String EMPTY_FILE = "The selected file is empty: ";
3737
private static final String DIRECTORY_ATTACK = "Potential directory traversal attack detected: ";
38-
private static final String MAX_FILESIZE_EXCEEDED = "The files selected for compression exceed the maximum permitted file size of ";
38+
private static final String MAX_FILESIZE_EXCEEDED = "The file(s) selected for (de)compression exceed the maximum permitted file size of ";
39+
private static final String TOO_MANY_FILES = "Too many files have been added for (de)compression. Maximum allowed is ";
40+
private static final String ZIP_SLIP_DETECTED = "Zip slip or path traversal attack detected in archive: ";
41+
private static final int MAX_FILES_ALLOWED = 1000;
3942

4043
private static void storageMessages(String error, GXBaseCollection<SdtMessages_Message> messages) {
4144
try {
@@ -55,6 +58,12 @@ public static Boolean compress(ArrayList<String> files, String path, long maxCom
5558
storageMessages(NO_FILES_ADDED, messages[0]);
5659
return false;
5760
}
61+
if(maxCombinedFileSize > -1 && files.size() > MAX_FILES_ALLOWED){
62+
log.error(TOO_MANY_FILES + MAX_FILES_ALLOWED);
63+
storageMessages(TOO_MANY_FILES + MAX_FILES_ALLOWED, messages[0]);
64+
files.clear();
65+
return false;
66+
}
5867
long totalSize = 0;
5968
File[] toCompress = new File[files.size()];
6069
int index = 0;
@@ -67,18 +76,18 @@ public static Boolean compress(ArrayList<String> files, String path, long maxCom
6776
storageMessages(FILE_NOT_EXISTS + filePath, messages[0]);
6877
continue;
6978
}
70-
if (normalizedPath.contains(File.separator + ".." + File.separator) ||
71-
normalizedPath.endsWith(File.separator + "..") ||
72-
normalizedPath.startsWith(".." + File.separator)) {
79+
if (!normalizedPath.equals(file.getAbsolutePath())) {
7380
log.error(DIRECTORY_ATTACK + "{}", filePath);
7481
storageMessages(DIRECTORY_ATTACK + filePath, messages[0]);
7582
return false;
7683
}
7784
long fileSize = file.length();
7885
totalSize += fileSize;
79-
if (totalSize > maxCombinedFileSize && maxCombinedFileSize > -1) {
80-
log.error(MAX_FILESIZE_EXCEEDED + "{}", maxCombinedFileSize);
86+
if (maxCombinedFileSize > -1 && totalSize > maxCombinedFileSize) {
87+
log.error(MAX_FILESIZE_EXCEEDED + maxCombinedFileSize);
8188
storageMessages(MAX_FILESIZE_EXCEEDED + maxCombinedFileSize, messages[0]);
89+
toCompress = null;
90+
files.clear();
8291
return false;
8392
}
8493
toCompress[index++] = file;
@@ -133,6 +142,29 @@ public static Boolean decompress(String file, String path, GXBaseCollection<SdtM
133142
storageMessages(EMPTY_FILE + file, messages[0]);
134143
return false;
135144
}
145+
try {
146+
int fileCount = CompressionUtils.countArchiveEntries(toCompress);
147+
if (fileCount > MAX_FILES_ALLOWED) {
148+
log.error(TOO_MANY_FILES + fileCount);
149+
storageMessages(TOO_MANY_FILES + fileCount, messages[0]);
150+
return false;
151+
}
152+
} catch (Exception e) {
153+
log.error("Error counting archive entries for file: {}", file, e);
154+
storageMessages("Error counting archive entries for file: " + file, messages[0]);
155+
return false;
156+
}
157+
try {
158+
if (!CompressionUtils.isArchiveSafe(toCompress, path)) {
159+
log.error(ZIP_SLIP_DETECTED + file);
160+
storageMessages(ZIP_SLIP_DETECTED + file, messages[0]);
161+
return false;
162+
}
163+
} catch (Exception e) {
164+
log.error("Error checking archive safety for file: {}", file, e);
165+
storageMessages("Error checking archive safety for file: " + file, messages[0]);
166+
return false;
167+
}
136168
String extension = getExtension(toCompress.getName());
137169
try {
138170
switch (extension.toLowerCase()) {
@@ -636,4 +668,4 @@ private static void decompressJar(File archive, String directory) throws IOExcep
636668
}
637669
}
638670
}
639-
}
671+
}

0 commit comments

Comments
 (0)