Skip to content

Commit 820c145

Browse files
authored
Merge pull request #13553 from am0o0/amammad-go-bombs
Go: Decompression Bombs
2 parents e7852f5 + 43df6a2 commit 820c145

File tree

26 files changed

+2508
-0
lines changed

26 files changed

+2508
-0
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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 to denial of service attacks.</p>
7+
<p>Attackers can compress a huge file which created by repeated similiar byte and convert it to a small compressed file.</p>
8+
9+
</overview>
10+
<recommendation>
11+
12+
<p>When you want to decompress a user-provided compressed file you must be careful about the decompression ratio or read these files within a loop byte by byte to be able to manage the decompressed size in each cycle of the loop. Also you can limit the size of reader buffer.</p>
13+
14+
</recommendation>
15+
<example>
16+
<p>
17+
Using "io.LimitReader" and "io.CopyN" are the best option to prevent decompression bomb attacks.
18+
</p>
19+
<sample src="example_good.go"/>
20+
21+
<sample src="example_good_2.go" />
22+
</example>
23+
<references>
24+
25+
<li>
26+
<a href="https://github.com/russellhaering/gosaml2/security/advisories/GHSA-6gc3-crp7-25w5">CVE-2023-26483 </a>
27+
</li>
28+
<li>
29+
<a href="https://www.bamsoftware.com/hacks/zipbomb/">A great research to gain more impact by this kind of attacks</a>
30+
</li>
31+
32+
</references>
33+
</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 Uncontrolled data that flows into decompression library APIs without checking the compression rate is dangerous
4+
* @kind path-problem
5+
* @problem.severity error
6+
* @security-severity 7.8
7+
* @precision high
8+
* @id go/uncontrolled-file-decompression
9+
* @tags security
10+
* experimental
11+
* external/cwe/cwe-409
12+
*/
13+
14+
import go
15+
import experimental.frameworks.DecompressionBombs
16+
import DecompressionBomb::Flow::PathGraph
17+
18+
from DecompressionBomb::Flow::PathNode source, DecompressionBomb::Flow::PathNode sink
19+
where DecompressionBomb::Flow::flowPath(source, sink)
20+
select sink.getNode(), source, sink, "This decompression is $@.", source.getNode(),
21+
"decompressing compressed data without managing output size"
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package main
2+
3+
import (
4+
"archive/zip"
5+
"fmt"
6+
"io"
7+
"os"
8+
)
9+
10+
func ZipOpenReader(filename string) {
11+
// Open the zip file
12+
r, _ := zip.OpenReader(filename)
13+
var totalBytes int64
14+
for _, f := range r.File {
15+
rc, _ := f.Open()
16+
totalBytes = 0
17+
for {
18+
result, _ := io.CopyN(os.Stdout, rc, 68)
19+
if result == 0 {
20+
break
21+
}
22+
totalBytes = totalBytes + result
23+
if totalBytes > 1024*1024 {
24+
fmt.Print(totalBytes)
25+
_ = rc.Close()
26+
break
27+
}
28+
}
29+
}
30+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package main
2+
3+
import (
4+
"compress/gzip"
5+
"io"
6+
"os"
7+
)
8+
9+
func safeReader() {
10+
var src io.Reader
11+
src, _ = os.Open("filename")
12+
gzipR, _ := gzip.NewReader(src)
13+
dstF, _ := os.OpenFile("./test", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
14+
defer dstF.Close()
15+
var newSrc io.Reader
16+
newSrc = io.LimitReader(gzipR, 1024*1024*1024*5)
17+
_, _ = io.Copy(dstF, newSrc)
18+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* Provides a taint tracking configuration for reasoning about decompression bomb vulnerabilities.
3+
*/
4+
5+
import go
6+
7+
class MimeMultipartFileHeader extends UntrustedFlowSource::Range {
8+
MimeMultipartFileHeader() {
9+
exists(DataFlow::FieldReadNode frn | this = frn |
10+
frn.getField().hasQualifiedName("mime/multipart", "FileHeader", ["Filename", "Header"])
11+
)
12+
or
13+
exists(DataFlow::Method m |
14+
m.hasQualifiedName("mime/multipart", "FileHeader", "Open") and
15+
this = m.getACall().getResult(0)
16+
)
17+
or
18+
exists(DataFlow::FieldReadNode frn |
19+
frn.getField().hasQualifiedName("mime/multipart", "Form", "Value")
20+
)
21+
}
22+
}
23+
24+
/** Provides a taint tracking configuration for reasoning about decompression bomb vulnerabilities. */
25+
module DecompressionBomb {
26+
import experimental.frameworks.DecompressionBombsCustomizations
27+
28+
module Config implements DataFlow::StateConfigSig {
29+
class FlowState = DecompressionBombs::FlowState;
30+
31+
predicate isSource(DataFlow::Node source, FlowState state) {
32+
source instanceof UntrustedFlowSource and
33+
state = ""
34+
}
35+
36+
predicate isSink(DataFlow::Node sink, FlowState state) {
37+
sink instanceof DecompressionBombs::Sink and
38+
state =
39+
[
40+
"ZstdNewReader", "XzNewReader", "GzipNewReader", "PgzipNewReader", "S2NewReader",
41+
"SnappyNewReader", "ZlibNewReader", "FlateNewReader", "Bzip2NewReader", "ZipOpenReader",
42+
"ZipKlauspost"
43+
]
44+
}
45+
46+
predicate isAdditionalFlowStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
47+
exists(DecompressionBombs::AdditionalTaintStep addStep |
48+
addStep.isAdditionalFlowStep(fromNode, toNode)
49+
)
50+
}
51+
52+
predicate isAdditionalFlowStep(
53+
DataFlow::Node fromNode, FlowState fromState, DataFlow::Node toNode, FlowState toState
54+
) {
55+
exists(DecompressionBombs::AdditionalTaintStep addStep |
56+
addStep.isAdditionalFlowStep(fromNode, fromState, toNode, toState)
57+
)
58+
}
59+
}
60+
61+
/** Tracks taint flow for reasoning about decompression bomb vulnerabilities. */
62+
module Flow = TaintTracking::GlobalWithState<Config>;
63+
}

0 commit comments

Comments
 (0)