-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Ruby: Decompression Bombs #13556
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ruby: Decompression Bombs #13556
Conversation
QHelp previews: ruby/ql/src/experimental/CWE-522-DecompressionBombs/DecompressionBombs.qhelperrors/warnings:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CodeQL found more than 10 potential problems in the proposed changes. Check the Files changed tab for more details.
Hi, I've completed the work on this query. |
Hey @amammad, would you be able to submit these to the Security Lab bug bounty program using this template as this will help us prioritise this work, and it would also be really helpful to test the CVEs found as well. Thank you for your submission! |
@calumgrant I'll submit related issues in GH security lab in this week. |
QHelp previews: ruby/ql/src/experimental/CWE-522-DecompressionBombs/DecompressionBombs.qhelperrors/warnings:
|
ruby/ql/src/experimental/CWE-522-DecompressionBombs/DecompressionBombs.ql
Fixed
Show resolved
Hide resolved
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() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, I can't use a predicate with a better performance like the following here:
API::Node rubyZipNode() {
result = zipFile()
or
result = rubyZipNode().getMethod(_)
or
result = rubyZipNode().getReturn()
or
result = rubyZipNode().getParameter(_)
or
result = rubyZipNode().getAnElement()
or
result = rubyZipNode().getBlock()
}
because lines 164 and 165 won't work for this then.
QHelp previews: ruby/ql/src/experimental/CWE-522-DecompressionBombs/DecompressionBombs.qhelperrors/warnings:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, I've run an initial review of this. Some of the feedback here may overlap with comments on the versions of this query for other languages.
ruby/ql/src/experimental/CWE-522-DecompressionBombs/DecompressionBombs.qhelp
Outdated
Show resolved
Hide resolved
ruby/ql/src/experimental/CWE-522-DecompressionBombs/DecompressionBombs.ql
Outdated
Show resolved
Hide resolved
ruby/ql/src/experimental/CWE-522-DecompressionBombs/DecompressionBombs.qll
Outdated
Show resolved
Hide resolved
ruby/ql/src/experimental/CWE-522-DecompressionBombs/DecompressionBombs.ql
Fixed
Show resolved
Hide resolved
ruby/ql/src/experimental/CWE-522-DecompressionBombs/DecompressionBombs.ql
Outdated
Show resolved
Hide resolved
ruby/ql/src/experimental/CWE-522-DecompressionBombs/DecompressionBombs.ql
Outdated
Show resolved
Hide resolved
ruby/ql/src/experimental/CWE-522-DecompressionBombs/DecompressionBombs.ql
Outdated
Show resolved
Hide resolved
ruby/ql/src/experimental/CWE-522-DecompressionBombs/DecompressionBombs.ql
Outdated
Show resolved
Hide resolved
|
||
override DataFlow::Node sink() { | ||
result = this.getMethod(["extract", "read"]).getReturn().asSource() and | ||
not exists(this.getMethod("size").getReturn().getMethod(">").getParameter(0)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably be using DataFlow::OperationNode
and/or Ast::RelationalOperation
to detect other inequality tests (e.g. >
, <
, <=
, >=
) here.
I'm also not sure that restricting this to just cases where entry.size
is the greater operand is desirable. e.g. the following both function as sanitizers:
if entry.size > limit
raise "exceeds limit"
else
entry.extract
end
if entry.size < limit
entry.extract
else
raise "exceeds limit"
end
… taint steps that can improve performance
Hi @alexrford |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delay, approved.
QHelp previews: ruby/ql/src/experimental/CWE-522-DecompressionBombs/DecompressionBombs.qhelpUser-controlled file decompressionExtracting 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. RecommendationWhen 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 ExampleRubyzip: According to official Documentation 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 # "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 References
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a couple of suggestions to make CI pass.
</recommendation> | ||
<p>Please read official RubyZip Documentation <a href="https://github.com/rubyzip/rubyzip/#size-validation">here</a></p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
</recommendation> | |
<p>Please read official RubyZip Documentation <a href="https://github.com/rubyzip/rubyzip/#size-validation">here</a></p> | |
<p>Please read official RubyZip Documentation <a href="https://github.com/rubyzip/rubyzip/#size-validation">here</a></p> | |
</recommendation> | |
This should fix the qhelp check error.
* @problem.severity error | ||
* @security-severity 7.8 | ||
* @precision high | ||
* @id rb/user-controlled-file-decompression |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* @id rb/user-controlled-file-decompression | |
* @id rb/user-controlled-data-decompression |
For consistency with JS and to address the duplicate query ID check.
@@ -0,0 +1,114 @@ | |||
edges |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This .expected
file looks like it needs to be regenerated to account for a change in the edges
relation format.
Thank you a lot for approving this PR and developing and improving the CodeQL every day :) |
This is part of All for one, one for all query submission, I'm going to submit an issue in github/securitylab for this pull request too.
I wrote two versions which V2 is using recursion by use of API graphs to find zip file sinks. I added V1 because I think it maybe have some advantage in your opinion but I prefer V2. the predicates in V2 can used in ZipSlip query too I can work on this too if you agree.( I couldn't write the recursion predicate without the initial help of @asgerf in #13431)
I added the only sanitizer that I think is available which is checking the size of each zip entry.
Also I added some instance of some really popular additional taint steps which I think it is good to be added as global steps.