diff --git a/ruby/ql/src/experimental/CWE-522-DecompressionBombs/DecompressionBombs.qhelp b/ruby/ql/src/experimental/CWE-522-DecompressionBombs/DecompressionBombs.qhelp new file mode 100644 index 00000000000..01741e519aa --- /dev/null +++ b/ruby/ql/src/experimental/CWE-522-DecompressionBombs/DecompressionBombs.qhelp @@ -0,0 +1,31 @@ + + + +

Extracting Compressed files with any compression algorithm like gzip can cause to denial of service attacks.

+

Attackers can compress a huge file which created by repeated similiar byte and convert it to a small compressed file.

+ +
+ + +

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.

+

Please read official RubyZip Documentation here

+
+ +

Rubyzip: According to official Documentation

+ + +
+ + +
  • +CVE-2023-22898 +Gitlab issue +
  • +
  • +A great research to gain more impact by this kind of attack +
  • + +
    +
    diff --git a/ruby/ql/src/experimental/CWE-522-DecompressionBombs/DecompressionBombs.ql b/ruby/ql/src/experimental/CWE-522-DecompressionBombs/DecompressionBombs.ql new file mode 100644 index 00000000000..5ab6d29af7b --- /dev/null +++ b/ruby/ql/src/experimental/CWE-522-DecompressionBombs/DecompressionBombs.ql @@ -0,0 +1,69 @@ +/** + * @name User-controlled file decompression + * @description User-controlled 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 rb/user-controlled-data-decompression + * @tags security + * experimental + * external/cwe/cwe-409 + */ + +import ruby +private import codeql.ruby.Concepts +private import codeql.ruby.DataFlow +private import codeql.ruby.TaintTracking +import DecompressionBombs + +/** + * A call to `IO.copy_stream` + */ +class IoCopyStream extends DataFlow::CallNode { + IoCopyStream() { this = API::getTopLevelMember("IO").getAMethodCall("copy_stream") } + + DataFlow::Node getAPathArgument() { result = this.getArgument(0) } +} + +module BombsConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } + + predicate isSink(DataFlow::Node sink) { sink instanceof DecompressionBomb::Sink } + + predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { + any(DecompressionBomb::AdditionalTaintStep ats).isAdditionalTaintStep(nodeFrom, nodeTo) + or + exists(API::Node n | n = API::getTopLevelMember("File").getMethod("open") | + nodeFrom = n.getParameter(0).asSink() and + nodeTo = n.getReturn().asSource() + ) + or + nodeFrom = nodeTo.(File::FileOpen).getAPathArgument() + or + exists(API::Node n | n = API::getTopLevelMember("StringIO").getMethod("new") | + nodeFrom = n.getParameter(0).asSink() and + nodeTo = n.getReturn().asSource() + ) + or + nodeFrom = nodeTo.(IoCopyStream).getAPathArgument() + or + // following can be a global additional step + exists(DataFlow::CallNode cn | + cn.getMethodName() = "open" and + cn.getReceiver().getExprNode().getExpr() instanceof Ast::SelfVariableAccess + | + nodeFrom = cn.getArgument(0) and + nodeTo = cn + ) + } +} + +module Bombs = TaintTracking::Global; + +import Bombs::PathGraph + +from Bombs::PathNode source, Bombs::PathNode sink +where Bombs::flowPath(source, sink) +select sink.getNode(), source, sink, "This file Decompression depends on a $@.", source.getNode(), + "potentially untrusted source" diff --git a/ruby/ql/src/experimental/CWE-522-DecompressionBombs/DecompressionBombs.qll b/ruby/ql/src/experimental/CWE-522-DecompressionBombs/DecompressionBombs.qll new file mode 100644 index 00000000000..c9622f5ed09 --- /dev/null +++ b/ruby/ql/src/experimental/CWE-522-DecompressionBombs/DecompressionBombs.qll @@ -0,0 +1,215 @@ +import codeql.ruby.AST +import codeql.ruby.frameworks.Files +import codeql.ruby.ApiGraphs +import codeql.ruby.DataFlow +import codeql.ruby.dataflow.RemoteFlowSources + +module DecompressionBomb { + /** + * A abstract class responsible for extending new decompression sinks + * + * can be a path, stream of compressed data, + * or a call to function that use pipe + */ + abstract class Sink extends DataFlow::Node { } + + /** + * The additional taint steps that need for creating taint tracking or dataflow. + */ + abstract class AdditionalTaintStep extends string { + AdditionalTaintStep() { this = "AdditionalTaintStep" } + + /** + * Holds if there is a additional taint step between pred and succ. + */ + abstract predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ); + } +} + +module Zlib { + /** + * Gets a node of `Zlib::GzipReader` member + * + * Note that if you use the lower level Zip::InputStream interface, rubyzip does not check the entry sizes. + */ + private API::Node gzipReaderInstance() { + result = API::getTopLevelMember("Zlib").getMember("GzipReader") + } + + /** + * A return values of following methods + * `Zlib::GzipReader.open` + * `Zlib::GzipReader.zcat` + * `Zlib::GzipReader.new` + */ + class DecompressionBombSink extends DecompressionBomb::Sink { + DecompressionBombSink() { + this = gzipReaderInstance().getMethod(["open", "new", "zcat"]).getReturn().asSource() + } + } + + /** + * The additional taint steps that need for creating taint tracking or dataflow for `Zlib`. + */ + class AdditionalTaintStep extends DecompressionBomb::AdditionalTaintStep { + AdditionalTaintStep() { this = "AdditionalTaintStep" } + + override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { + exists(API::Node openOrNewOrZcat | + openOrNewOrZcat = gzipReaderInstance().getMethod(["open", "new", "zcat"]) + | + nodeFrom = openOrNewOrZcat.getParameter(0).asSink() and + nodeTo = openOrNewOrZcat.getReturn().asSource() + ) + } + } +} + +module ZipInputStream { + /** + * Gets a node of `Zip::InputStream` member + * + * Note that if you use the lower level Zip::InputStream interface, rubyZip does not check the entry sizes. + */ + private API::Node zipInputStream() { + result = API::getTopLevelMember("Zip").getMember("InputStream") + } + + /** + * The methods + * `Zip::InputStream.read` + * `Zip::InputStream.extract` + * + * as source of decompression bombs, they need an additional taint step for a dataflow or taint tracking query + */ + class DecompressionBombSink extends DecompressionBomb::Sink { + DecompressionBombSink() { + this = zipInputStream().getMethod(["open", "new"]).getReturn().asSource() + } + } + + /** + * The additional taint steps that need for creating taint tracking or dataflow for `Zip`. + */ + class AdditionalTaintStep extends DecompressionBomb::AdditionalTaintStep { + AdditionalTaintStep() { this = "AdditionalTaintStep" } + + override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { + exists(API::Node openOrNew | openOrNew = zipInputStream().getMethod(["open", "new"]) | + nodeFrom = openOrNew.getParameter(0).asSink() and + nodeTo = openOrNew.getReturn().asSource() + ) + } + } +} + +module ZipFile { + /** + * Gets a node of `Zip::File` member + */ + API::Node zipFile() { result = API::getTopLevelMember("Zip").getMember("File") } + + /** + * Gets nodes that have a `base` at the beginning + */ + API::Node rubyZipNode(API::Node base) { + result = base + or + result = rubyZipNode(base).getMethod(_) + or + result = rubyZipNode(base).getReturn() + or + result = rubyZipNode(base).getParameter(_) + or + result = rubyZipNode(base).getAnElement() + or + result = rubyZipNode(base).getBlock() + } + + /** + * The return values of following methods + * `ZipIO.read` + * `ZipEntry.extract` + * A sanitizer exists inside the nodes which have `entry.size > someOBJ` + */ + class DecompressionBombSink extends DecompressionBomb::Sink { + DecompressionBombSink() { + exists(API::Node rubyZip | rubyZip = rubyZipNode(zipFile()) | + this = rubyZip.getMethod(["extract", "read"]).getReturn().asSource() and + not exists( + rubyZip.getMethod("size").getReturn().getMethod([">", " <", "<=", " >="]).getParameter(0) + ) + ) + } + } + + /** + * The additional taint steps that need for creating taint tracking or dataflow for `Zip::File`. + */ + class AdditionalTaintStep extends DecompressionBomb::AdditionalTaintStep { + AdditionalTaintStep() { this = "AdditionalTaintStep" } + + override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { + exists(API::Node zipFile | zipFile = zipFile().getMethod("open") | + nodeFrom = zipFile.getParameter(0).asSink() and + nodeTo = rubyZipNode(zipFile).getMethod(["extract", "read"]).getReturn().asSource() + ) + } + } + + predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { + exists(API::Node zipFile | zipFile = zipFile().getMethod("open") | + nodeFrom = zipFile.getParameter(0).asSink() and + nodeTo = rubyZipNode(zipFile).getMethod(["extract", "read"]).getReturn().asSource() + ) + } + // /** + // * The additional taint steps that need for creating taint tracking or dataflow for `Zip::File`. + // */ + // class AdditionalTaintStep1 extends DecompressionBomb::AdditionalTaintStep { + // AdditionalTaintStep1() { this = "AdditionalTaintStep" } + // override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { + // exists(API::Node zipFile | zipFile = zipFile().getMethod("open") | + // nodeFrom = zipFile.getParameter(0).asSink() and + // nodeTo = zipFile.asSource() + // ) + // } + // } + // API::Node rubyZipNode2() { + // result = zipFile().getMethod("open") + // or + // result = rubyZipNode2().getMethod(_) + // or + // result = rubyZipNode2().getReturn() + // or + // result = rubyZipNode2().getParameter(_) + // or + // result = rubyZipNode2().getAnElement() + // or + // result = rubyZipNode2().getBlock() + // } + // /** + // * The additional taint steps that need for creating taint tracking or dataflow for `Zip::File`. + // */ + // class AdditionalTaintStep2 extends DecompressionBomb::AdditionalTaintStep { + // AdditionalTaintStep2() { this = "AdditionalTaintStep" } + // override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { + // exists(API::Node zipFileOpen | zipFileOpen = rubyZipNode2() | + // nodeFrom = zipFileOpen.getReturn().asSource() and + // nodeTo = zipFileOpen.getMethod(["extract", "read"]).getReturn().asSource() + // ) + // } + // } + // /** + // * The additional taint steps that need for creating taint tracking or dataflow for `Zip::File`. + // */ + // class AdditionalTaintStep3 extends DecompressionBomb::AdditionalTaintStep { + // AdditionalTaintStep3() { this = "AdditionalTaintStep" } + // override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { + // exists(API::Node zipFileOpen | zipFileOpen = rubyZipNode2() | + // nodeFrom = zipFileOpen.asCall() and + // nodeTo = zipFileOpen.getMethod(["extract", "read"]).getReturn().asSource() + // ) + // } + // } +} diff --git a/ruby/ql/src/experimental/CWE-522-DecompressionBombs/example_bad.rb b/ruby/ql/src/experimental/CWE-522-DecompressionBombs/example_bad.rb new file mode 100644 index 00000000000..2195921ac28 --- /dev/null +++ b/ruby/ql/src/experimental/CWE-522-DecompressionBombs/example_bad.rb @@ -0,0 +1,5 @@ +# "Note that if you use the lower level Zip::InputStream interface, rubyzip does not check the entry sizes" +zip_stream = Zip::InputStream.new(File.open('file.zip')) +while entry = zip_stream.get_next_entry + # All required operations on `entry` go here. +end \ No newline at end of file diff --git a/ruby/ql/src/experimental/CWE-522-DecompressionBombs/example_good.rb b/ruby/ql/src/experimental/CWE-522-DecompressionBombs/example_good.rb new file mode 100644 index 00000000000..0785901ef6c --- /dev/null +++ b/ruby/ql/src/experimental/CWE-522-DecompressionBombs/example_good.rb @@ -0,0 +1,11 @@ +MAX_FILE_SIZE = 10 * 1024**2 # 10MiB +MAX_FILES = 100 +Zip::File.open('foo.zip') do |zip_file| + num_files = 0 + zip_file.each do |entry| + num_files += 1 if entry.file? + raise 'Too many extracted files' if num_files > MAX_FILES + raise 'File too large when extracted' if entry.size > MAX_FILE_SIZE + entry.extract + end +end \ No newline at end of file diff --git a/ruby/ql/test/query-tests/experimental/CWE-522-DecompressionBombs/DecompressionBombs.expected b/ruby/ql/test/query-tests/experimental/CWE-522-DecompressionBombs/DecompressionBombs.expected new file mode 100644 index 00000000000..7f3fab2ea87 --- /dev/null +++ b/ruby/ql/test/query-tests/experimental/CWE-522-DecompressionBombs/DecompressionBombs.expected @@ -0,0 +1,114 @@ +edges +| gzipBombs.rb:4:3:4:11 | gzip_path | gzipBombs.rb:6:25:6:33 | gzip_path | provenance | | +| gzipBombs.rb:4:15:4:20 | call to params | gzipBombs.rb:4:15:4:27 | ...[...] | provenance | | +| gzipBombs.rb:4:15:4:27 | ...[...] | gzipBombs.rb:4:3:4:11 | gzip_path | provenance | | +| gzipBombs.rb:6:25:6:33 | gzip_path | gzipBombs.rb:6:3:6:34 | call to open | provenance | | +| gzipBombs.rb:6:25:6:33 | gzip_path | gzipBombs.rb:7:25:7:33 | gzip_path | provenance | | +| gzipBombs.rb:7:25:7:33 | gzip_path | gzipBombs.rb:7:3:9:5 | call to open | provenance | | +| gzipBombs.rb:7:25:7:33 | gzip_path | gzipBombs.rb:10:25:10:33 | gzip_path | provenance | | +| gzipBombs.rb:10:25:10:33 | gzip_path | gzipBombs.rb:10:3:14:5 | call to open | provenance | | +| gzipBombs.rb:10:25:10:33 | gzip_path | gzipBombs.rb:15:44:15:52 | gzip_path | provenance | | +| gzipBombs.rb:15:44:15:52 | gzip_path | gzipBombs.rb:15:22:15:53 | call to open | provenance | | +| gzipBombs.rb:15:44:15:52 | gzip_path | gzipBombs.rb:20:34:20:42 | gzip_path | provenance | | +| gzipBombs.rb:20:24:20:49 | call to open | gzipBombs.rb:20:3:20:50 | call to new | provenance | | +| gzipBombs.rb:20:34:20:42 | gzip_path | gzipBombs.rb:20:24:20:49 | call to open | provenance | | +| gzipBombs.rb:20:34:20:42 | gzip_path | gzipBombs.rb:21:34:21:42 | gzip_path | provenance | | +| gzipBombs.rb:21:24:21:49 | call to open | gzipBombs.rb:21:3:21:50 | call to new | provenance | | +| gzipBombs.rb:21:34:21:42 | gzip_path | gzipBombs.rb:21:24:21:49 | call to open | provenance | | +| gzipBombs.rb:21:34:21:42 | gzip_path | gzipBombs.rb:25:30:25:38 | gzip_path | provenance | | +| gzipBombs.rb:25:25:25:39 | call to open | gzipBombs.rb:25:3:25:40 | call to zcat | provenance | | +| gzipBombs.rb:25:30:25:38 | gzip_path | gzipBombs.rb:25:25:25:39 | call to open | provenance | | +| zipBombs.rb:4:3:4:14 | zipfile_path | zipBombs.rb:6:25:6:36 | zipfile_path | provenance | | +| zipBombs.rb:4:18:4:23 | call to params | zipBombs.rb:4:18:4:30 | ...[...] | provenance | | +| zipBombs.rb:4:18:4:30 | ...[...] | zipBombs.rb:4:3:4:14 | zipfile_path | provenance | | +| zipBombs.rb:6:25:6:36 | zipfile_path | zipBombs.rb:6:3:11:5 | call to open | provenance | | +| zipBombs.rb:6:25:6:36 | zipfile_path | zipBombs.rb:12:25:12:36 | zipfile_path | provenance | | +| zipBombs.rb:12:25:12:36 | zipfile_path | zipBombs.rb:12:3:14:5 | call to open | provenance | | +| zipBombs.rb:12:25:12:36 | zipfile_path | zipBombs.rb:15:33:15:44 | zipfile_path | provenance | | +| zipBombs.rb:15:33:15:44 | zipfile_path | zipBombs.rb:15:11:15:45 | call to open | provenance | | +| zipBombs.rb:15:33:15:44 | zipfile_path | zipBombs.rb:17:18:17:29 | zipfile_path | provenance | | +| zipBombs.rb:17:18:17:29 | zipfile_path | zipBombs.rb:17:3:17:42 | call to read | provenance | | +| zipBombs.rb:17:18:17:29 | zipfile_path | zipBombs.rb:18:18:18:29 | zipfile_path | provenance | | +| zipBombs.rb:18:18:18:29 | zipfile_path | zipBombs.rb:18:3:18:51 | call to extract | provenance | | +| zipBombs.rb:18:18:18:29 | zipfile_path | zipBombs.rb:20:18:20:29 | zipfile_path | provenance | | +| zipBombs.rb:20:18:20:29 | zipfile_path | zipBombs.rb:28:7:28:33 | call to read | provenance | | +| zipBombs.rb:20:18:20:29 | zipfile_path | zipBombs.rb:32:29:32:40 | zipfile_path | provenance | | +| zipBombs.rb:32:29:32:40 | zipfile_path | zipBombs.rb:34:5:34:17 | call to extract | provenance | | +| zipBombs.rb:32:29:32:40 | zipfile_path | zipBombs.rb:35:5:35:31 | call to read | provenance | | +| zipBombs.rb:32:29:32:40 | zipfile_path | zipBombs.rb:39:18:39:29 | zipfile_path | provenance | | +| zipBombs.rb:39:18:39:29 | zipfile_path | zipBombs.rb:41:7:41:31 | call to read | provenance | | +| zipBombs.rb:39:18:39:29 | zipfile_path | zipBombs.rb:42:7:42:19 | call to extract | provenance | | +| zipBombs.rb:39:18:39:29 | zipfile_path | zipBombs.rb:46:10:46:36 | call to read | provenance | | +| zipBombs.rb:39:18:39:29 | zipfile_path | zipBombs.rb:49:29:49:40 | zipfile_path | provenance | | +| zipBombs.rb:49:29:49:40 | zipfile_path | zipBombs.rb:51:8:51:34 | call to read | provenance | | +| zipBombs.rb:49:29:49:40 | zipfile_path | zipBombs.rb:53:29:53:40 | zipfile_path | provenance | | +| zipBombs.rb:53:29:53:40 | zipfile_path | zipBombs.rb:55:5:55:31 | call to read | provenance | | +nodes +| gzipBombs.rb:4:3:4:11 | gzip_path | semmle.label | gzip_path | +| gzipBombs.rb:4:15:4:20 | call to params | semmle.label | call to params | +| gzipBombs.rb:4:15:4:27 | ...[...] | semmle.label | ...[...] | +| gzipBombs.rb:6:3:6:34 | call to open | semmle.label | call to open | +| gzipBombs.rb:6:25:6:33 | gzip_path | semmle.label | gzip_path | +| gzipBombs.rb:7:3:9:5 | call to open | semmle.label | call to open | +| gzipBombs.rb:7:25:7:33 | gzip_path | semmle.label | gzip_path | +| gzipBombs.rb:10:3:14:5 | call to open | semmle.label | call to open | +| gzipBombs.rb:10:25:10:33 | gzip_path | semmle.label | gzip_path | +| gzipBombs.rb:15:22:15:53 | call to open | semmle.label | call to open | +| gzipBombs.rb:15:44:15:52 | gzip_path | semmle.label | gzip_path | +| gzipBombs.rb:20:3:20:50 | call to new | semmle.label | call to new | +| gzipBombs.rb:20:24:20:49 | call to open | semmle.label | call to open | +| gzipBombs.rb:20:34:20:42 | gzip_path | semmle.label | gzip_path | +| gzipBombs.rb:21:3:21:50 | call to new | semmle.label | call to new | +| gzipBombs.rb:21:24:21:49 | call to open | semmle.label | call to open | +| gzipBombs.rb:21:34:21:42 | gzip_path | semmle.label | gzip_path | +| gzipBombs.rb:25:3:25:40 | call to zcat | semmle.label | call to zcat | +| gzipBombs.rb:25:25:25:39 | call to open | semmle.label | call to open | +| gzipBombs.rb:25:30:25:38 | gzip_path | semmle.label | gzip_path | +| zipBombs.rb:4:3:4:14 | zipfile_path | semmle.label | zipfile_path | +| zipBombs.rb:4:18:4:23 | call to params | semmle.label | call to params | +| zipBombs.rb:4:18:4:30 | ...[...] | semmle.label | ...[...] | +| zipBombs.rb:6:3:11:5 | call to open | semmle.label | call to open | +| zipBombs.rb:6:25:6:36 | zipfile_path | semmle.label | zipfile_path | +| zipBombs.rb:12:3:14:5 | call to open | semmle.label | call to open | +| zipBombs.rb:12:25:12:36 | zipfile_path | semmle.label | zipfile_path | +| zipBombs.rb:15:11:15:45 | call to open | semmle.label | call to open | +| zipBombs.rb:15:33:15:44 | zipfile_path | semmle.label | zipfile_path | +| zipBombs.rb:17:3:17:42 | call to read | semmle.label | call to read | +| zipBombs.rb:17:18:17:29 | zipfile_path | semmle.label | zipfile_path | +| zipBombs.rb:18:3:18:51 | call to extract | semmle.label | call to extract | +| zipBombs.rb:18:18:18:29 | zipfile_path | semmle.label | zipfile_path | +| zipBombs.rb:20:18:20:29 | zipfile_path | semmle.label | zipfile_path | +| zipBombs.rb:28:7:28:33 | call to read | semmle.label | call to read | +| zipBombs.rb:32:29:32:40 | zipfile_path | semmle.label | zipfile_path | +| zipBombs.rb:34:5:34:17 | call to extract | semmle.label | call to extract | +| zipBombs.rb:35:5:35:31 | call to read | semmle.label | call to read | +| zipBombs.rb:39:18:39:29 | zipfile_path | semmle.label | zipfile_path | +| zipBombs.rb:41:7:41:31 | call to read | semmle.label | call to read | +| zipBombs.rb:42:7:42:19 | call to extract | semmle.label | call to extract | +| zipBombs.rb:46:10:46:36 | call to read | semmle.label | call to read | +| zipBombs.rb:49:29:49:40 | zipfile_path | semmle.label | zipfile_path | +| zipBombs.rb:51:8:51:34 | call to read | semmle.label | call to read | +| zipBombs.rb:53:29:53:40 | zipfile_path | semmle.label | zipfile_path | +| zipBombs.rb:55:5:55:31 | call to read | semmle.label | call to read | +subpaths +#select +| gzipBombs.rb:6:3:6:34 | call to open | gzipBombs.rb:4:15:4:20 | call to params | gzipBombs.rb:6:3:6:34 | call to open | This file Decompression depends on a $@. | gzipBombs.rb:4:15:4:20 | call to params | potentially untrusted source | +| gzipBombs.rb:7:3:9:5 | call to open | gzipBombs.rb:4:15:4:20 | call to params | gzipBombs.rb:7:3:9:5 | call to open | This file Decompression depends on a $@. | gzipBombs.rb:4:15:4:20 | call to params | potentially untrusted source | +| gzipBombs.rb:10:3:14:5 | call to open | gzipBombs.rb:4:15:4:20 | call to params | gzipBombs.rb:10:3:14:5 | call to open | This file Decompression depends on a $@. | gzipBombs.rb:4:15:4:20 | call to params | potentially untrusted source | +| gzipBombs.rb:15:22:15:53 | call to open | gzipBombs.rb:4:15:4:20 | call to params | gzipBombs.rb:15:22:15:53 | call to open | This file Decompression depends on a $@. | gzipBombs.rb:4:15:4:20 | call to params | potentially untrusted source | +| gzipBombs.rb:20:3:20:50 | call to new | gzipBombs.rb:4:15:4:20 | call to params | gzipBombs.rb:20:3:20:50 | call to new | This file Decompression depends on a $@. | gzipBombs.rb:4:15:4:20 | call to params | potentially untrusted source | +| gzipBombs.rb:21:3:21:50 | call to new | gzipBombs.rb:4:15:4:20 | call to params | gzipBombs.rb:21:3:21:50 | call to new | This file Decompression depends on a $@. | gzipBombs.rb:4:15:4:20 | call to params | potentially untrusted source | +| gzipBombs.rb:25:3:25:40 | call to zcat | gzipBombs.rb:4:15:4:20 | call to params | gzipBombs.rb:25:3:25:40 | call to zcat | This file Decompression depends on a $@. | gzipBombs.rb:4:15:4:20 | call to params | potentially untrusted source | +| zipBombs.rb:6:3:11:5 | call to open | zipBombs.rb:4:18:4:23 | call to params | zipBombs.rb:6:3:11:5 | call to open | This file Decompression depends on a $@. | zipBombs.rb:4:18:4:23 | call to params | potentially untrusted source | +| zipBombs.rb:12:3:14:5 | call to open | zipBombs.rb:4:18:4:23 | call to params | zipBombs.rb:12:3:14:5 | call to open | This file Decompression depends on a $@. | zipBombs.rb:4:18:4:23 | call to params | potentially untrusted source | +| zipBombs.rb:15:11:15:45 | call to open | zipBombs.rb:4:18:4:23 | call to params | zipBombs.rb:15:11:15:45 | call to open | This file Decompression depends on a $@. | zipBombs.rb:4:18:4:23 | call to params | potentially untrusted source | +| zipBombs.rb:17:3:17:42 | call to read | zipBombs.rb:4:18:4:23 | call to params | zipBombs.rb:17:3:17:42 | call to read | This file Decompression depends on a $@. | zipBombs.rb:4:18:4:23 | call to params | potentially untrusted source | +| zipBombs.rb:18:3:18:51 | call to extract | zipBombs.rb:4:18:4:23 | call to params | zipBombs.rb:18:3:18:51 | call to extract | This file Decompression depends on a $@. | zipBombs.rb:4:18:4:23 | call to params | potentially untrusted source | +| zipBombs.rb:28:7:28:33 | call to read | zipBombs.rb:4:18:4:23 | call to params | zipBombs.rb:28:7:28:33 | call to read | This file Decompression depends on a $@. | zipBombs.rb:4:18:4:23 | call to params | potentially untrusted source | +| zipBombs.rb:34:5:34:17 | call to extract | zipBombs.rb:4:18:4:23 | call to params | zipBombs.rb:34:5:34:17 | call to extract | This file Decompression depends on a $@. | zipBombs.rb:4:18:4:23 | call to params | potentially untrusted source | +| zipBombs.rb:35:5:35:31 | call to read | zipBombs.rb:4:18:4:23 | call to params | zipBombs.rb:35:5:35:31 | call to read | This file Decompression depends on a $@. | zipBombs.rb:4:18:4:23 | call to params | potentially untrusted source | +| zipBombs.rb:41:7:41:31 | call to read | zipBombs.rb:4:18:4:23 | call to params | zipBombs.rb:41:7:41:31 | call to read | This file Decompression depends on a $@. | zipBombs.rb:4:18:4:23 | call to params | potentially untrusted source | +| zipBombs.rb:42:7:42:19 | call to extract | zipBombs.rb:4:18:4:23 | call to params | zipBombs.rb:42:7:42:19 | call to extract | This file Decompression depends on a $@. | zipBombs.rb:4:18:4:23 | call to params | potentially untrusted source | +| zipBombs.rb:46:10:46:36 | call to read | zipBombs.rb:4:18:4:23 | call to params | zipBombs.rb:46:10:46:36 | call to read | This file Decompression depends on a $@. | zipBombs.rb:4:18:4:23 | call to params | potentially untrusted source | +| zipBombs.rb:51:8:51:34 | call to read | zipBombs.rb:4:18:4:23 | call to params | zipBombs.rb:51:8:51:34 | call to read | This file Decompression depends on a $@. | zipBombs.rb:4:18:4:23 | call to params | potentially untrusted source | +| zipBombs.rb:55:5:55:31 | call to read | zipBombs.rb:4:18:4:23 | call to params | zipBombs.rb:55:5:55:31 | call to read | This file Decompression depends on a $@. | zipBombs.rb:4:18:4:23 | call to params | potentially untrusted source | diff --git a/ruby/ql/test/query-tests/experimental/CWE-522-DecompressionBombs/DecompressionBombs.qlref b/ruby/ql/test/query-tests/experimental/CWE-522-DecompressionBombs/DecompressionBombs.qlref new file mode 100644 index 00000000000..c24a4cc9678 --- /dev/null +++ b/ruby/ql/test/query-tests/experimental/CWE-522-DecompressionBombs/DecompressionBombs.qlref @@ -0,0 +1 @@ +experimental/CWE-522-DecompressionBombs/DecompressionBombs.ql \ No newline at end of file diff --git a/ruby/ql/test/query-tests/experimental/CWE-522-DecompressionBombs/gzipBombs.rb b/ruby/ql/test/query-tests/experimental/CWE-522-DecompressionBombs/gzipBombs.rb new file mode 100644 index 00000000000..bf9bb7b329d --- /dev/null +++ b/ruby/ql/test/query-tests/experimental/CWE-522-DecompressionBombs/gzipBombs.rb @@ -0,0 +1,27 @@ +require 'zlib' + +class TestController < ActionController::Base + gzip_path = params[:path] + + Zlib::GzipReader.open(gzip_path).read + Zlib::GzipReader.open(gzip_path) do |uncompressedfile| + puts uncompressedfile.read + end + Zlib::GzipReader.open(gzip_path) do |uncompressedfile| + uncompressedfile.each do |entry| + puts entry + end + end + uncompressedfile = Zlib::GzipReader.open(gzip_path) + uncompressedfile.each do |entry| + puts entry + end + + Zlib::GzipReader.new(File.open(gzip_path, 'rb')).read + Zlib::GzipReader.new(File.open(gzip_path, 'rb')).each do |entry| + puts entry + end + + Zlib::GzipReader.zcat(open(gzip_path)) +end + diff --git a/ruby/ql/test/query-tests/experimental/CWE-522-DecompressionBombs/zipBombs.rb b/ruby/ql/test/query-tests/experimental/CWE-522-DecompressionBombs/zipBombs.rb new file mode 100644 index 00000000000..5aab5ce6382 --- /dev/null +++ b/ruby/ql/test/query-tests/experimental/CWE-522-DecompressionBombs/zipBombs.rb @@ -0,0 +1,57 @@ +require 'zip' + +class TestController < ActionController::Base + zipfile_path = params[:path] + + Zip::InputStream.open(zipfile_path) do |input| + while (entry = input.get_next_entry) + puts :file_name, entry.name + input + end + end + Zip::InputStream.open(zipfile_path) do |input| + input.read + end + input = Zip::InputStream.open(zipfile_path) + + Zip::File.open(zipfile_path).read "10GB" + Zip::File.open(zipfile_path).extract "10GB", "./" + + Zip::File.open(zipfile_path) do |zip_file| + # Handle entries one by one + zip_file.each do |entry| + puts "Extracting #{entry.name}" + raise 'File too large when extracted' if entry.size > MAX_SIZE + # Extract to file or directory based on name in the archive + entry.extract + # Read into memory + entry.get_input_stream.read + end + end + + zip_file = Zip::File.open(zipfile_path) + zip_file.each do |entry| + entry.extract + entry.get_input_stream.read + end + + # Find specific entry + Zip::File.open(zipfile_path) do |zip_file| + zip_file.glob('*.xml').each do |entry| + zip_file.read(entry.name) + entry.extract + end + entry = zip_file.glob('*.csv').first + raise 'File too large when extracted' if entry.size > MAX_SIZE + puts entry.get_input_stream.read + end + + zip_file = Zip::File.open(zipfile_path) + entry = zip_file.glob('*.csv') + puts entry.get_input_stream.read + + zip_file = Zip::File.open(zipfile_path) + zip_file.glob('*') do |entry| + entry.get_input_stream.read + end +end