Skip to content

Commit 6280ed2

Browse files
authored
Merge pull request #13555 from am0o0/amammad-java-bombs
Java: Decompression Bombs
2 parents 05b0a3f + 40eef25 commit 6280ed2

File tree

82 files changed

+2611
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+2611
-0
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.example;
2+
3+
import java.nio.file.StandardCopyOption;
4+
import java.util.Enumeration;
5+
import java.io.IOException;
6+
import java.util.zip.*;
7+
import java.util.zip.ZipEntry;
8+
import java.io.File;
9+
import java.nio.file.Files;
10+
11+
12+
class BadExample {
13+
public static void ZipInputStreamUnSafe(String filename) throws IOException {
14+
File f = new File(filename);
15+
try (ZipFile zipFile = new ZipFile(f)) {
16+
Enumeration<? extends ZipEntry> entries = zipFile.entries();
17+
18+
while (entries.hasMoreElements()) {
19+
ZipEntry ze = entries.nextElement();
20+
File out = new File("./tmp/tmp.txt");
21+
Files.copy(zipFile.getInputStream(ze), out.toPath(), StandardCopyOption.REPLACE_EXISTING);
22+
}
23+
}
24+
}
25+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>Extracting Compressed files with any compression algorithm like gzip can cause a denial of service attack.</p>
7+
<p>Attackers can create a huge file by just repeating a single byte and compress it to a small file.</p>
8+
9+
</overview>
10+
<recommendation>
11+
12+
<p>When decompressing a user-provided compressed file, verify the decompression ratio or decompress the files within a loop byte by byte to be able to manage the decompressed size in each cycle of the loop.</p>
13+
14+
</recommendation>
15+
<example>
16+
17+
<p>
18+
In the following example, the decompressed file size is not checked before decompression, exposing the application to a denial of service.
19+
</p>
20+
<sample src="BadExample.java" />
21+
22+
<p>
23+
A better approach is shown in the following example, where a ZIP file is read within a loop and a size threshold is checked every cycle.
24+
</p>
25+
<sample src="GoodExample.java"/>
26+
27+
</example>
28+
<references>
29+
30+
<li>
31+
<a href="https://github.com/advisories/GHSA-47vx-fqr5-j2gw">CVE-2022-4565</a>
32+
</li>
33+
<li>
34+
David Fifield: <a href="https://www.bamsoftware.com/hacks/zipbomb/">A better zip bomb</a>.
35+
</li>
36+
37+
</references>
38+
</qhelp>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* @name Uncontrolled file decompression
3+
* @description Decompressing user-controlled files without checking the compression ratio may allow attackers to perform denial-of-service attacks.
4+
* @kind path-problem
5+
* @problem.severity error
6+
* @security-severity 7.8
7+
* @precision high
8+
* @id java/uncontrolled-file-decompression
9+
* @tags security
10+
* experimental
11+
* external/cwe/cwe-409
12+
*/
13+
14+
import java
15+
import experimental.semmle.code.java.security.DecompressionBombQuery
16+
import DecompressionBombsFlow::PathGraph
17+
18+
from DecompressionBombsFlow::PathNode source, DecompressionBombsFlow::PathNode sink
19+
where DecompressionBombsFlow::flowPath(source, sink)
20+
select sink.getNode(), source, sink, "This file extraction depends on a $@.", source.getNode(),
21+
"potentially untrusted source"
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import java.util.zip.*;
2+
import java.io.FileInputStream;
3+
import java.io.FileOutputStream;
4+
import java.util.zip.ZipEntry;
5+
6+
public class GoodExample {
7+
public static void ZipInputStreamSafe(String filename) throws IOException {
8+
int UncompressedSizeThreshold = 10 * 1024 * 1024; // 10MB
9+
int BUFFERSIZE = 256;
10+
FileInputStream fis = new FileInputStream(filename);
11+
try (ZipInputStream zis = new ZipInputStream(new BufferedInputStream(fis))) {
12+
ZipEntry entry;
13+
while ((entry = zis.getNextEntry()) != null) {
14+
int count;
15+
byte[] data = new byte[BUFFERSIZE];
16+
FileOutputStream fos = new FileOutputStream(entry.getName());
17+
BufferedOutputStream dest = new BufferedOutputStream(fos, BUFFERSIZE);
18+
int totalRead = 0;
19+
while ((count = zis.read(data, 0, BUFFERSIZE)) != -1) {
20+
totalRead = totalRead + count;
21+
if (totalRead > UncompressedSizeThreshold) {
22+
System.out.println("This Compressed file can be a bomb!");
23+
break;
24+
}
25+
dest.write(data, 0, count);
26+
}
27+
dest.flush();
28+
dest.close();
29+
zis.closeEntry();
30+
}
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)