mirror of
https://github.com/github/codeql.git
synced 2025-12-17 01:03:14 +01:00
Merge pull request #9477 from thiggy1342/experimental-archive-api
RB: Adding experimental query for detecting path traversal in Archive libraries
This commit is contained in:
5
ruby/ql/lib/change-notes/released/0.2.3.md
Normal file
5
ruby/ql/lib/change-notes/released/0.2.3.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## 0.2.3
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
- Calls to `Zip::File.open` and `Zip::File.new` have been added as `FileSystemAccess` sinks. As a result queries like `rb/path-injection` now flag up cases where users may access arbitrary archive files.
|
||||
@@ -8,6 +8,7 @@ private import codeql.ruby.frameworks.ActiveRecord
|
||||
private import codeql.ruby.frameworks.ActiveStorage
|
||||
private import codeql.ruby.frameworks.ActionView
|
||||
private import codeql.ruby.frameworks.ActiveSupport
|
||||
private import codeql.ruby.frameworks.Archive
|
||||
private import codeql.ruby.frameworks.GraphQL
|
||||
private import codeql.ruby.frameworks.Rails
|
||||
private import codeql.ruby.frameworks.Stdlib
|
||||
|
||||
33
ruby/ql/lib/codeql/ruby/frameworks/Archive.qll
Normal file
33
ruby/ql/lib/codeql/ruby/frameworks/Archive.qll
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Provides classes for working with archive libraries.
|
||||
*/
|
||||
|
||||
private import ruby
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.ApiGraphs
|
||||
|
||||
/**
|
||||
* Classes and predicates for modeling the RubyZip library
|
||||
*/
|
||||
module RubyZip {
|
||||
/**
|
||||
* A call to `Zip::File.new`, considered as a `FileSystemAccess`
|
||||
*/
|
||||
class RubyZipFileNew extends DataFlow::CallNode, FileSystemAccess::Range {
|
||||
RubyZipFileNew() { this = API::getTopLevelMember("Zip").getMember("File").getAnInstantiation() }
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `Zip::File.open`, considered as a `FileSystemAccess`.
|
||||
*/
|
||||
class RubyZipFileOpen extends DataFlow::CallNode, FileSystemAccess::Range {
|
||||
RubyZipFileOpen() {
|
||||
this = API::getTopLevelMember("Zip").getMember("File").getAMethodCall("open")
|
||||
}
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
rubyZipFileOpens
|
||||
| Archive.rb:2:12:2:35 | call to open |
|
||||
rubyZipFileNew
|
||||
| Archive.rb:5:12:5:34 | call to new |
|
||||
6
ruby/ql/test/library-tests/frameworks/archive/Archive.ql
Normal file
6
ruby/ql/test/library-tests/frameworks/archive/Archive.ql
Normal file
@@ -0,0 +1,6 @@
|
||||
private import ruby
|
||||
private import codeql.ruby.frameworks.Archive
|
||||
|
||||
query predicate rubyZipFileOpens(RubyZip::RubyZipFileOpen f) { any() }
|
||||
|
||||
query predicate rubyZipFileNew(RubyZip::RubyZipFileNew f) { any() }
|
||||
5
ruby/ql/test/library-tests/frameworks/archive/Archive.rb
Normal file
5
ruby/ql/test/library-tests/frameworks/archive/Archive.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
# `foo_file` is a RubyZip `Zip::File.open` instance
|
||||
foo_file = Zip::File.open(filename)
|
||||
|
||||
# `new_file` is a RubyZip `Zip::File.new` instance
|
||||
new_file = Zip::File.new(filename)
|
||||
@@ -0,0 +1,82 @@
|
||||
class TestContoller < ActionController::Base
|
||||
|
||||
# this is vulnerable
|
||||
def upload
|
||||
untar params[:file], params[:filename]
|
||||
end
|
||||
|
||||
# this is vulnerable
|
||||
def unpload_zip
|
||||
unzip params[:file]
|
||||
end
|
||||
|
||||
# this is vulnerable
|
||||
def create_new_zip
|
||||
zip params[:filename], files
|
||||
end
|
||||
|
||||
# these are not vulnerable because of the string compare sanitizer
|
||||
def safe_upload_string_compare
|
||||
filename = params[:filename]
|
||||
if filename == "safefile.tar"
|
||||
untar params[:file], filename
|
||||
end
|
||||
end
|
||||
|
||||
def safe_upload_zip_string_compare
|
||||
filename = params[:filename]
|
||||
if filename == "safefile.zip"
|
||||
unzip filename
|
||||
end
|
||||
end
|
||||
|
||||
# these are not vulnerable beacuse of the string array compare sanitizer
|
||||
def safe_upload_string_array_compare
|
||||
filename = params[:filename]
|
||||
if ["safefile1.tar", "safefile2.tar"].include? filename
|
||||
untar params[:file], filename
|
||||
end
|
||||
end
|
||||
|
||||
def safe_upload_zip_string_array_compare
|
||||
filename = params[:filename]
|
||||
if ["safefile1.zip", "safefile2.zip"].include? filename
|
||||
unzip filename
|
||||
end
|
||||
end
|
||||
|
||||
# these are our two sinks
|
||||
def untar(io, destination)
|
||||
Gem::Package::TarReader.new io do |tar|
|
||||
tar.each do |tarfile|
|
||||
destination_file = File.join destination, tarfile.full_name
|
||||
|
||||
if tarfile.directory?
|
||||
FileUtils.mkdir_p destination_file
|
||||
else
|
||||
destination_directory = File.dirname(destination_file)
|
||||
FileUtils.mkdir_p destination_directory unless File.directory?(destination_directory)
|
||||
File.open destination_file, "wb" do |f|
|
||||
f.print tarfile.read
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def unzip(file)
|
||||
Zip::File.open(file) do |zip_file|
|
||||
zip_file.each do |entry|
|
||||
entry.extract
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def zip(filename, files = [])
|
||||
Zip::File.new(filename) do |zf|
|
||||
files.each do |f|
|
||||
zf.add f
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,15 @@
|
||||
edges
|
||||
| ArchiveApiPathTraversal.rb:5:26:5:31 | call to params : | ArchiveApiPathTraversal.rb:5:26:5:42 | ...[...] : |
|
||||
| ArchiveApiPathTraversal.rb:5:26:5:42 | ...[...] : | ArchiveApiPathTraversal.rb:49:17:49:27 | destination : |
|
||||
| ArchiveApiPathTraversal.rb:10:11:10:16 | call to params : | ArchiveApiPathTraversal.rb:10:11:10:23 | ...[...] : |
|
||||
| ArchiveApiPathTraversal.rb:10:11:10:23 | ...[...] : | ArchiveApiPathTraversal.rb:67:13:67:16 | file : |
|
||||
| ArchiveApiPathTraversal.rb:15:9:15:14 | call to params : | ArchiveApiPathTraversal.rb:15:9:15:25 | ...[...] : |
|
||||
| ArchiveApiPathTraversal.rb:15:9:15:25 | ...[...] : | ArchiveApiPathTraversal.rb:75:11:75:18 | filename : |
|
||||
| ArchiveApiPathTraversal.rb:49:17:49:27 | destination : | ArchiveApiPathTraversal.rb:52:38:52:48 | destination : |
|
||||
| ArchiveApiPathTraversal.rb:52:28:52:67 | call to join : | ArchiveApiPathTraversal.rb:59:21:59:36 | destination_file |
|
||||
| ArchiveApiPathTraversal.rb:52:38:52:48 | destination : | ArchiveApiPathTraversal.rb:52:28:52:67 | call to join : |
|
||||
| ArchiveApiPathTraversal.rb:67:13:67:16 | file : | ArchiveApiPathTraversal.rb:68:20:68:23 | file |
|
||||
| ArchiveApiPathTraversal.rb:75:11:75:18 | filename : | ArchiveApiPathTraversal.rb:76:19:76:26 | filename |
|
||||
| tainted_path.rb:4:12:4:17 | call to params : | tainted_path.rb:4:12:4:24 | ...[...] : |
|
||||
| tainted_path.rb:4:12:4:24 | ...[...] : | tainted_path.rb:5:26:5:29 | path |
|
||||
| tainted_path.rb:10:12:10:43 | call to absolute_path : | tainted_path.rb:11:26:11:29 | path |
|
||||
@@ -26,6 +37,20 @@ edges
|
||||
| tainted_path.rb:59:40:59:45 | call to params : | tainted_path.rb:59:40:59:52 | ...[...] : |
|
||||
| tainted_path.rb:59:40:59:52 | ...[...] : | tainted_path.rb:59:12:59:53 | call to new : |
|
||||
nodes
|
||||
| ArchiveApiPathTraversal.rb:5:26:5:31 | call to params : | semmle.label | call to params : |
|
||||
| ArchiveApiPathTraversal.rb:5:26:5:42 | ...[...] : | semmle.label | ...[...] : |
|
||||
| ArchiveApiPathTraversal.rb:10:11:10:16 | call to params : | semmle.label | call to params : |
|
||||
| ArchiveApiPathTraversal.rb:10:11:10:23 | ...[...] : | semmle.label | ...[...] : |
|
||||
| ArchiveApiPathTraversal.rb:15:9:15:14 | call to params : | semmle.label | call to params : |
|
||||
| ArchiveApiPathTraversal.rb:15:9:15:25 | ...[...] : | semmle.label | ...[...] : |
|
||||
| ArchiveApiPathTraversal.rb:49:17:49:27 | destination : | semmle.label | destination : |
|
||||
| ArchiveApiPathTraversal.rb:52:28:52:67 | call to join : | semmle.label | call to join : |
|
||||
| ArchiveApiPathTraversal.rb:52:38:52:48 | destination : | semmle.label | destination : |
|
||||
| ArchiveApiPathTraversal.rb:59:21:59:36 | destination_file | semmle.label | destination_file |
|
||||
| ArchiveApiPathTraversal.rb:67:13:67:16 | file : | semmle.label | file : |
|
||||
| ArchiveApiPathTraversal.rb:68:20:68:23 | file | semmle.label | file |
|
||||
| ArchiveApiPathTraversal.rb:75:11:75:18 | filename : | semmle.label | filename : |
|
||||
| ArchiveApiPathTraversal.rb:76:19:76:26 | filename | semmle.label | filename |
|
||||
| tainted_path.rb:4:12:4:17 | call to params : | semmle.label | call to params : |
|
||||
| tainted_path.rb:4:12:4:24 | ...[...] : | semmle.label | ...[...] : |
|
||||
| tainted_path.rb:5:26:5:29 | path | semmle.label | path |
|
||||
@@ -63,6 +88,9 @@ nodes
|
||||
| tainted_path.rb:60:26:60:29 | path | semmle.label | path |
|
||||
subpaths
|
||||
#select
|
||||
| ArchiveApiPathTraversal.rb:59:21:59:36 | destination_file | ArchiveApiPathTraversal.rb:5:26:5:31 | call to params : | ArchiveApiPathTraversal.rb:59:21:59:36 | destination_file | This path depends on $@. | ArchiveApiPathTraversal.rb:5:26:5:31 | call to params | a user-provided value |
|
||||
| ArchiveApiPathTraversal.rb:68:20:68:23 | file | ArchiveApiPathTraversal.rb:10:11:10:16 | call to params : | ArchiveApiPathTraversal.rb:68:20:68:23 | file | This path depends on $@. | ArchiveApiPathTraversal.rb:10:11:10:16 | call to params | a user-provided value |
|
||||
| ArchiveApiPathTraversal.rb:76:19:76:26 | filename | ArchiveApiPathTraversal.rb:15:9:15:14 | call to params : | ArchiveApiPathTraversal.rb:76:19:76:26 | filename | This path depends on $@. | ArchiveApiPathTraversal.rb:15:9:15:14 | call to params | a user-provided value |
|
||||
| tainted_path.rb:5:26:5:29 | path | tainted_path.rb:4:12:4:17 | call to params : | tainted_path.rb:5:26:5:29 | path | This path depends on $@. | tainted_path.rb:4:12:4:17 | call to params | a user-provided value |
|
||||
| tainted_path.rb:11:26:11:29 | path | tainted_path.rb:10:31:10:36 | call to params : | tainted_path.rb:11:26:11:29 | path | This path depends on $@. | tainted_path.rb:10:31:10:36 | call to params | a user-provided value |
|
||||
| tainted_path.rb:17:26:17:29 | path | tainted_path.rb:16:28:16:33 | call to params : | tainted_path.rb:17:26:17:29 | path | This path depends on $@. | tainted_path.rb:16:28:16:33 | call to params | a user-provided value |
|
||||
|
||||
Reference in New Issue
Block a user