Skip to content
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: Use the new dataflow API for checked in queries #14124

Merged
merged 51 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
ce35d69
Ruby: configsig rb/hardcoded-data-interpreted-as-code
alexrford Aug 22, 2023
2a2f21d
Ruby: configsig rb/clear-text-logging-sensitive-data
alexrford Aug 22, 2023
6fa267a
Ruby: configsig rb/clear-text-storage-sensitive-data
alexrford Aug 23, 2023
b1a49dd
Ruby: configsig rb/code-injection
alexrford Aug 23, 2023
377570f
Ruby: configsig rb/command-line-injection
alexrford Aug 24, 2023
2536f1a
Ruby: configsig rb/user-controlled-bypass
alexrford Aug 24, 2023
c973fc1
Ruby: configsig rb/http-to-file-access
alexrford Aug 31, 2023
a8ad0d8
Ruby: renames for rb/insecure-download
alexrford Aug 31, 2023
d46eceb
Ruby: configsig rb/kernel-open
alexrford Sep 1, 2023
eb34bbb
Ruby: renames for rb/ldap-injection
alexrford Sep 1, 2023
867e47b
Ruby: renames for rb/log-injection
alexrford Sep 1, 2023
ad2bbfb
Ruby: configsig rb/path-injection
alexrford Sep 1, 2023
593d9a4
Ruby: configsig rb/reflected-xss
alexrford Sep 1, 2023
df91735
Ruby: configsig rb/sensitive-get-query
alexrford Sep 1, 2023
ba8ff07
Ruby: configsig rb/request-forgery
alexrford Sep 1, 2023
bf1cb33
Ruby: configsig rb/sql-injection
alexrford Sep 1, 2023
030aae5
Ruby: configsig rb/stack-trace-exposure
alexrford Sep 1, 2023
f5e4339
Ruby: renames for rb/stored-xss
alexrford Sep 1, 2023
0a73ebd
Ruby: configsig rb/tainted-format-string
alexrford Sep 1, 2023
3e23a6e
Ruby: configsig rb/server-side-template-injection
alexrford Sep 1, 2023
461bc0d
Ruby: configsig rb/unsafe-code-construction
alexrford Sep 1, 2023
8ad6c72
Ruby: configsig rb/unsafe-deserialization
alexrford Sep 3, 2023
f03f670
Ruby: configsig rb/html-constructed-from-input
alexrford Sep 3, 2023
f79796a
Ruby: configsig rb/shell-command-constructed-from-input
alexrford Sep 3, 2023
42cd586
Ruby: configsig rb/url-redirection
alexrford Sep 3, 2023
77f3a70
Ruby: renames for rb/xpath-injection
alexrford Sep 3, 2023
04d3d04
Ruby: configsig rb/regex/badly-anchored-regexp
alexrford Sep 3, 2023
494b7b3
Ruby: configsig rb/polynomial-redos
alexrford Sep 3, 2023
7445fc4
Ruby: configsig rb/regexp-injection
alexrford Sep 3, 2023
ebf2a2e
Ruby: configsig rb/unicode-bypass-validation
alexrford Sep 3, 2023
b6d12f8
Ruby: configsig rb/zip-slip
alexrford Sep 3, 2023
4d1684e
Ruby: configsig rb/overly-permissive-file
alexrford Sep 3, 2023
cdc788b
Ruby: configsig rb/hardcoded-credentials
alexrford Sep 3, 2023
39af2d2
Ruby: configsig rb/user-controlled-file-decompression
alexrford Sep 3, 2023
6c06def
Ruby: configsig rb/manually-checking-http-verb
alexrford Sep 3, 2023
f24102e
Ruby: configsig rb/weak-params
alexrford Sep 3, 2023
956207b
Ruby: configsig rb/meta/tainted-nodes
alexrford Sep 3, 2023
73ed569
Ruby: configsig rb/xxe
alexrford Sep 3, 2023
e399eac
Ruby: changenote for using new dataflow api
alexrford Sep 3, 2023
bf6837c
Revert "Ruby: configsig rb/http-to-file-access"
alexrford Sep 3, 2023
9885173
Revert "Ruby: configsig rb/tainted-format-string"
alexrford Sep 3, 2023
dfc3b33
Ruby: Use a newtype instead of DataFlow::FlowState for unicode-bypass…
alexrford Sep 7, 2023
0d7d5a3
Ruby: Use a newtype instead of DataFlow::FlowState for code-injection
alexrford Sep 7, 2023
75fdde5
Ruby: Use a newtype instead of DataFlow::FlowState for hardcoded-data
alexrford Sep 7, 2023
a893911
Ruby: Use a newtype instead of DataFlow::FlowState for insecure-download
alexrford Sep 7, 2023
13300a2
Ruby: un-private PathGraph imports
alexrford Sep 7, 2023
0aee7f6
Ruby: qlformat
alexrford Sep 7, 2023
4a01de1
Ruby: avoid toString in query warning
alexrford Sep 7, 2023
947fa0d
Ruby: fix qldoc warnings
alexrford Sep 7, 2023
5b013dd
Merge branch 'main' into rb/dataflow-query-refactor
alexrford Sep 7, 2023
b5ec99c
Ruby: fix missing qldoc
alexrford Sep 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,64 @@
PostValidation() { this = "PostValidation" }
}

