mirror of
https://github.com/github/codeql.git
synced 2026-05-05 21:55:19 +02:00
Ruby: Add rb/tainted-format-string query
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for reasoning about
|
||||
* format injections, as well as extension points for adding your own.
|
||||
*/
|
||||
|
||||
private import ruby
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.RemoteFlowSources
|
||||
private import codeql.ruby.ApiGraphs
|
||||
|
||||
module TaintedFormatString {
|
||||
/**
|
||||
* A data flow source for format injections.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A data flow sink for format injections.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sanitizer for format injections.
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/** A source of remote user input, considered as a flow source for format injection. */
|
||||
class RemoteSource extends Source {
|
||||
RemoteSource() { this instanceof RemoteFlowSource }
|
||||
}
|
||||
|
||||
/**
|
||||
* A format argument to a printf-like function, considered as a flow sink for format injection.
|
||||
*/
|
||||
class FormatSink extends Sink {
|
||||
FormatSink() {
|
||||
exists(PrintfCall printf |
|
||||
this = printf.getFormatString() and
|
||||
// exclude trivial case where there are no arguments to interpolate
|
||||
exists(printf.getFormatArgument(_))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `printf` or `sprintf`.
|
||||
*/
|
||||
abstract class PrintfCall extends DataFlow::CallNode {
|
||||
// We assume that most printf-like calls have the signature f(format_string, args...)
|
||||
DataFlow::Node getFormatString() { result = this.getArgument(0) }
|
||||
|
||||
DataFlow::Node getFormatArgument(int n) { n > 0 and result = this.getArgument(n) }
|
||||
}
|
||||
|
||||
class KernelPrintfCall extends PrintfCall {
|
||||
KernelPrintfCall() {
|
||||
this = API::getTopLevelMember("Kernel").getAMethodCall("printf")
|
||||
or
|
||||
this.asExpr().getExpr() instanceof UnknownMethodCall and
|
||||
this.getMethodName() = "printf"
|
||||
}
|
||||
|
||||
// Kernel#printf supports two signatures:
|
||||
// printf(io, string, ...)
|
||||
// printf(string, ...)
|
||||
override DataFlow::Node getFormatString() { result = this.getArgument([0, 1]) }
|
||||
}
|
||||
|
||||
class KernelSprintfCall extends PrintfCall {
|
||||
KernelSprintfCall() {
|
||||
this = API::getTopLevelMember("Kernel").getAMethodCall("sprintf")
|
||||
or
|
||||
this.asExpr().getExpr() instanceof UnknownMethodCall and
|
||||
this.getMethodName() = "sprintf"
|
||||
}
|
||||
}
|
||||
|
||||
class IOPrintfCall extends PrintfCall {
|
||||
IOPrintfCall() { this = API::getTopLevelMember("IO").getInstance().getAMethodCall("printf") }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for reasoning about format
|
||||
* injections.
|
||||
*
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `TaintedFormatString::Configuration` is needed, otherwise
|
||||
* `TaintedFormatStringCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
import ruby
|
||||
import codeql.ruby.DataFlow
|
||||
import codeql.ruby.TaintTracking
|
||||
import TaintedFormatStringCustomizations::TaintedFormatString
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for format injections.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "TaintedFormatString" }
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Methods like <code>Kernel.printf</code> accept a format string that is used to format
|
||||
the remaining arguments by providing inline format specifiers. If the format string
|
||||
contains unsanitized input from an untrusted source, then that string may contain
|
||||
unexpected format specifiers that cause garbled output or throw an exception.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Either sanitize the input before including it in the format string, or use a
|
||||
<code>%s</code> specifier in the format string, and pass the untrusted data as corresponding
|
||||
argument.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
The following program snippet logs information about an unauthorized access attempt. The
|
||||
log message includes the user name, and the user's IP address is passed as an additional
|
||||
argument to <code>Kernel.printf</code> to be appended to the message:
|
||||
</p>
|
||||
<sample src="examples/tainted_format_string_bad.rb"/>
|
||||
<p>
|
||||
However, if a malicious user provides a format specified such as <code>%s</code> as their
|
||||
user name, <code>Kernel.printf</code> throw an exception that there are too few arguments
|
||||
to satisfy the format. This can result in denial of service or leaking of internal
|
||||
information to the attacker via a stack trace.
|
||||
</p>
|
||||
<p>
|
||||
Instead, the user name should be included using the <code>%s</code> specifier:
|
||||
</p>
|
||||
<sample src="examples/tainted_format_string_good.rb"/>
|
||||
|
||||
<p>
|
||||
Alternatively, a method such as <code>Kernel.puts</code> should be used, which does not
|
||||
apply string formatting to its arguments.
|
||||
</p>
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>Ruby documentation for <a href="https://docs.ruby-lang.org/en/3.1/Kernel.html#method-i-sprintf">format strings</a>.</li>
|
||||
<li>Common Weakness Enumeration: <a href="https://cwe.mitre.org/data/definitions/134.html">CWE-134</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
20
ruby/ql/src/queries/security/cwe-134/TaintedFormatString.ql
Normal file
20
ruby/ql/src/queries/security/cwe-134/TaintedFormatString.ql
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @name Use of externally-controlled format string
|
||||
* @description Using external input in format strings can lead to garbled output.
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 7.3
|
||||
* @precision high
|
||||
* @id rb/tainted-format-string
|
||||
* @tags security
|
||||
* external/cwe/cwe-134
|
||||
*/
|
||||
|
||||
import ruby
|
||||
import codeql.ruby.security.TaintedFormatStringQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "$@ flows here and is used in a format string.",
|
||||
source.getNode(), "User-provided value"
|
||||
@@ -0,0 +1,5 @@
|
||||
class UsersController < ActionController::Base
|
||||
def index
|
||||
printf("Unauthorised access attempt by #{params[:user]}: %s", request.ip)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class UsersController < ActionController::Base
|
||||
def index
|
||||
printf("Unauthorised access attempt by %s: %s", params[:user], request.ip)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,46 @@
|
||||
edges
|
||||
| tainted_format_string.rb:4:12:4:17 | call to params : | tainted_format_string.rb:4:12:4:26 | ...[...] |
|
||||
| tainted_format_string.rb:5:19:5:24 | call to params : | tainted_format_string.rb:5:19:5:33 | ...[...] |
|
||||
| tainted_format_string.rb:10:23:10:28 | call to params : | tainted_format_string.rb:10:23:10:37 | ...[...] |
|
||||
| tainted_format_string.rb:11:30:11:35 | call to params : | tainted_format_string.rb:11:30:11:44 | ...[...] |
|
||||
| tainted_format_string.rb:13:23:13:28 | call to params : | tainted_format_string.rb:13:23:13:37 | ...[...] |
|
||||
| tainted_format_string.rb:14:30:14:35 | call to params : | tainted_format_string.rb:14:30:14:44 | ...[...] |
|
||||
| tainted_format_string.rb:16:27:16:32 | call to params : | tainted_format_string.rb:16:27:16:41 | ...[...] |
|
||||
| tainted_format_string.rb:17:20:17:25 | call to params : | tainted_format_string.rb:17:20:17:34 | ...[...] |
|
||||
| tainted_format_string.rb:23:19:23:24 | call to params : | tainted_format_string.rb:23:19:23:33 | ...[...] |
|
||||
| tainted_format_string.rb:29:32:29:37 | call to params : | tainted_format_string.rb:29:32:29:46 | ...[...] : |
|
||||
| tainted_format_string.rb:29:32:29:46 | ...[...] : | tainted_format_string.rb:29:12:29:46 | ... + ... |
|
||||
nodes
|
||||
| tainted_format_string.rb:4:12:4:17 | call to params : | semmle.label | call to params : |
|
||||
| tainted_format_string.rb:4:12:4:26 | ...[...] | semmle.label | ...[...] |
|
||||
| tainted_format_string.rb:5:19:5:24 | call to params : | semmle.label | call to params : |
|
||||
| tainted_format_string.rb:5:19:5:33 | ...[...] | semmle.label | ...[...] |
|
||||
| tainted_format_string.rb:10:23:10:28 | call to params : | semmle.label | call to params : |
|
||||
| tainted_format_string.rb:10:23:10:37 | ...[...] | semmle.label | ...[...] |
|
||||
| tainted_format_string.rb:11:30:11:35 | call to params : | semmle.label | call to params : |
|
||||
| tainted_format_string.rb:11:30:11:44 | ...[...] | semmle.label | ...[...] |
|
||||
| tainted_format_string.rb:13:23:13:28 | call to params : | semmle.label | call to params : |
|
||||
| tainted_format_string.rb:13:23:13:37 | ...[...] | semmle.label | ...[...] |
|
||||
| tainted_format_string.rb:14:30:14:35 | call to params : | semmle.label | call to params : |
|
||||
| tainted_format_string.rb:14:30:14:44 | ...[...] | semmle.label | ...[...] |
|
||||
| tainted_format_string.rb:16:27:16:32 | call to params : | semmle.label | call to params : |
|
||||
| tainted_format_string.rb:16:27:16:41 | ...[...] | semmle.label | ...[...] |
|
||||
| tainted_format_string.rb:17:20:17:25 | call to params : | semmle.label | call to params : |
|
||||
| tainted_format_string.rb:17:20:17:34 | ...[...] | semmle.label | ...[...] |
|
||||
| tainted_format_string.rb:23:19:23:24 | call to params : | semmle.label | call to params : |
|
||||
| tainted_format_string.rb:23:19:23:33 | ...[...] | semmle.label | ...[...] |
|
||||
| tainted_format_string.rb:29:12:29:46 | ... + ... | semmle.label | ... + ... |
|
||||
| tainted_format_string.rb:29:32:29:37 | call to params : | semmle.label | call to params : |
|
||||
| tainted_format_string.rb:29:32:29:46 | ...[...] : | semmle.label | ...[...] : |
|
||||
subpaths
|
||||
#select
|
||||
| tainted_format_string.rb:4:12:4:26 | ...[...] | tainted_format_string.rb:4:12:4:17 | call to params : | tainted_format_string.rb:4:12:4:26 | ...[...] | $@ flows here and is used in a format string. | tainted_format_string.rb:4:12:4:17 | call to params | User-provided value |
|
||||
| tainted_format_string.rb:5:19:5:33 | ...[...] | tainted_format_string.rb:5:19:5:24 | call to params : | tainted_format_string.rb:5:19:5:33 | ...[...] | $@ flows here and is used in a format string. | tainted_format_string.rb:5:19:5:24 | call to params | User-provided value |
|
||||
| tainted_format_string.rb:10:23:10:37 | ...[...] | tainted_format_string.rb:10:23:10:28 | call to params : | tainted_format_string.rb:10:23:10:37 | ...[...] | $@ flows here and is used in a format string. | tainted_format_string.rb:10:23:10:28 | call to params | User-provided value |
|
||||
| tainted_format_string.rb:11:30:11:44 | ...[...] | tainted_format_string.rb:11:30:11:35 | call to params : | tainted_format_string.rb:11:30:11:44 | ...[...] | $@ flows here and is used in a format string. | tainted_format_string.rb:11:30:11:35 | call to params | User-provided value |
|
||||
| tainted_format_string.rb:13:23:13:37 | ...[...] | tainted_format_string.rb:13:23:13:28 | call to params : | tainted_format_string.rb:13:23:13:37 | ...[...] | $@ flows here and is used in a format string. | tainted_format_string.rb:13:23:13:28 | call to params | User-provided value |
|
||||
| tainted_format_string.rb:14:30:14:44 | ...[...] | tainted_format_string.rb:14:30:14:35 | call to params : | tainted_format_string.rb:14:30:14:44 | ...[...] | $@ flows here and is used in a format string. | tainted_format_string.rb:14:30:14:35 | call to params | User-provided value |
|
||||
| tainted_format_string.rb:16:27:16:41 | ...[...] | tainted_format_string.rb:16:27:16:32 | call to params : | tainted_format_string.rb:16:27:16:41 | ...[...] | $@ flows here and is used in a format string. | tainted_format_string.rb:16:27:16:32 | call to params | User-provided value |
|
||||
| tainted_format_string.rb:17:20:17:34 | ...[...] | tainted_format_string.rb:17:20:17:25 | call to params : | tainted_format_string.rb:17:20:17:34 | ...[...] | $@ flows here and is used in a format string. | tainted_format_string.rb:17:20:17:25 | call to params | User-provided value |
|
||||
| tainted_format_string.rb:23:19:23:33 | ...[...] | tainted_format_string.rb:23:19:23:24 | call to params : | tainted_format_string.rb:23:19:23:33 | ...[...] | $@ flows here and is used in a format string. | tainted_format_string.rb:23:19:23:24 | call to params | User-provided value |
|
||||
| tainted_format_string.rb:29:12:29:46 | ... + ... | tainted_format_string.rb:29:32:29:37 | call to params : | tainted_format_string.rb:29:12:29:46 | ... + ... | $@ flows here and is used in a format string. | tainted_format_string.rb:29:32:29:37 | call to params | User-provided value |
|
||||
@@ -0,0 +1 @@
|
||||
queries/security/cwe-134/TaintedFormatString.ql
|
||||
@@ -0,0 +1,31 @@
|
||||
class UsersController < ActionController::Base
|
||||
|
||||
def show
|
||||
printf(params[:format], arg) # BAD
|
||||
Kernel.printf(params[:format], arg) # BAD
|
||||
|
||||
printf(params[:format]) # GOOD
|
||||
Kernel.printf(params[:format]) # GOOD
|
||||
|
||||
printf(IO.new(1), params[:format], arg) # BAD
|
||||
Kernel.printf(IO.new(1), params[:format], arg) # BAD
|
||||
|
||||
printf(IO.new(1), params[:format]) # GOOD [FALSE POSITIVE]
|
||||
Kernel.printf(IO.new(1), params[:format]) # GOOD [FALSE POSITIVE]
|
||||
|
||||
str1 = Kernel.sprintf(params[:format], arg) # BAD
|
||||
str2 = sprintf(params[:format], arg) # BAD
|
||||
|
||||
str1 = Kernel.sprintf(params[:format]) # GOOD
|
||||
str2 = sprintf(params[:format]) # GOOD
|
||||
|
||||
stdout = IO.new 1
|
||||
stdout.printf(params[:format], arg) # BAD
|
||||
|
||||
stdout.printf(params[:format]) # GOOD
|
||||
|
||||
# Taint via string concatenation
|
||||
|
||||
printf("A log message: " + params[:format], arg) # BAD
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user