Skip to content

Commit

Permalink
Merge pull request #13553 from am0o0/amammad-go-bombs
Browse files Browse the repository at this point in the history
Go: Decompression Bombs
  • Loading branch information
owen-mc authored Mar 10, 2024
2 parents e7852f5 + 43df6a2 commit 820c145
Show file tree
Hide file tree
Showing 26 changed files with 2,508 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Extracting Compressed files with any compression algorithm like gzip can cause to denial of service attacks.</p>
<p>Attackers can compress a huge file which created by repeated similiar byte and convert it to a small compressed file.</p>

</overview>
<recommendation>

<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>

</recommendation>
<example>
<p>
Using "io.LimitReader" and "io.CopyN" are the best option to prevent decompression bomb attacks.
</p>
<sample src="example_good.go"/>

<sample src="example_good_2.go" />
</example>
<references>

<li>
<a href="https://github.com/russellhaering/gosaml2/security/advisories/GHSA-6gc3-crp7-25w5">CVE-2023-26483 </a>
</li>
<li>
<a href="https://www.bamsoftware.com/hacks/zipbomb/">A great research to gain more impact by this kind of attacks</a>
</li>

</references>
</qhelp>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* @name Uncontrolled file decompression
* @description Uncontrolled data that flows into decompression library APIs without checking the compression rate is dangerous
* @kind path-problem
* @problem.severity error
* @security-severity 7.8
* @precision high
* @id go/uncontrolled-file-decompression
* @tags security
* experimental
* external/cwe/cwe-409
*/

import go
import experimental.frameworks.DecompressionBombs
import DecompressionBomb::Flow::PathGraph

from DecompressionBomb::Flow::PathNode source, DecompressionBomb::Flow::PathNode sink
where DecompressionBomb::Flow::flowPath(source, sink)
select sink.getNode(), source, sink, "This decompression is $@.", source.getNode(),
"decompressing compressed data without managing output size"
30 changes: 30 additions & 0 deletions go/ql/src/experimental/CWE-522-DecompressionBombs/example_good.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import (
"archive/zip"
"fmt"
"io"
"os"
)

func ZipOpenReader(filename string) {
// Open the zip file
r, _ := zip.OpenReader(filename)
var totalBytes int64
for _, f := range r.File {
rc, _ := f.Open()
totalBytes = 0
for {
result, _ := io.CopyN(os.Stdout, rc, 68)
if result == 0 {
break
}
totalBytes = totalBytes + result
if totalBytes > 1024*1024 {
fmt.Print(totalBytes)
_ = rc.Close()
break
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package main

import (
"compress/gzip"
"io"
"os"
)

func safeReader() {
var src io.Reader
src, _ = os.Open("filename")
gzipR, _ := gzip.NewReader(src)
dstF, _ := os.OpenFile("./test", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
defer dstF.Close()
var newSrc io.Reader
newSrc = io.LimitReader(gzipR, 1024*1024*1024*5)
_, _ = io.Copy(dstF, newSrc)
}
63 changes: 63 additions & 0 deletions go/ql/src/experimental/frameworks/DecompressionBombs.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* Provides a taint tracking configuration for reasoning about decompression bomb vulnerabilities.
*/

import go

class MimeMultipartFileHeader extends UntrustedFlowSource::Range {
MimeMultipartFileHeader() {
exists(DataFlow::FieldReadNode frn | this = frn |
frn.getField().hasQualifiedName("mime/multipart", "FileHeader", ["Filename", "Header"])
)
or
exists(DataFlow::Method m |
m.hasQualifiedName("mime/multipart", "FileHeader", "Open") and
this = m.getACall().getResult(0)
)
or
exists(DataFlow::FieldReadNode frn |
frn.getField().hasQualifiedName("mime/multipart", "Form", "Value")
)
}
}

/** Provides a taint tracking configuration for reasoning about decompression bomb vulnerabilities. */
module DecompressionBomb {
import experimental.frameworks.DecompressionBombsCustomizations

module Config implements DataFlow::StateConfigSig {
class FlowState = DecompressionBombs::FlowState;

predicate isSource(DataFlow::Node source, FlowState state) {
source instanceof UntrustedFlowSource and
state = ""
}

predicate isSink(DataFlow::Node sink, FlowState state) {
sink instanceof DecompressionBombs::Sink and
state =
[
"ZstdNewReader", "XzNewReader", "GzipNewReader", "PgzipNewReader", "S2NewReader",
"SnappyNewReader", "ZlibNewReader", "FlateNewReader", "Bzip2NewReader", "ZipOpenReader",
"ZipKlauspost"
]
}

predicate isAdditionalFlowStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
exists(DecompressionBombs::AdditionalTaintStep addStep |
addStep.isAdditionalFlowStep(fromNode, toNode)
)
}

predicate isAdditionalFlowStep(
DataFlow::Node fromNode, FlowState fromState, DataFlow::Node toNode, FlowState toState
) {
exists(DecompressionBombs::AdditionalTaintStep addStep |
addStep.isAdditionalFlowStep(fromNode, fromState, toNode, toState)
)
}
}

/** Tracks taint flow for reasoning about decompression bomb vulnerabilities. */
module Flow = TaintTracking::GlobalWithState<Config>;
}
Loading

0 comments on commit 820c145

Please sign in to comment.