/**
* A state signifying if a logical validation has been performed or not.
*/
private newtype ValidationState =
// A state signifying that a logical validation has not been performed.
PreValidationState() or
// A state signifying that a logical validation has been performed.
PostValidationState()

/**
* A taint-tracking configuration for detecting "Unicode transformation mishandling" vulnerabilities.
*
* This configuration uses two flow states, `PreValidation` and `PostValidation`,
* to track the requirement that a logical validation has been performed before the Unicode Transformation.
* DEPRECATED: Use `UnicodeBypassValidationFlow`
*/
class Configuration extends TaintTracking::Configuration {
deprecated class Configuration extends TaintTracking::Configuration {
Configuration() { this = "UnicodeBypassValidation" }

private ValidationState convertState(DataFlow::FlowState state) {
state instanceof PreValidation and result = PreValidationState()
or
state instanceof PostValidation and result = PostValidationState()
}

override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
source instanceof RemoteFlowSource and state instanceof PreValidation
UnicodeBypassValidationConfig::isSource(source, this.convertState(state))
}

override predicate isAdditionalTaintStep(
DataFlow::Node nodeFrom, DataFlow::FlowState stateFrom, DataFlow::Node nodeTo,
DataFlow::FlowState stateTo
) {
UnicodeBypassValidationConfig::isAdditionalFlowStep(nodeFrom, this.convertState(stateFrom),
nodeTo, this.convertState(stateTo))
}

/* A Unicode Tranformation (Unicode tranformation) is considered a sink when the algorithm used is either NFC or NFKC. */
override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
UnicodeBypassValidationConfig::isSink(sink, this.convertState(state))
}
}

/**
* A taint-tracking configuration for detecting "Unicode transformation mishandling" vulnerabilities.
*
* This configuration uses two flow states, `PreValidation` and `PostValidation`,
* to track the requirement that a logical validation has been performed before the Unicode Transformation.
*/
private module UnicodeBypassValidationConfig implements DataFlow::StateConfigSig {
class FlowState = ValidationState;

predicate isSource(DataFlow::Node source, FlowState state) {
source instanceof RemoteFlowSource and state = PreValidationState()
}

predicate isAdditionalFlowStep(
DataFlow::Node nodeFrom, FlowState stateFrom, DataFlow::Node nodeTo, FlowState stateTo
) {
(
exists(Escaping escaping | nodeFrom = escaping.getAnInput() and nodeTo = escaping.getOutput())
Expand Down Expand Up @@ -75,12 +117,12 @@
nodeTo = cn
)
) and
stateFrom instanceof PreValidation and
stateTo instanceof PostValidation
stateFrom = PreValidationState() and
stateTo = PostValidationState()
}

