@@ -34,11 +34,16 @@ public class GXCompressor implements IGXCompressor {
34
34
private static final String FILE_NOT_EXISTS = "File does not exist: " ;
35
35
private static final String UNSUPPORTED_FORMAT = " is an unsupported format. Supported formats are zip, 7z, tar, gz and jar." ;
36
36
private static final String EMPTY_FILE = "The selected file is empty: " ;
37
+ private static final String PURGED_ARCHIVE = "After performing security checks, no valid files where left to compress" ;
37
38
private static final String DIRECTORY_ATTACK = "Potential directory traversal attack detected: " ;
38
39
private static final String MAX_FILESIZE_EXCEEDED = "The file(s) selected for (de)compression exceed the maximum permitted file size of " ;
39
40
private static final String TOO_MANY_FILES = "Too many files have been added for (de)compression. Maximum allowed is " ;
40
41
private static final String ZIP_SLIP_DETECTED = "Zip slip or path traversal attack detected in archive: " ;
42
+ private static final String BIG_SINGLE_FILE = "Individual file exceeds maximum allowed size: " ;
43
+ private static final String PROCESSING_ERROR = "Error checking archive safety for file: " ;
44
+ private static final String ARCHIVE_SIZE_ESTIMATION_ERROR = "" ;
41
45
private static final int MAX_FILES_ALLOWED = 1000 ;
46
+ private static final long MAX_DECOMPRESSED_SIZE = 1024 * 1024 * 100 ; // 100MB limit
42
47
43
48
private static void storageMessages (String error , GXBaseCollection <SdtMessages_Message > messages ) {
44
49
try {
@@ -51,7 +56,7 @@ private static void storageMessages(String error, GXBaseCollection<SdtMessages_M
51
56
log .error ("Failed to store the following error message: {}" , error , e );
52
57
}
53
58
}
54
-
59
+
55
60
public static Boolean compress (ArrayList <String > files , String path , long maxCombinedFileSize , GXBaseCollection <SdtMessages_Message >[] messages ) {
56
61
if (files .isEmpty ()){
57
62
log .error (NO_FILES_ADDED );
@@ -76,25 +81,52 @@ public static Boolean compress(ArrayList<String> files, String path, long maxCom
76
81
storageMessages (FILE_NOT_EXISTS + filePath , messages [0 ]);
77
82
continue ;
78
83
}
79
- if (!normalizedPath .equals (file .getAbsolutePath ())) {
84
+
85
+ // More permissive directory traversal check
86
+ // Check if the canonical path points to a parent directory of the file
87
+ // or if it points to a completely different location
88
+ File absFile = file .getAbsoluteFile ();
89
+ if (!normalizedPath .startsWith (absFile .getParentFile ().getCanonicalPath ())) {
80
90
log .error (DIRECTORY_ATTACK + "{}" , filePath );
81
91
storageMessages (DIRECTORY_ATTACK + filePath , messages [0 ]);
82
92
return false ;
83
93
}
94
+
84
95
long fileSize = file .length ();
96
+
97
+ if (maxCombinedFileSize > -1 && fileSize > maxCombinedFileSize ) {
98
+ log .error (BIG_SINGLE_FILE + filePath );
99
+ storageMessages (BIG_SINGLE_FILE + filePath , messages [0 ]);
100
+ files .clear ();
101
+ return false ;
102
+ }
103
+
85
104
totalSize += fileSize ;
86
105
if (maxCombinedFileSize > -1 && totalSize > maxCombinedFileSize ) {
87
106
log .error (MAX_FILESIZE_EXCEEDED + maxCombinedFileSize );
88
107
storageMessages (MAX_FILESIZE_EXCEEDED + maxCombinedFileSize , messages [0 ]);
89
- toCompress = null ;
90
108
files .clear ();
91
109
return false ;
92
110
}
93
111
toCompress [index ++] = file ;
94
112
} catch (IOException e ) {
95
113
log .error ("Error normalizing path for file: {}" , filePath , e );
114
+ return false ;
96
115
}
97
116
}
117
+
118
+ if (index < toCompress .length ) {
119
+ File [] resized = new File [index ];
120
+ System .arraycopy (toCompress , 0 , resized , 0 , index );
121
+ toCompress = resized ;
122
+ }
123
+
124
+ if (toCompress .length == 0 ) {
125
+ log .error (PURGED_ARCHIVE );
126
+ storageMessages (PURGED_ARCHIVE , messages [0 ]);
127
+ return false ;
128
+ }
129
+
98
130
String format = CommonUtil .getFileType (path ).toLowerCase ();
99
131
try {
100
132
switch (format .toLowerCase ()) {
@@ -150,8 +182,8 @@ public static Boolean decompress(String file, String path, GXBaseCollection<SdtM
150
182
return false ;
151
183
}
152
184
} 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 ]);
185
+ log .error (PROCESSING_ERROR + file , e );
186
+ storageMessages (PROCESSING_ERROR + file , messages [0 ]);
155
187
return false ;
156
188
}
157
189
try {
@@ -161,10 +193,28 @@ public static Boolean decompress(String file, String path, GXBaseCollection<SdtM
161
193
return false ;
162
194
}
163
195
} 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 ]);
196
+ log .error (PROCESSING_ERROR + file , e );
197
+ storageMessages (PROCESSING_ERROR + file , messages [0 ]);
166
198
return false ;
167
199
}
200
+
201
+ try {
202
+ long totalSizeEstimate = CompressionUtils .estimateDecompressedSize (toCompress );
203
+
204
+ if (totalSizeEstimate > MAX_DECOMPRESSED_SIZE ) {
205
+ log .error ("Potential zip bomb detected. Estimated size: " + totalSizeEstimate + " bytes" );
206
+ storageMessages ("Potential zip bomb detected. Operation aborted for security reasons." , messages [0 ]);
207
+ if (!toCompress .delete ()) {
208
+ log .error ("Failed to delete suspicious archive: {}" , file );
209
+ }
210
+ return false ;
211
+ }
212
+ } catch (Exception e ) {
213
+ log .error (ARCHIVE_SIZE_ESTIMATION_ERROR + file , e );
214
+ storageMessages (ARCHIVE_SIZE_ESTIMATION_ERROR + file , messages [0 ]);
215
+ return false ;
216
+ }
217
+
168
218
String extension = getExtension (toCompress .getName ());
169
219
try {
170
220
switch (extension .toLowerCase ()) {
0 commit comments