/* A Unicode Tranformation (Unicode tranformation) is considered a sink when the algorithm used is either NFC or NFKC. */

Check warning

Code scanning / CodeQL

Block comment that is not QLDoc Warning

Block comment could be QLDoc for
the below code
.
override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
predicate isSink(DataFlow::Node sink, FlowState state) {
(
exists(DataFlow::CallNode cn |
cn.getMethodName() = "unicode_normalize" and
Expand Down Expand Up @@ -118,6 +160,11 @@
sink = cn.getArgument(0)
)
) and
state instanceof PostValidation
state = PostValidationState()
}
}

/**
* Taint-tracking configuration for detecting "Unicode transformation mishandling" vulnerabilities.
*/
module UnicodeBypassValidationFlow = TaintTracking::GlobalWithState<UnicodeBypassValidationConfig>;
30 changes: 29 additions & 1 deletion ruby/ql/lib/codeql/ruby/experimental/ZipSlipQuery.qll
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ private import codeql.ruby.ApiGraphs
/**
* A taint-tracking configuration for reasoning about zip slip
* vulnerabilities.
* DEPRECATED: Use `ZipSlipFlow`
*/
class Configuration extends TaintTracking::Configuration {
deprecated class Configuration extends TaintTracking::Configuration {
Configuration() { this = "ZipSlip" }

override predicate isSource(DataFlow::Node source) { source instanceof ZipSlip::Source }
Expand All @@ -36,3 +37,30 @@ class Configuration extends TaintTracking::Configuration {

override predicate isSanitizer(DataFlow::Node node) { node instanceof ZipSlip::Sanitizer }
}

private module ZipSlipConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof ZipSlip::Source }

predicate isSink(DataFlow::Node sink) { sink instanceof ZipSlip::Sink }

/**
* This should actually be
* `and cn = API::getTopLevelMember("Gem").getMember("Package").getMember("TarReader").getMember("Entry").getAMethodCall("full_name")` and similar for other classes
* but I couldn't make it work so there's only check for the method name called on the entry. It is `full_name` for `Gem::Package::TarReader::Entry` and `Zlib`
* and `name` for `Zip::File`
*/
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
exists(DataFlow::CallNode cn |
cn.getReceiver() = nodeFrom and
cn.getMethodName() in ["full_name", "name"] and
cn = nodeTo
)
}

predicate isBarrier(DataFlow::Node node) { node instanceof ZipSlip::Sanitizer }
}

/**
* Taint-tracking for reasoning about zip slip vulnerabilities.
*/
module ZipSlipFlow = TaintTracking::Global<ZipSlipConfig>;
32 changes: 25 additions & 7 deletions ruby/ql/lib/codeql/ruby/security/CleartextLoggingQuery.qll
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,51 @@
* Provides a taint-tracking configuration for "Clear-text logging of sensitive information".
*
* Note, for performance reasons: only import this file if
* `CleartextLogging::Configuration` is needed, otherwise
* `CleartextLoggingFlow` is needed, otherwise
* `CleartextLoggingCustomizations` should be imported instead.
*/

private import codeql.ruby.AST
private import codeql.ruby.DataFlow
private import codeql.ruby.TaintTracking
import CleartextLoggingCustomizations::CleartextLogging
private import CleartextLoggingCustomizations::CleartextLogging as CleartextLogging
private import CleartextLoggingCustomizations::CleartextLogging as CL

/**
* A taint-tracking configuration for detecting "Clear-text logging of sensitive information".
* DEPRECATED: Use `CleartextLoggingFlow` instead
*/
class Configuration extends TaintTracking::Configuration {
deprecated class Configuration extends TaintTracking::Configuration {
Configuration() { this = "CleartextLogging" }

override predicate isSource(DataFlow::Node source) { source instanceof CleartextLogging::Source }
override predicate isSource(DataFlow::Node source) { source instanceof CL::Source }

override predicate isSink(DataFlow::Node sink) { sink instanceof CleartextLogging::Sink }
override predicate isSink(DataFlow::Node sink) { sink instanceof CL::Sink }

override predicate isSanitizer(DataFlow::Node node) {
super.isSanitizer(node)
or
node instanceof CleartextLogging::Sanitizer
node instanceof CL::Sanitizer
}

override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
CleartextLogging::isAdditionalTaintStep(nodeFrom, nodeTo)
CL::isAdditionalTaintStep(nodeFrom, nodeTo)
}
}

private module Config implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof CL::Source }

predicate isSink(DataFlow::Node sink) { sink instanceof CL::Sink }

predicate isBarrier(DataFlow::Node node) { node instanceof CL::Sanitizer }

predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
CL::isAdditionalTaintStep(nodeFrom, nodeTo)
}
}

/**
* Taint-tracking for detecting "Clear-text logging of sensitive information".
*/
module CleartextLoggingFlow = TaintTracking::Global<Config>;
34 changes: 26 additions & 8 deletions ruby/ql/lib/codeql/ruby/security/CleartextStorageQuery.qll
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,50 @@
* Provides a taint-tracking configuration for "Clear-text storage of sensitive information".
*
* Note, for performance reasons: only import this file if
* `Configuration` is needed, otherwise `CleartextStorageCustomizations` should be
* imported instead.
* `CleartextStorageFlow` is needed, otherwise
* `CleartextStorageCustomizations` should be imported instead.
*/

private import codeql.ruby.AST
private import codeql.ruby.DataFlow
private import codeql.ruby.TaintTracking
private import CleartextStorageCustomizations::CleartextStorage as CleartextStorage
private import CleartextStorageCustomizations::CleartextStorage as CS

/**
* A taint-tracking configuration for detecting "Clear-text storage of sensitive information".
* DEPRECATED: Use `CleartextStorageFlow` instead
*/
class Configuration extends TaintTracking::Configuration {
deprecated class Configuration extends TaintTracking::Configuration {
Configuration() { this = "CleartextStorage" }

override predicate isSource(DataFlow::Node source) { source instanceof CleartextStorage::Source }
override predicate isSource(DataFlow::Node source) { source instanceof CS::Source }

override predicate isSink(DataFlow::Node sink) { sink instanceof CleartextStorage::Sink }
override predicate isSink(DataFlow::Node sink) { sink instanceof CS::Sink }

override predicate isSanitizer(DataFlow::Node node) {
super.isSanitizer(node)
or
node instanceof CleartextStorage::Sanitizer
node instanceof CS::Sanitizer
}

override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
CleartextStorage::isAdditionalTaintStep(nodeFrom, nodeTo)
CS::isAdditionalTaintStep(nodeFrom, nodeTo)
}
}

private module Config implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof CS::Source }

predicate isSink(DataFlow::Node sink) { sink instanceof CS::Sink }

predicate isBarrier(DataFlow::Node node) { node instanceof CS::Sanitizer }

predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
CS::isAdditionalTaintStep(nodeFrom, nodeTo)
}
}

/**
* Taint-tracking for detecting "Clear-text storage of sensitive information".
*/
module CleartextStorageFlow = TaintTracking::Global<Config>;
86 changes: 76 additions & 10 deletions ruby/ql/lib/codeql/ruby/security/CodeInjectionCustomizations.qll
Original file line number Diff line number Diff line change
Expand Up @@ -13,38 +13,97 @@
module CodeInjection {
/** Flow states used to distinguish whether an attacker controls the entire string. */
module FlowState {
/** Flow state used for normal tainted data, where an attacker might only control a substring. */
DataFlow::FlowState substring() { result = "substring" }
/**
* Flow state used for normal tainted data, where an attacker might only control a substring.
* DEPRECATED: Use `Full()`
*/
deprecated DataFlow::FlowState substring() { result = "substring" }

/**
* Flow state used for data that is entirely controlled by the attacker.
* DEPRECATED: Use `Full()`
*/
deprecated DataFlow::FlowState full() { result = "full" }

/** Flow state used for data that is entirely controlled by the attacker. */
DataFlow::FlowState full() { result = "full" }
private newtype TState =
TFull() or
TSubString()

/** A flow state used to distinguish whether an attacker controls the entire string. */
class State extends TState {
string toString() { result = this.getStringRepresentation() }

Check warning on line 34 in ruby/ql/lib/codeql/ruby/security/CodeInjectionCustomizations.qll

View workflow job for this annotation

GitHub Actions / qldoc

Missing QLdoc for member-predicate CodeInjectionCustomizations::CodeInjection::FlowState::State::toString/0

/**
* Gets a canonical string representation of this state.
*/
string getStringRepresentation() {
this = TSubString() and result = "substring"
or
this = TFull() and result = "full"
}
}

/**
* A flow state used for normal tainted data, where an attacker might only control a substring.
*/
class SubString extends State, TSubString { }

/**
* A flow state used for data that is entirely controlled by the attacker.
*/
class Full extends State, TFull { }
}

/**
* A data flow source for "Code injection" vulnerabilities.
*/
abstract class Source extends DataFlow::Node {
/**
* Gets a flow state for which this is a source.
* DEPRECATED: Use `getAState()`
*/
deprecated DataFlow::FlowState getAFlowState() {
result = [FlowState::substring(), FlowState::full()]
}

/** Gets a flow state for which this is a source. */
DataFlow::FlowState getAFlowState() { result = [FlowState::substring(), FlowState::full()] }
FlowState::State getAState() {
result instanceof FlowState::SubString or result instanceof FlowState::Full
}
}

/**
* A data flow sink for "Code injection" vulnerabilities.
*/
abstract class Sink extends DataFlow::Node {
/**
* Holds if this sink is safe for an attacker that only controls a substring.
* DEPRECATED: Use `getAState()`
*/
deprecated DataFlow::FlowState getAFlowState() {
result = [FlowState::substring(), FlowState::full()]
}

/** Holds if this sink is safe for an attacker that only controls a substring. */
DataFlow::FlowState getAFlowState() { result = [FlowState::substring(), FlowState::full()] }
FlowState::State getAState() { any() }
}

/**
* A sanitizer for "Code injection" vulnerabilities.
*/
abstract class Sanitizer extends DataFlow::Node {
/**
* Gets a flow state for which this is a sanitizer.
* Sanitizes all states if the result is empty.
* DEPRECATED: Use `getAState()`
*/
deprecated DataFlow::FlowState getAFlowState() { none() }

/**
* Gets a flow state for which this is a sanitizer.
* Sanitizes all states if the result is empty.
*/
DataFlow::FlowState getAFlowState() { none() }
FlowState::State getAState() { none() }
}

/**
Expand All @@ -67,12 +126,17 @@

CodeExecutionAsSink() { this = c.getCode() }

/** Gets a flow state for which this is a sink. */
override DataFlow::FlowState getAFlowState() {
deprecated override DataFlow::FlowState getAFlowState() {
if c.runsArbitraryCode()
then result = [FlowState::substring(), FlowState::full()] // If it runs arbitrary code then it's always vulnerable.
else result = FlowState::full() // If it "just" loads something, then it's only vulnerable if the attacker controls the entire string.
}

override FlowState::State getAState() {
if c.runsArbitraryCode()
then any() // If it runs arbitrary code then it's always vulnerable.
else result instanceof FlowState::Full // If it "just" loads something, then it's only vulnerable if the attacker controls the entire string.
}
}

private import codeql.ruby.AST as Ast
Expand All @@ -92,6 +156,8 @@
)
}

override DataFlow::FlowState getAFlowState() { result = FlowState::full() }
deprecated override DataFlow::FlowState getAFlowState() { result = FlowState::full() }

override FlowState::State getAState() { result instanceof FlowState::Full }
}
}
Loading
Loading