Merge remote-tracking branch 'origin/main' into rb/sensitive-get-query

This commit is contained in:
Alex Ford
2022-10-14 10:50:09 +01:00
536 changed files with 6032 additions and 2271 deletions

View File

@@ -216,7 +216,7 @@ struct Visitor<'a> {
schema: &'a NodeTypeMap,
/// A stack for gathering information from child nodes. Whenever a node is
/// entered the parent's [Label], child counter, and an empty list is pushed.
/// All children append their data to the the list. When the visitor leaves a
/// All children append their data to the list. When the visitor leaves a
/// node the list containing the child data is popped from the stack and
/// matched against the dbscheme for the node. If the expectations are met
/// the corresponding row definitions are added to the trap_output.

View File

@@ -68,7 +68,6 @@ fn main() -> std::io::Result<()> {
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("ruby_extractor=warn")),
)
.init();
tracing::warn!("Support for Ruby is currently in Beta: https://codeql.github.com/docs/codeql-overview/supported-languages-and-frameworks/");
let num_threads = num_codeql_threads();
tracing::info!(
"Using {} {}",

View File

@@ -43,7 +43,7 @@ pub enum FieldTypeInfo {
},
/// The field can be one of several tokens, so the db type will be an `int`
/// with a `case @foo.kind` for each possiblity.
/// with a `case @foo.kind` for each possibility.
ReservedWordInt(BTreeMap<String, (usize, String)>),
}

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Calls to `params` in `ActionMailer` classes are now treated as sources of remote user input.

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* `ActiveJob::Serializers.deserialize` is considered to be a code execution sink.

View File

@@ -0,0 +1,5 @@
---
category: minorAnalysis
---
* More sources of remote input arising from methods on `ActionDispatch::Request`
are now recognised.

View File

@@ -522,7 +522,8 @@ module API {
or
exists(TypeTrackerSpecific::TypeTrackerContent c |
TypeTrackerSpecific::basicLoadStep(node, ref, c) and
lbl = Label::content(c.getAStoreContent())
lbl = Label::content(c.getAStoreContent()) and
not c.isSingleton(any(DataFlow::Content::AttributeNameContent k))
)
// note: method calls are not handled here as there is no DataFlow::Node for the intermediate MkMethodAccessNode API node
}
@@ -638,7 +639,10 @@ module API {
isUse(src) and
t.start()
or
exists(TypeTracker t2 | result = trackUseNode(src, t2).track(t2, t))
exists(TypeTracker t2 |
result = trackUseNode(src, t2).track(t2, t) and
not result instanceof DataFlowPrivate::SelfParameterNode
)
}
/**
@@ -657,7 +661,11 @@ module API {
isDef(rhs) and
result = rhs.getALocalSource()
or
exists(TypeBackTracker t2 | result = trackDefNode(rhs, t2).backtrack(t2, t))
exists(TypeBackTracker t2, DataFlow::LocalSourceNode mid |
mid = trackDefNode(rhs, t2) and
not mid instanceof DataFlowPrivate::SelfParameterNode and
result = mid.backtrack(t2, t)
)
}
/** Gets a data flow node reaching the RHS of the given def node. */
@@ -890,7 +898,7 @@ module API {
/** Gets the `subclass` edge label. */
LabelSubclass subclass() { any() }
/** Gets the label representing the given keword argument/parameter. */
/** Gets the label representing the given keyword argument/parameter. */
LabelKeywordParameter keywordParameter(string name) { result.getName() = name }
/** Gets the label representing the `n`th positional argument/parameter. */

View File

@@ -300,6 +300,26 @@ module Http {
}
}
/** A kind of request input. */
class RequestInputKind extends string {
RequestInputKind() { this = ["parameter", "header", "body", "url", "cookie"] }
}
/** Input from the parameters of a request. */
RequestInputKind parameterInputKind() { result = "parameter" }
/** Input from the headers of a request. */
RequestInputKind headerInputKind() { result = "header" }
/** Input from the body of a request. */
RequestInputKind bodyInputKind() { result = "body" }
/** Input from the URL of a request. */
RequestInputKind urlInputKind() { result = "url" }
/** Input from the cookies of a request. */
RequestInputKind cookieInputKind() { result = "cookie" }
/**
* An access to a user-controlled HTTP request input. For example, the URL or body of a request.
* Instances of this class automatically become `RemoteFlowSource`s.
@@ -318,10 +338,28 @@ module Http {
/**
* Gets the kind of the accessed input,
* Can be one of "parameter", "header", "body", "url", "cookie".
*
* Note that this predicate is functional.
*/
string getKind() { result = super.getKind() }
RequestInputKind getKind() { result = super.getKind() }
/**
* Holds if this part of the request may be controlled by a third party,
* that is, an agent other than the one who sent the request.
*
* This is true for the URL, query parameters, and request body.
* These can be controlled by a malicious third party in the following scenarios:
*
* - The user clicks a malicious link or is otherwise redirected to a malicious URL.
* - The user visits a web site that initiates a form submission or AJAX request on their behalf.
*
* In these cases, the request is technically sent from the user's browser, but
* the user is not in direct control of the URL or POST body.
*
* Headers are never considered third-party controllable by this predicate, although the
* third party does have some control over the the Referer and Origin headers.
*/
predicate isThirdPartyControllable() {
this.getKind() = [parameterInputKind(), urlInputKind(), bodyInputKind()]
}
}
/** Provides a class for modeling new HTTP request inputs. */
@@ -343,10 +381,7 @@ module Http {
/**
* Gets the kind of the accessed input,
* Can be one of "parameter", "header", "body", "url", "cookie".
*
* Note that this predicate is functional.
*/
abstract string getKind();
abstract RequestInputKind getKind();
}
}
@@ -428,7 +463,7 @@ module Http {
override string getSourceType() { result = handler.getFramework() + " RoutedParameter" }
override string getKind() { result = "url" }
override RequestInputKind getKind() { result = parameterInputKind() }
}
/**

View File

@@ -5,6 +5,8 @@
private import codeql.ruby.frameworks.Core
private import codeql.ruby.frameworks.ActionCable
private import codeql.ruby.frameworks.ActionController
private import codeql.ruby.frameworks.ActiveJob
private import codeql.ruby.frameworks.ActionMailer
private import codeql.ruby.frameworks.ActiveRecord
private import codeql.ruby.frameworks.ActiveResource
private import codeql.ruby.frameworks.ActiveStorage

View File

@@ -106,7 +106,7 @@ class MethodCall extends Call instanceof MethodCallImpl {
final Block getBlock() { result = super.getBlockImpl() }
/**
* Holds if the safe nagivation operator (`&.`) is used in this call.
* Holds if the safe navigation operator (`&.`) is used in this call.
* ```rb
* foo&.empty?
* ```

View File

@@ -65,7 +65,7 @@ class ConstantValue extends TConstantValue {
/** Holds if this is the string value `s`. */
predicate isString(string s) { s = this.getString() }
/** Gets the symbol value (exluding the `:` prefix), if this is a symbol. */
/** Gets the symbol value (excluding the `:` prefix), if this is a symbol. */
string getSymbol() { this = TSymbol(result) }
/** Holds if this is the symbol value `:s`. */

View File

@@ -394,7 +394,7 @@ private module ResolveImpl {
/**
* The qualified names of the ancestors of a class/module. The ancestors should be an ordered list
* of the ancestores of `prepend`ed modules, the module itself , the ancestors or `include`d modules
* of the ancestors of `prepend`ed modules, the module itself , the ancestors or `include`d modules
* and the ancestors of the super class. The priority value only distinguishes the kind of ancestor,
* it does not order the ancestors within a group of the same kind. This is an over-approximation, however,
* computing the precise order is tricky because it depends on the evaluation/file loading order.
@@ -486,12 +486,15 @@ private import ResolveImpl
* methods evaluate the block in the context of some other module/class instead of
* the enclosing one.
*/
private ModuleBase enclosingModule(AstNode node) { result = parent*(node).getParent() }
private AstNode parent(AstNode n) {
result = n.getParent() and
not result instanceof ModuleBase and
not result instanceof Block
private ModuleBase enclosingModule(AstNode node) {
result = node.getParent()
or
exists(AstNode mid |
result = enclosingModule(mid) and
mid = node.getParent() and
not mid instanceof ModuleBase and
not mid instanceof Block
)
}
private Module getAncestors(Module m) {

View File

@@ -118,7 +118,11 @@ class Synthesis extends TSynthesis {
private class Desugared extends AstNode {
Desugared() { this = any(AstNode sugar).getDesugared() }
AstNode getADescendant() { result = this.getAChild*() }
AstNode getADescendant() {
result = this
or
result = this.getADescendant().getAChild()
}
}
/**
@@ -132,7 +136,10 @@ int desugarLevel(AstNode n) { result = count(Desugared desugared | n = desugared
* Holds if `n` appears in a context that is desugared. That is, a
* transitive, reflexive parent of `n` is a desugared node.
*/
predicate isInDesugaredContext(AstNode n) { n = any(AstNode sugar).getDesugared().getAChild*() }
predicate isInDesugaredContext(AstNode n) {
n = any(AstNode sugar).getDesugared() or
n = any(AstNode mid | isInDesugaredContext(mid)).getAChild()
}
/**
* Holds if `n` is a node that only exists as a result of desugaring some

View File

@@ -71,18 +71,22 @@ private predicate completionIsValidForStmt(AstNode n, Completion c) {
c = TReturnCompletion()
}
private AstNode getARescuableBodyChild() {
exists(Trees::BodyStmtTree bst | result = bst.getBodyChild(_, true) |
exists(bst.getARescue())
or
exists(bst.getEnsure())
)
or
result = getARescuableBodyChild().getAChild()
}
/**
* Holds if `c` happens in an exception-aware context, that is, it may be
* `rescue`d or `ensure`d. In such cases, we assume that the target of `c`
* may raise an exception (in addition to evaluating normally).
*/
private predicate mayRaise(Call c) {
exists(Trees::BodyStmtTree bst | c = bst.getBodyChild(_, true).getAChild*() |
exists(bst.getARescue())
or
exists(bst.getEnsure())
)
}
private predicate mayRaise(Call c) { c = getARescuableBodyChild() }
/** A completion of a statement or an expression. */
abstract class Completion extends TCompletion {

View File

@@ -885,7 +885,7 @@ module TestOutput {
/**
* Gets a string used to resolve ties in node and edge ordering.
*/
string getOrderDisambuigation() { result = "" }
string getOrderDisambiguation() { result = "" }
}
query predicate nodes(RelevantNode n, string attr, string val) {
@@ -900,7 +900,7 @@ module TestOutput {
order by
l.getFile().getBaseName(), l.getFile().getAbsolutePath(), l.getStartLine(),
l.getStartColumn(), l.getEndLine(), l.getEndColumn(), p.toString(),
p.getOrderDisambuigation()
p.getOrderDisambiguation()
)
).toString()
}
@@ -923,7 +923,7 @@ module TestOutput {
order by
l.getFile().getBaseName(), l.getFile().getAbsolutePath(), l.getStartLine(),
l.getStartColumn(), l.getEndLine(), l.getEndColumn(), t.toString(), s.toString(),
s.getOrderDisambuigation()
s.getOrderDisambiguation()
)
).toString()
}

View File

@@ -2,8 +2,6 @@
import codeql.ruby.AST
import codeql.ruby.DataFlow
private import codeql.ruby.frameworks.data.ModelsAsData
private import codeql.ruby.ApiGraphs
private import internal.FlowSummaryImpl as Impl
private import internal.DataFlowDispatch
private import internal.DataFlowPrivate
@@ -11,6 +9,7 @@ private import internal.DataFlowPrivate
// import all instances below
private module Summaries {
private import codeql.ruby.Frameworks
private import codeql.ruby.frameworks.data.ModelsAsData
}
class SummaryComponent = Impl::Public::SummaryComponent;
@@ -47,7 +46,7 @@ module SummaryComponent {
/**
* Gets a summary component that represents an element in a collection at a specific
* known index `cv`, or an uknown index.
* known index `cv`, or an unknown index.
*/
SummaryComponent elementKnownOrUnknown(ConstantValue cv) {
result = SC::content(TKnownOrUnknownElementContent(TKnownElementContent(cv)))
@@ -144,33 +143,3 @@ abstract class SimpleSummarizedCallable extends SummarizedCallable {
}
class RequiredSummaryComponentStack = Impl::Public::RequiredSummaryComponentStack;
private class SummarizedCallableFromModel extends SummarizedCallable {
string package;
string type;
string path;
SummarizedCallableFromModel() {
ModelOutput::relevantSummaryModel(package, type, path, _, _, _) and
this = package + ";" + type + ";" + path
}
override Call getACall() {
exists(API::MethodAccessNode base |
ModelOutput::resolvedSummaryBase(package, type, path, base) and
result = base.getCallNode().asExpr().getExpr()
)
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
exists(string kind |
ModelOutput::relevantSummaryModel(package, type, path, input, output, kind)
|
kind = "value" and
preservesValue = true
or
kind = "taint" and
preservesValue = false
)
}
}

View File

@@ -1,5 +1,5 @@
/**
* Provides an extension point for for modeling user-controlled data.
* Provides an extension point for modeling user-controlled data.
* Such data is often used as data-flow sources in security queries.
*/

View File

@@ -163,7 +163,9 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
predicate hasFlowTo(Node sink) {
sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode()
}
/**
* Holds if data may flow from some source to `sink` for this configuration.
@@ -836,13 +838,13 @@ private module Stage1 implements StageSig {
* by `revFlow`.
*/
pragma[nomagic]
predicate revFlowIsReadAndStored(Content c, Configuration conf) {
additional predicate revFlowIsReadAndStored(Content c, Configuration conf) {
revFlowConsCand(c, conf) and
revFlowStore(c, _, _, conf)
}
pragma[nomagic]
predicate viableReturnPosOutNodeCandFwd1(
additional predicate viableReturnPosOutNodeCandFwd1(
DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config
) {
fwdFlowReturnPosition(pos, _, config) and
@@ -858,7 +860,7 @@ private module Stage1 implements StageSig {
}
pragma[nomagic]
predicate viableParamArgNodeCandFwd1(
additional predicate viableParamArgNodeCandFwd1(
DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config
) {
viableParamArgEx(call, p, arg) and
@@ -905,7 +907,7 @@ private module Stage1 implements StageSig {
)
}
predicate revFlowState(FlowState state, Configuration config) {
additional predicate revFlowState(FlowState state, Configuration config) {
exists(NodeEx node |
sinkNode(node, state, config) and
revFlow(node, _, pragma[only_bind_into](config)) and
@@ -997,7 +999,7 @@ private module Stage1 implements StageSig {
)
}
predicate stats(
additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
@@ -1258,7 +1260,7 @@ private module MkStage<StageSig PrevStage> {
* argument.
*/
pragma[nomagic]
predicate fwdFlow(
additional predicate fwdFlow(
NodeEx node, FlowState state, Cc cc, ApOption argAp, Ap ap, Configuration config
) {
fwdFlow0(node, state, cc, argAp, ap, config) and
@@ -1482,7 +1484,7 @@ private module MkStage<StageSig PrevStage> {
* the access path of the returned value.
*/
pragma[nomagic]
predicate revFlow(
additional predicate revFlow(
NodeEx node, FlowState state, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
) {
revFlow0(node, state, toReturn, returnAp, ap, config) and
@@ -1660,7 +1662,7 @@ private module MkStage<StageSig PrevStage> {
)
}
predicate revFlow(NodeEx node, FlowState state, Configuration config) {
additional predicate revFlow(NodeEx node, FlowState state, Configuration config) {
revFlow(node, state, _, _, _, config)
}
@@ -1673,11 +1675,13 @@ private module MkStage<StageSig PrevStage> {
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
predicate revFlowAlias(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) }
additional predicate revFlowAlias(NodeEx node, Configuration config) {
revFlow(node, _, _, _, _, config)
}
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
revFlow(node, state, ap, config)
}
@@ -1698,7 +1702,7 @@ private module MkStage<StageSig PrevStage> {
)
}
predicate consCand(TypedContent tc, Ap ap, Configuration config) {
additional predicate consCand(TypedContent tc, Ap ap, Configuration config) {
revConsCand(tc, ap, config) and
validAp(ap, config)
}
@@ -1740,7 +1744,7 @@ private module MkStage<StageSig PrevStage> {
)
}
predicate stats(
additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
@@ -2925,12 +2929,17 @@ abstract private class PathNodeImpl extends PathNode {
result = this.getASuccessorImpl()
}
final PathNodeImpl getANonHiddenSuccessor() {
result = this.getASuccessorImpl().getASuccessorIfHidden*() and
not this.isHidden() and
pragma[nomagic]
private PathNodeImpl getANonHiddenSuccessor0() {
result = this.getASuccessorIfHidden*() and
not result.isHidden()
}
final PathNodeImpl getANonHiddenSuccessor() {
result = this.getASuccessorImpl().getANonHiddenSuccessor0() and
not this.isHidden()
}
abstract NodeEx getNodeEx();
predicate isHidden() {

View File

@@ -163,7 +163,9 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
predicate hasFlowTo(Node sink) {
sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode()
}
/**
* Holds if data may flow from some source to `sink` for this configuration.
@@ -836,13 +838,13 @@ private module Stage1 implements StageSig {
* by `revFlow`.
*/
pragma[nomagic]
predicate revFlowIsReadAndStored(Content c, Configuration conf) {
additional predicate revFlowIsReadAndStored(Content c, Configuration conf) {
revFlowConsCand(c, conf) and
revFlowStore(c, _, _, conf)
}
pragma[nomagic]
predicate viableReturnPosOutNodeCandFwd1(
additional predicate viableReturnPosOutNodeCandFwd1(
DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config
) {
fwdFlowReturnPosition(pos, _, config) and
@@ -858,7 +860,7 @@ private module Stage1 implements StageSig {
}
pragma[nomagic]
predicate viableParamArgNodeCandFwd1(
additional predicate viableParamArgNodeCandFwd1(
DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config
) {
viableParamArgEx(call, p, arg) and
@@ -905,7 +907,7 @@ private module Stage1 implements StageSig {
)
}
predicate revFlowState(FlowState state, Configuration config) {
additional predicate revFlowState(FlowState state, Configuration config) {
exists(NodeEx node |
sinkNode(node, state, config) and
revFlow(node, _, pragma[only_bind_into](config)) and
@@ -997,7 +999,7 @@ private module Stage1 implements StageSig {
)
}
predicate stats(
additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
@@ -1258,7 +1260,7 @@ private module MkStage<StageSig PrevStage> {
* argument.
*/
pragma[nomagic]
predicate fwdFlow(
additional predicate fwdFlow(
NodeEx node, FlowState state, Cc cc, ApOption argAp, Ap ap, Configuration config
) {
fwdFlow0(node, state, cc, argAp, ap, config) and
@@ -1482,7 +1484,7 @@ private module MkStage<StageSig PrevStage> {
* the access path of the returned value.
*/
pragma[nomagic]
predicate revFlow(
additional predicate revFlow(
NodeEx node, FlowState state, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
) {
revFlow0(node, state, toReturn, returnAp, ap, config) and
@@ -1660,7 +1662,7 @@ private module MkStage<StageSig PrevStage> {
)
}
predicate revFlow(NodeEx node, FlowState state, Configuration config) {
additional predicate revFlow(NodeEx node, FlowState state, Configuration config) {
revFlow(node, state, _, _, _, config)
}
@@ -1673,11 +1675,13 @@ private module MkStage<StageSig PrevStage> {
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
predicate revFlowAlias(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) }
additional predicate revFlowAlias(NodeEx node, Configuration config) {
revFlow(node, _, _, _, _, config)
}
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
revFlow(node, state, ap, config)
}
@@ -1698,7 +1702,7 @@ private module MkStage<StageSig PrevStage> {
)
}
predicate consCand(TypedContent tc, Ap ap, Configuration config) {
additional predicate consCand(TypedContent tc, Ap ap, Configuration config) {
revConsCand(tc, ap, config) and
validAp(ap, config)
}
@@ -1740,7 +1744,7 @@ private module MkStage<StageSig PrevStage> {
)
}
predicate stats(
additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
@@ -2925,12 +2929,17 @@ abstract private class PathNodeImpl extends PathNode {
result = this.getASuccessorImpl()
}
final PathNodeImpl getANonHiddenSuccessor() {
result = this.getASuccessorImpl().getASuccessorIfHidden*() and
not this.isHidden() and
pragma[nomagic]
private PathNodeImpl getANonHiddenSuccessor0() {
result = this.getASuccessorIfHidden*() and
not result.isHidden()
}
final PathNodeImpl getANonHiddenSuccessor() {
result = this.getASuccessorImpl().getANonHiddenSuccessor0() and
not this.isHidden()
}
abstract NodeEx getNodeEx();
predicate isHidden() {

View File

@@ -163,7 +163,9 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
predicate hasFlowTo(Node sink) {
sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode()
}
/**
* Holds if data may flow from some source to `sink` for this configuration.
@@ -836,13 +838,13 @@ private module Stage1 implements StageSig {
* by `revFlow`.
*/
pragma[nomagic]
predicate revFlowIsReadAndStored(Content c, Configuration conf) {
additional predicate revFlowIsReadAndStored(Content c, Configuration conf) {
revFlowConsCand(c, conf) and
revFlowStore(c, _, _, conf)
}
pragma[nomagic]
predicate viableReturnPosOutNodeCandFwd1(
additional predicate viableReturnPosOutNodeCandFwd1(
DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config
) {
fwdFlowReturnPosition(pos, _, config) and
@@ -858,7 +860,7 @@ private module Stage1 implements StageSig {
}
pragma[nomagic]
predicate viableParamArgNodeCandFwd1(
additional predicate viableParamArgNodeCandFwd1(
DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config
) {
viableParamArgEx(call, p, arg) and
@@ -905,7 +907,7 @@ private module Stage1 implements StageSig {
)
}
predicate revFlowState(FlowState state, Configuration config) {
additional predicate revFlowState(FlowState state, Configuration config) {
exists(NodeEx node |
sinkNode(node, state, config) and
revFlow(node, _, pragma[only_bind_into](config)) and
@@ -997,7 +999,7 @@ private module Stage1 implements StageSig {
)
}
predicate stats(
additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
@@ -1258,7 +1260,7 @@ private module MkStage<StageSig PrevStage> {
* argument.
*/
pragma[nomagic]
predicate fwdFlow(
additional predicate fwdFlow(
NodeEx node, FlowState state, Cc cc, ApOption argAp, Ap ap, Configuration config
) {
fwdFlow0(node, state, cc, argAp, ap, config) and
@@ -1482,7 +1484,7 @@ private module MkStage<StageSig PrevStage> {
* the access path of the returned value.
*/
pragma[nomagic]
predicate revFlow(
additional predicate revFlow(
NodeEx node, FlowState state, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
) {
revFlow0(node, state, toReturn, returnAp, ap, config) and
@@ -1660,7 +1662,7 @@ private module MkStage<StageSig PrevStage> {
)
}
predicate revFlow(NodeEx node, FlowState state, Configuration config) {
additional predicate revFlow(NodeEx node, FlowState state, Configuration config) {
revFlow(node, state, _, _, _, config)
}
@@ -1673,11 +1675,13 @@ private module MkStage<StageSig PrevStage> {
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
predicate revFlowAlias(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) }
additional predicate revFlowAlias(NodeEx node, Configuration config) {
revFlow(node, _, _, _, _, config)
}
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
revFlow(node, state, ap, config)
}
@@ -1698,7 +1702,7 @@ private module MkStage<StageSig PrevStage> {
)
}
predicate consCand(TypedContent tc, Ap ap, Configuration config) {
additional predicate consCand(TypedContent tc, Ap ap, Configuration config) {
revConsCand(tc, ap, config) and
validAp(ap, config)
}
@@ -1740,7 +1744,7 @@ private module MkStage<StageSig PrevStage> {
)
}
predicate stats(
additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
@@ -2925,12 +2929,17 @@ abstract private class PathNodeImpl extends PathNode {
result = this.getASuccessorImpl()
}
final PathNodeImpl getANonHiddenSuccessor() {
result = this.getASuccessorImpl().getASuccessorIfHidden*() and
not this.isHidden() and
pragma[nomagic]
private PathNodeImpl getANonHiddenSuccessor0() {
result = this.getASuccessorIfHidden*() and
not result.isHidden()
}
final PathNodeImpl getANonHiddenSuccessor() {
result = this.getASuccessorImpl().getANonHiddenSuccessor0() and
not this.isHidden()
}
abstract NodeEx getNodeEx();
predicate isHidden() {

View File

@@ -163,7 +163,9 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
predicate hasFlowTo(Node sink) {
sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode()
}
/**
* Holds if data may flow from some source to `sink` for this configuration.
@@ -836,13 +838,13 @@ private module Stage1 implements StageSig {
* by `revFlow`.
*/
pragma[nomagic]
predicate revFlowIsReadAndStored(Content c, Configuration conf) {
additional predicate revFlowIsReadAndStored(Content c, Configuration conf) {
revFlowConsCand(c, conf) and
revFlowStore(c, _, _, conf)
}
pragma[nomagic]
predicate viableReturnPosOutNodeCandFwd1(
additional predicate viableReturnPosOutNodeCandFwd1(
DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config
) {
fwdFlowReturnPosition(pos, _, config) and
@@ -858,7 +860,7 @@ private module Stage1 implements StageSig {
}
pragma[nomagic]
predicate viableParamArgNodeCandFwd1(
additional predicate viableParamArgNodeCandFwd1(
DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config
) {
viableParamArgEx(call, p, arg) and
@@ -905,7 +907,7 @@ private module Stage1 implements StageSig {
)
}
predicate revFlowState(FlowState state, Configuration config) {
additional predicate revFlowState(FlowState state, Configuration config) {
exists(NodeEx node |
sinkNode(node, state, config) and
revFlow(node, _, pragma[only_bind_into](config)) and
@@ -997,7 +999,7 @@ private module Stage1 implements StageSig {
)
}
predicate stats(
additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
@@ -1258,7 +1260,7 @@ private module MkStage<StageSig PrevStage> {
* argument.
*/
pragma[nomagic]
predicate fwdFlow(
additional predicate fwdFlow(
NodeEx node, FlowState state, Cc cc, ApOption argAp, Ap ap, Configuration config
) {
fwdFlow0(node, state, cc, argAp, ap, config) and
@@ -1482,7 +1484,7 @@ private module MkStage<StageSig PrevStage> {
* the access path of the returned value.
*/
pragma[nomagic]
predicate revFlow(
additional predicate revFlow(
NodeEx node, FlowState state, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
) {
revFlow0(node, state, toReturn, returnAp, ap, config) and
@@ -1660,7 +1662,7 @@ private module MkStage<StageSig PrevStage> {
)
}
predicate revFlow(NodeEx node, FlowState state, Configuration config) {
additional predicate revFlow(NodeEx node, FlowState state, Configuration config) {
revFlow(node, state, _, _, _, config)
}
@@ -1673,11 +1675,13 @@ private module MkStage<StageSig PrevStage> {
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
predicate revFlowAlias(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) }
additional predicate revFlowAlias(NodeEx node, Configuration config) {
revFlow(node, _, _, _, _, config)
}
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
revFlow(node, state, ap, config)
}
@@ -1698,7 +1702,7 @@ private module MkStage<StageSig PrevStage> {
)
}
predicate consCand(TypedContent tc, Ap ap, Configuration config) {
additional predicate consCand(TypedContent tc, Ap ap, Configuration config) {
revConsCand(tc, ap, config) and
validAp(ap, config)
}
@@ -1740,7 +1744,7 @@ private module MkStage<StageSig PrevStage> {
)
}
predicate stats(
additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
@@ -2925,12 +2929,17 @@ abstract private class PathNodeImpl extends PathNode {
result = this.getASuccessorImpl()
}
final PathNodeImpl getANonHiddenSuccessor() {
result = this.getASuccessorImpl().getASuccessorIfHidden*() and
not this.isHidden() and
pragma[nomagic]
private PathNodeImpl getANonHiddenSuccessor0() {
result = this.getASuccessorIfHidden*() and
not result.isHidden()
}
final PathNodeImpl getANonHiddenSuccessor() {
result = this.getASuccessorImpl().getANonHiddenSuccessor0() and
not this.isHidden()
}
abstract NodeEx getNodeEx();
predicate isHidden() {

View File

@@ -163,7 +163,9 @@ abstract class Configuration extends string {
/**
* Holds if data may flow from some source to `sink` for this configuration.
*/
predicate hasFlowTo(Node sink) { this.hasFlow(_, sink) }
predicate hasFlowTo(Node sink) {
sink = any(PathNodeSink n | this = n.getConfiguration()).getNodeEx().asNode()
}
/**
* Holds if data may flow from some source to `sink` for this configuration.
@@ -836,13 +838,13 @@ private module Stage1 implements StageSig {
* by `revFlow`.
*/
pragma[nomagic]
predicate revFlowIsReadAndStored(Content c, Configuration conf) {
additional predicate revFlowIsReadAndStored(Content c, Configuration conf) {
revFlowConsCand(c, conf) and
revFlowStore(c, _, _, conf)
}
pragma[nomagic]
predicate viableReturnPosOutNodeCandFwd1(
additional predicate viableReturnPosOutNodeCandFwd1(
DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config
) {
fwdFlowReturnPosition(pos, _, config) and
@@ -858,7 +860,7 @@ private module Stage1 implements StageSig {
}
pragma[nomagic]
predicate viableParamArgNodeCandFwd1(
additional predicate viableParamArgNodeCandFwd1(
DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config
) {
viableParamArgEx(call, p, arg) and
@@ -905,7 +907,7 @@ private module Stage1 implements StageSig {
)
}
predicate revFlowState(FlowState state, Configuration config) {
additional predicate revFlowState(FlowState state, Configuration config) {
exists(NodeEx node |
sinkNode(node, state, config) and
revFlow(node, _, pragma[only_bind_into](config)) and
@@ -997,7 +999,7 @@ private module Stage1 implements StageSig {
)
}
predicate stats(
additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
@@ -1258,7 +1260,7 @@ private module MkStage<StageSig PrevStage> {
* argument.
*/
pragma[nomagic]
predicate fwdFlow(
additional predicate fwdFlow(
NodeEx node, FlowState state, Cc cc, ApOption argAp, Ap ap, Configuration config
) {
fwdFlow0(node, state, cc, argAp, ap, config) and
@@ -1482,7 +1484,7 @@ private module MkStage<StageSig PrevStage> {
* the access path of the returned value.
*/
pragma[nomagic]
predicate revFlow(
additional predicate revFlow(
NodeEx node, FlowState state, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
) {
revFlow0(node, state, toReturn, returnAp, ap, config) and
@@ -1660,7 +1662,7 @@ private module MkStage<StageSig PrevStage> {
)
}
predicate revFlow(NodeEx node, FlowState state, Configuration config) {
additional predicate revFlow(NodeEx node, FlowState state, Configuration config) {
revFlow(node, state, _, _, _, config)
}
@@ -1673,11 +1675,13 @@ private module MkStage<StageSig PrevStage> {
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
predicate revFlowAlias(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) }
additional predicate revFlowAlias(NodeEx node, Configuration config) {
revFlow(node, _, _, _, _, config)
}
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
revFlow(node, state, ap, config)
}
@@ -1698,7 +1702,7 @@ private module MkStage<StageSig PrevStage> {
)
}
predicate consCand(TypedContent tc, Ap ap, Configuration config) {
additional predicate consCand(TypedContent tc, Ap ap, Configuration config) {
revConsCand(tc, ap, config) and
validAp(ap, config)
}
@@ -1740,7 +1744,7 @@ private module MkStage<StageSig PrevStage> {
)
}
predicate stats(
additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
@@ -2925,12 +2929,17 @@ abstract private class PathNodeImpl extends PathNode {
result = this.getASuccessorImpl()
}
final PathNodeImpl getANonHiddenSuccessor() {
result = this.getASuccessorImpl().getASuccessorIfHidden*() and
not this.isHidden() and
pragma[nomagic]
private PathNodeImpl getANonHiddenSuccessor0() {
result = this.getASuccessorIfHidden*() and
not result.isHidden()
}
final PathNodeImpl getANonHiddenSuccessor() {
result = this.getASuccessorImpl().getANonHiddenSuccessor0() and
not this.isHidden()
}
abstract NodeEx getNodeEx();
predicate isHidden() {

View File

@@ -735,6 +735,9 @@ class SummaryNode extends NodeImpl, TSummaryNode {
SummaryNode() { this = TSummaryNode(c, state) }
/** Gets the summarized callable that this node belongs to. */
FlowSummaryImpl::Public::SummarizedCallable getSummarizedCallable() { result = c }
override CfgScope getCfgScope() { none() }
override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = c }
@@ -1162,8 +1165,8 @@ private module PostUpdateNodes {
ExprPostUpdateNode() { this = TExprPostUpdateNode(e) }
override ExprNode getPreUpdateNode() {
// For compund arguments, such as `m(if b then x else y)`, we want the leaf nodes
// `[post] x` and `[post] y` to have two pre-update nodes: (1) the compund argument,
// For compound arguments, such as `m(if b then x else y)`, we want the leaf nodes
// `[post] x` and `[post] y` to have two pre-update nodes: (1) the compound argument,
// `if b then x else y`; and the (2) the underlying expressions; `x` and `y`,
// respectively.
//

View File

@@ -64,7 +64,7 @@ predicate uninitializedWrite(Cfg::EntryBasicBlock bb, int i, LocalVariable v) {
i = -1
}
/** Holds if `bb` contains a caputured read of variable `v`. */
/** Holds if `bb` contains a captured read of variable `v`. */
pragma[noinline]
private predicate hasCapturedVariableRead(Cfg::BasicBlock bb, LocalVariable v) {
exists(LocalVariableReadAccess read |
@@ -74,7 +74,7 @@ private predicate hasCapturedVariableRead(Cfg::BasicBlock bb, LocalVariable v) {
)
}
/** Holds if `bb` contains a caputured write to variable `v`. */
/** Holds if `bb` contains a captured write to variable `v`. */
pragma[noinline]
private predicate writesCapturedVariable(Cfg::BasicBlock bb, LocalVariable v) {
exists(LocalVariableWriteAccess write |

View File

@@ -1,191 +0,0 @@
/**
* Provides an implementation of global (interprocedural) taint tracking.
* This file re-exports the local (intraprocedural) taint-tracking analysis
* from `TaintTrackingParameter::Public` and adds a global analysis, mainly
* exposed through the `Configuration` class. For some languages, this file
* exists in several identical copies, allowing queries to use multiple
* `Configuration` classes that depend on each other without introducing
* mutual recursion among those configurations.
*/
import TaintTrackingParameter::Public
private import TaintTrackingParameter::Private
/**
* A configuration of interprocedural taint tracking analysis. This defines
* sources, sinks, and any other configurable aspect of the analysis. Each
* use of the taint tracking library must define its own unique extension of
* this abstract class.
*
* A taint-tracking configuration is a special data flow configuration
* (`DataFlow::Configuration`) that allows for flow through nodes that do not
* necessarily preserve values but are still relevant from a taint tracking
* perspective. (For example, string concatenation, where one of the operands
* is tainted.)
*
* To create a configuration, extend this class with a subclass whose
* characteristic predicate is a unique singleton string. For example, write
*
* ```ql
* class MyAnalysisConfiguration extends TaintTracking::Configuration {
* MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" }
* // Override `isSource` and `isSink`.
* // Optionally override `isSanitizer`.
* // Optionally override `isSanitizerIn`.
* // Optionally override `isSanitizerOut`.
* // Optionally override `isSanitizerGuard`.
* // Optionally override `isAdditionalTaintStep`.
* }
* ```
*
* Then, to query whether there is flow between some `source` and `sink`,
* write
*
* ```ql
* exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink))
* ```
*
* Multiple configurations can coexist, but it is unsupported to depend on
* another `TaintTracking::Configuration` or a `DataFlow::Configuration` in the
* overridden predicates that define sources, sinks, or additional steps.
* Instead, the dependency should go to a `TaintTracking2::Configuration` or a
* `DataFlow2::Configuration`, `DataFlow3::Configuration`, etc.
*/
abstract class Configuration extends DataFlow::Configuration {
bindingset[this]
Configuration() { any() }
/**
* Holds if `source` is a relevant taint source.
*
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
override predicate isSource(DataFlow::Node source) { none() }
/**
* Holds if `source` is a relevant taint source with the given initial
* `state`.
*
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) { none() }
/**
* Holds if `sink` is a relevant taint sink
*
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
override predicate isSink(DataFlow::Node sink) { none() }
/**
* Holds if `sink` is a relevant taint sink accepting `state`.
*
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) { none() }
/** Holds if the node `node` is a taint sanitizer. */
predicate isSanitizer(DataFlow::Node node) { none() }
final override predicate isBarrier(DataFlow::Node node) {
this.isSanitizer(node) or
defaultTaintSanitizer(node)
}
/**
* Holds if the node `node` is a taint sanitizer when the flow state is
* `state`.
*/
predicate isSanitizer(DataFlow::Node node, DataFlow::FlowState state) { none() }
final override predicate isBarrier(DataFlow::Node node, DataFlow::FlowState state) {
this.isSanitizer(node, state)
}
/** Holds if taint propagation into `node` is prohibited. */
predicate isSanitizerIn(DataFlow::Node node) { none() }
final override predicate isBarrierIn(DataFlow::Node node) { this.isSanitizerIn(node) }
/** Holds if taint propagation out of `node` is prohibited. */
predicate isSanitizerOut(DataFlow::Node node) { none() }
final override predicate isBarrierOut(DataFlow::Node node) { this.isSanitizerOut(node) }
/**
* DEPRECATED: Use `isSanitizer` and `BarrierGuard` module instead.
*
* Holds if taint propagation through nodes guarded by `guard` is prohibited.
*/
deprecated predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
deprecated final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
this.isSanitizerGuard(guard)
}
/**
* DEPRECATED: Use `isSanitizer` and `BarrierGuard` module instead.
*
* Holds if taint propagation through nodes guarded by `guard` is prohibited
* when the flow state is `state`.
*/
deprecated predicate isSanitizerGuard(DataFlow::BarrierGuard guard, DataFlow::FlowState state) {
none()
}
deprecated final override predicate isBarrierGuard(
DataFlow::BarrierGuard guard, DataFlow::FlowState state
) {
this.isSanitizerGuard(guard, state)
}
/**
* Holds if taint may propagate from `node1` to `node2` in addition to the normal data-flow and taint steps.
*/
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
this.isAdditionalTaintStep(node1, node2) or
defaultAdditionalTaintStep(node1, node2)
}
/**
* Holds if taint may propagate from `node1` to `node2` in addition to the normal data-flow and taint steps.
* This step is only applicable in `state1` and updates the flow state to `state2`.
*/
predicate isAdditionalTaintStep(
DataFlow::Node node1, DataFlow::FlowState state1, DataFlow::Node node2,
DataFlow::FlowState state2
) {
none()
}
final override predicate isAdditionalFlowStep(
DataFlow::Node node1, DataFlow::FlowState state1, DataFlow::Node node2,
DataFlow::FlowState state2
) {
this.isAdditionalTaintStep(node1, state1, node2, state2)
}
override predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) {
(
this.isSink(node) or
this.isSink(node, _) or
this.isAdditionalTaintStep(node, _) or
this.isAdditionalTaintStep(node, _, _, _)
) and
defaultImplicitTaintRead(node, c)
}
/**
* Holds if taint may flow from `source` to `sink` for this configuration.
*/
// overridden to provide taint-tracking specific qldoc
override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) {
super.hasFlow(source, sink)
}
}

View File

@@ -1,6 +0,0 @@
import codeql.ruby.dataflow.internal.TaintTrackingPublic as Public
module Private {
import codeql.ruby.dataflow.internal.DataFlowImplForRegExp as DataFlow
import codeql.ruby.dataflow.internal.TaintTrackingPrivate
}

View File

@@ -417,7 +417,7 @@ module Rbi {
override ReturnType getReturnType() { result = ReturnsCall.super.getReturnType() }
}
/** A call to `void` that spcifies that a given method does not return a useful value. */
/** A call to `void` that specifies that a given method does not return a useful value. */
class MethodVoidCall extends MethodReturnsTypeCall instanceof VoidCall {
override ReturnType getReturnType() { result = VoidCall.super.getReturnType() }
}
@@ -448,7 +448,7 @@ module Rbi {
}
/**
* A call to `void` that spcifies that a given proc or block does not return
* A call to `void` that specifies that a given proc or block does not return
* a useful value.
*/
class ProcVoidCall extends ProcReturnsTypeCall instanceof VoidCall {

View File

@@ -6,7 +6,7 @@ private import codeql.ruby.ast.internal.TreeSitter
/** A source file that contains generated code. */
abstract class GeneratedCodeFile extends RubyFile { }
/** A file contining comments suggesting it contains generated code. */
/** A file continuing comments suggesting it contains generated code. */
class GeneratedCommentFile extends GeneratedCodeFile {
GeneratedCommentFile() { this = any(GeneratedCodeComment c).getLocation().getFile() }
}

View File

@@ -142,7 +142,7 @@ class ParamsSource extends Http::Server::RequestInputAccess::Range {
override string getSourceType() { result = "ActionController::Metal#params" }
override string getKind() { result = "parameter" }
override Http::Server::RequestInputKind getKind() { result = Http::Server::parameterInputKind() }
}
/**
@@ -154,7 +154,7 @@ class CookiesSource extends Http::Server::RequestInputAccess::Range {
override string getSourceType() { result = "ActionController::Metal#cookies" }
override string getKind() { result = "cookie" }
override Http::Server::RequestInputKind getKind() { result = Http::Server::cookieInputKind() }
}
/** A call to `cookies` from within a controller. */
@@ -167,6 +167,140 @@ private class ActionControllerParamsCall extends ActionControllerContextCall, Pa
ActionControllerParamsCall() { this.getMethodName() = "params" }
}
/** Modeling for `ActionDispatch::Request`. */
private module Request {
/**
* A call to `request` from within a controller. This is an instance of
* `ActionDispatch::Request`.
*/
private class RequestNode extends DataFlow::CallNode {
RequestNode() {
this.asExpr().getExpr() instanceof ActionControllerContextCall and
this.getMethodName() = "request"
}
}
/**
* A method call on `request`.
*/
private class RequestMethodCall extends DataFlow::CallNode {
RequestMethodCall() {
any(RequestNode r).(DataFlow::LocalSourceNode).flowsTo(this.getReceiver())
}
}
abstract private class RequestInputAccess extends RequestMethodCall,
Http::Server::RequestInputAccess::Range {
override string getSourceType() { result = "ActionDispatch::Request#" + this.getMethodName() }
}
/**
* A method call on `request` which returns request parameters.
*/
private class ParametersCall extends RequestInputAccess {
ParametersCall() {
this.getMethodName() =
[
"parameters", "params", "GET", "POST", "query_parameters", "request_parameters",
"filtered_parameters"
]
}
override Http::Server::RequestInputKind getKind() {
result = Http::Server::parameterInputKind()
}
}
/** A method call on `request` which returns part or all of the request path. */
private class PathCall extends RequestInputAccess {
PathCall() {
this.getMethodName() =
["path", "filtered_path", "fullpath", "original_fullpath", "original_url", "url"]
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::urlInputKind() }
}
/** A method call on `request` which returns a specific request header. */
private class HeadersCall extends RequestInputAccess {
HeadersCall() {
this.getMethodName() =
[
"authorization", "script_name", "path_info", "user_agent", "referer", "referrer",
"host_authority", "content_type", "host", "hostname", "accept_encoding",
"accept_language", "if_none_match", "if_none_match_etags", "content_mime_type"
]
or
// Request headers are prefixed with `HTTP_` to distinguish them from
// "headers" supplied by Rack middleware.
this.getMethodName() = ["get_header", "fetch_header"] and
this.getArgument(0).asExpr().getExpr().getConstantValue().getString().regexpMatch("^HTTP_.+")
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
}
// TODO: each_header
/**
* A method call on `request` which returns part or all of the host.
* This can be influenced by headers such as Host and X-Forwarded-Host.
*/
private class HostCall extends RequestInputAccess {
HostCall() {
this.getMethodName() =
[
"authority", "host", "host_authority", "host_with_port", "hostname", "forwarded_for",
"forwarded_host", "port", "forwarded_port"
]
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
}
/**
* A method call on `request` which is influenced by one or more request
* headers.
*/
private class HeaderTaintedCall extends RequestInputAccess {
HeaderTaintedCall() {
this.getMethodName() = ["media_type", "media_type_params", "content_charset", "base_url"]
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
}
/** A method call on `request` which returns the request body. */
private class BodyCall extends RequestInputAccess {
BodyCall() { this.getMethodName() = ["body", "raw_post"] }
override Http::Server::RequestInputKind getKind() { result = Http::Server::bodyInputKind() }
}
/**
* A method call on `request` which returns the rack env.
* This is a hash containing all the information about the request. Values
* under keys starting with `HTTP_` are user-controlled.
*/
private class EnvCall extends RequestMethodCall {
EnvCall() { this.getMethodName() = ["env", "filtered_env"] }
}
/**
* A read of a user-controlled parameter from the request env.
*/
private class EnvHttpAccess extends DataFlow::CallNode, Http::Server::RequestInputAccess::Range {
EnvHttpAccess() {
any(EnvCall c).(DataFlow::LocalSourceNode).flowsTo(this.getReceiver()) and
this.getMethodName() = "[]" and
this.getArgument(0).asExpr().getExpr().getConstantValue().getString().regexpMatch("^HTTP_.+")
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
override string getSourceType() { result = "ActionDispatch::Request#env[]" }
}
}
/** A call to `render` from within a controller. */
private class ActionControllerRenderCall extends ActionControllerContextCall, RenderCallImpl {
ActionControllerRenderCall() { this.getMethodName() = "render" }

View File

@@ -0,0 +1,53 @@
/**
* Provides modeling for the `ActionMailer` library.
*/
private import codeql.ruby.AST
private import codeql.ruby.ApiGraphs
private import codeql.ruby.frameworks.internal.Rails
/**
* Provides modeling for the `ActionMailer` library.
*/
module ActionMailer {
/**
* A `ClassDeclaration` for a class that extends `ActionMailer::Base`.
* For example,
*
* ```rb
* class FooMailer < ActionMailer::Base
* ...
* end
* ```
*/
class MailerClass extends ClassDeclaration {
MailerClass() {
this.getSuperclassExpr() =
[
API::getTopLevelMember("ActionMailer").getMember("Base"),
// In Rails applications `ApplicationMailer` typically extends
// `ActionMailer::Base`, but we treat it separately in case the
// `ApplicationMailer` definition is not in the database.
API::getTopLevelMember("ApplicationMailer")
].getASubclass().getAValueReachableFromSource().asExpr().getExpr()
}
}
/** A method call with a `self` receiver from within a mailer class */
private class ContextCall extends MethodCall {
private MailerClass mailerClass;
ContextCall() {
this.getReceiver() instanceof SelfVariableAccess and
this.getEnclosingModule() = mailerClass
}
/** Gets the mailer class containing this method. */
MailerClass getMailerClass() { result = mailerClass }
}
/** A call to `params` from within a mailer. */
class ParamsCall extends ContextCall, ParamsCallImpl {
ParamsCall() { this.getMethodName() = "params" }
}
}

View File

@@ -0,0 +1,30 @@
/**
* Modeling for `ActiveJob`, a framweork for declaring and enqueueing jobs that
* ships with Rails.
* https://rubygems.org/gems/activejob
*/
private import codeql.ruby.ApiGraphs
private import codeql.ruby.Concepts
private import codeql.ruby.DataFlow
/** Modeling for `ActiveJob`. */
module ActiveJob {
/**
* `ActiveJob::Serializers`
*/
module Serializers {
/**
* A call to `ActiveJob::Serializers.deserialize`, which interprets part of
* its argument as a Ruby constant.
*/
class DeserializeCall extends DataFlow::CallNode, CodeExecution::Range {
DeserializeCall() {
this =
API::getTopLevelMember("ActiveJob").getMember("Serializers").getAMethodCall("deserialize")
}
override DataFlow::Node getCode() { result = this.getArgument(0) }
}
}
}

View File

@@ -140,6 +140,25 @@ module ActiveSupport {
}
}
/**
* Type summaries for extensions to the `Pathname` module.
*/
private class PathnameTypeSummary extends ModelInput::TypeModelCsv {
override predicate row(string row) {
// package1;type1;package2;type2;path
// Pathname#existence : Pathname
row = ";Pathname;;Pathname;Method[existence].ReturnValue"
}
}
/** Taint flow summaries for extensions to the `Pathname` module. */
private class PathnameTaintSummary extends ModelInput::SummaryModelCsv {
override predicate row(string row) {
// Pathname#existence
row = ";Pathname;Method[existence];Argument[self];ReturnValue;taint"
}
}
/**
* `ActiveSupport::SafeBuffer` wraps a string, providing HTML-safe methods
* for concatenation.

View File

@@ -58,7 +58,7 @@ class SubshellHeredocExecution extends SystemCommandExecution::Range {
private class SplatSummary extends SummarizedCallable {
SplatSummary() { this = "*(splat)" }
override SplatExpr getACall() { any() }
override SplatExpr getACallSimple() { any() }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
(

View File

@@ -123,7 +123,7 @@ class GraphqlSchemaObjectClass extends ClassDeclaration {
* `GraphQL::Schema::RelayClassicMutation` or
* `GraphQL::Schema::Resolver`.
*
* Both of these classes have an overrideable `resolve` instance
* Both of these classes have an overridable `resolve` instance
* method which can receive user input in order to resolve a query or mutation.
*/
private class GraphqlResolvableClass extends ClassDeclaration {
@@ -147,7 +147,7 @@ private class GraphqlResolvableClass extends ClassDeclaration {
*
* ```rb
* module Mutation
* class NameAnInstrument < BaseMutationn
* class NameAnInstrument < BaseMutation
* argument :instrument_uuid, Types::Uuid,
* required: true,
* loads: ::Instrument,
@@ -193,7 +193,7 @@ class GraphqlResolveMethod extends Method, Http::Server::RequestHandler::Range {
*
* ```rb
* module Mutation
* class NameAnInstrument < BaseMutationn
* class NameAnInstrument < BaseMutation
* argument :instrument_uuid, Types::Uuid,
* required: true,
* loads: ::Instrument,

View File

@@ -71,6 +71,21 @@ module Rails {
/** A render call that does not automatically set the HTTP response body. */
class RenderToCall extends MethodCall instanceof RenderToCallImpl { }
/**
* A `render` call seen as a file system access.
*/
private class RenderAsFileSystemAccess extends FileSystemAccess::Range, DataFlow::CallNode {
RenderAsFileSystemAccess() {
exists(MethodCall call | this.asExpr().getExpr() = call |
call instanceof RenderCall
or
call instanceof RenderToCall
)
}
override DataFlow::Node getAPathArgument() { result = this.getKeywordArgument("file") }
}
}
/**

View File

@@ -95,7 +95,7 @@ module IO {
* popen([env,] cmd, mode="r" [, opt]) -> io
* popen([env,] cmd, mode="r" [, opt]) {|io| block } -> obj
* ```
* `IO.popen` does different things based on the the value of `cmd`:
* `IO.popen` does different things based on the value of `cmd`:
* ```
* "-" : fork
* commandline : command line string which is passed to a shell

View File

@@ -3,7 +3,7 @@
private import codeql.ruby.AST
private import codeql.ruby.ApiGraphs
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.FlowSummary
private import codeql.ruby.dataflow.FlowSummary as FlowSummary
private import codeql.ruby.dataflow.internal.DataFlowDispatch
private import codeql.ruby.controlflow.CfgNodes
private import codeql.ruby.Regexp as RE
@@ -107,6 +107,18 @@ module String {
preservesValue = false
}
/** A `String` callable with a flow summary. */
abstract class SummarizedCallable extends FlowSummary::SummarizedCallable {
bindingset[this]
SummarizedCallable() { any() }
}
abstract private class SimpleSummarizedCallable extends SummarizedCallable,
FlowSummary::SimpleSummarizedCallable {
bindingset[this]
SimpleSummarizedCallable() { any() }
}
private class NewSummary extends SummarizedCallable {
NewSummary() { this = "String.new" }

View File

@@ -15,11 +15,13 @@
*/
private import codeql.ruby.AST
private import codeql.ruby.ApiGraphs
private import internal.ApiGraphModels as Shared
private import internal.ApiGraphModelsSpecific as Specific
import Shared::ModelInput as ModelInput
import Shared::ModelOutput as ModelOutput
private import codeql.ruby.dataflow.RemoteFlowSources
private import codeql.ruby.dataflow.FlowSummary
/**
* A remote flow source originating from a CSV source row.
@@ -29,3 +31,33 @@ private class RemoteFlowSourceFromCsv extends RemoteFlowSource::Range {
override string getSourceType() { result = "Remote flow (from model)" }
}
private class SummarizedCallableFromModel extends SummarizedCallable {
string package;
string type;
string path;
SummarizedCallableFromModel() {
ModelOutput::relevantSummaryModel(package, type, path, _, _, _) and
this = package + ";" + type + ";" + path
}
override Call getACall() {
exists(API::MethodAccessNode base |
ModelOutput::resolvedSummaryBase(package, type, path, base) and
result = base.getCallNode().asExpr().getExpr()
)
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
exists(string kind |
ModelOutput::relevantSummaryModel(package, type, path, input, output, kind)
|
kind = "value" and
preservesValue = true
or
kind = "taint" and
preservesValue = false
)
}
}

View File

@@ -70,6 +70,7 @@ class ExconHttpRequest extends Http::Client::Request::Range, DataFlow::CallNode
)
}
cached
override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {

View File

@@ -71,6 +71,7 @@ class FaradayHttpRequest extends Http::Client::Request::Range, DataFlow::CallNod
)
}
cached
override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {

View File

@@ -25,7 +25,7 @@ class HttpClientRequest extends Http::Client::Request::Range, DataFlow::CallNode
[
// One-off requests
API::getTopLevelMember("HTTPClient"),
// Conncection re-use
// Connection re-use
API::getTopLevelMember("HTTPClient").getInstance()
] and
requestNode = connectionNode.getReturn(method) and
@@ -61,6 +61,7 @@ class HttpClientRequest extends Http::Client::Request::Range, DataFlow::CallNode
.getArgument(0)
}
cached
override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {

View File

@@ -53,6 +53,7 @@ class HttpartyRequest extends Http::Client::Request::Range, DataFlow::CallNode {
result = this.getKeywordArgumentIncludeHashArgument(["verify", "verify_peer"])
}
cached
override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {

View File

@@ -80,6 +80,7 @@ class NetHttpRequest extends Http::Client::Request::Range, DataFlow::CallNode {
)
}
cached
override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {

View File

@@ -42,6 +42,7 @@ class OpenUriRequest extends Http::Client::Request::Range, DataFlow::CallNode {
result = this.getKeywordArgumentIncludeHashArgument("ssl_verify_mode")
}
cached
override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {
@@ -91,6 +92,7 @@ class OpenUriKernelOpenRequest extends Http::Client::Request::Range, DataFlow::C
)
}
cached
override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {

View File

@@ -54,6 +54,7 @@ class RestClientHttpRequest extends Http::Client::Request::Range, DataFlow::Call
)
}
cached
override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {

View File

@@ -34,6 +34,7 @@ class TyphoeusHttpRequest extends Http::Client::Request::Range, DataFlow::CallNo
result = this.getKeywordArgumentIncludeHashArgument("ssl_verifypeer")
}
cached
override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {

View File

@@ -27,6 +27,7 @@ module Pathname {
* Every `PathnameInstance` is considered to be a `FileNameSource`.
*/
class PathnameInstance extends FileNameSource {
cached
PathnameInstance() { any(PathnameConfiguration c).hasFlowTo(this) }
}

View File

@@ -241,7 +241,7 @@ abstract class RegExp extends Ast::StringlikeLiteral {
/**
* Helper predicate for `escapingChar`.
* In order to avoid negative recusrion, we return a boolean.
* In order to avoid negative recursion, we return a boolean.
* This way, we can refer to `escaping(pos - 1).booleanNot()`
* rather than to a negated version of `escaping(pos)`.
*/

View File

@@ -1,10 +1,15 @@
private import codeql.ruby.Regexp
private import codeql.ruby.ast.Literal as Ast
private import codeql.ruby.AST as Ast
private import codeql.ruby.CFG
private import codeql.ruby.DataFlow
private import codeql.ruby.controlflow.CfgNodes
private import codeql.ruby.dataflow.internal.tainttrackingforregexp.TaintTrackingImpl
private import codeql.ruby.dataflow.internal.DataFlowImplForRegExp
private import codeql.ruby.typetracking.TypeTracker
private import codeql.ruby.ApiGraphs
private import codeql.ruby.dataflow.internal.DataFlowPrivate as DataFlowPrivate
private import codeql.ruby.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
private import codeql.ruby.dataflow.FlowSummary as FlowSummary
private import codeql.ruby.frameworks.core.String
class RegExpConfiguration extends Configuration {
RegExpConfiguration() { this = "RegExpConfiguration" }
@@ -20,7 +25,7 @@ class RegExpConfiguration extends Configuration {
override predicate isSink(DataFlow::Node sink) { sink instanceof RegExpInterpretation::Range }
override predicate isSanitizer(DataFlow::Node node) {
override predicate isBarrier(DataFlow::Node node) {
exists(DataFlow::CallNode mce | mce.getMethodName() = ["match", "match?"] |
// receiver of https://ruby-doc.org/core-2.4.0/String.html#method-i-match
node = mce.getReceiver() and
@@ -31,6 +36,24 @@ class RegExpConfiguration extends Configuration {
mce.getReceiver() = trackRegexpType()
)
}
override predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
// include taint flow through `String` summaries,
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, false) and
nodeFrom.(DataFlowPrivate::SummaryNode).getSummarizedCallable() instanceof
String::SummarizedCallable
or
// string concatenations, and
exists(CfgNodes::ExprNodes::OperationCfgNode op |
op = nodeTo.asExpr() and
op.getAnOperand() = nodeFrom.asExpr() and
op.getExpr().(Ast::BinaryOperation).getOperator() = "+"
)
or
// string interpolations
nodeFrom.asExpr() =
nodeTo.asExpr().(CfgNodes::ExprNodes::StringlikeLiteralCfgNode).getAComponent()
}
}
private DataFlow::LocalSourceNode trackRegexpType(TypeTracker t) {

View File

@@ -1,5 +1,5 @@
/**
* Provides precicates for reasoning about bad tag filter vulnerabilities.
* Provides predicates for reasoning about bad tag filter vulnerabilities.
*/
import regexp.RegexpMatching
@@ -65,7 +65,7 @@ predicate isBadRegexpFilter(HtmlMatchingRegExp regexp, string msg) {
regexp.matches("<!-- foo --!>") and
exists(int a, int b | a != b |
regexp.fillsCaptureGroup("<!-- foo -->", a) and
// <!-- foo --> might be ambigously parsed (matching both capture groups), and that is ok here.
// <!-- foo --> might be ambiguously parsed (matching both capture groups), and that is ok here.
regexp.fillsCaptureGroup("<!-- foo --!>", b) and
not regexp.fillsCaptureGroup("<!-- foo --!>", a) and
msg =

View File

@@ -0,0 +1,36 @@
/**
* Provides utility classes and predicates for reasoning about `Kernel.open` and related methods.
*/
private import codeql.ruby.AST
private import codeql.ruby.DataFlow
private import codeql.ruby.AST
private import codeql.ruby.ApiGraphs
private import codeql.ruby.frameworks.core.Kernel::Kernel
/** A call to a method that might access a file or start a process. */
class AmbiguousPathCall extends DataFlow::CallNode {
string name;
AmbiguousPathCall() {
this.(KernelMethodCall).getMethodName() = "open" and
name = "Kernel.open"
or
this = API::getTopLevelMember("IO").getAMethodCall("read") and
not this = API::getTopLevelMember("File").getAMethodCall("read") and // needed in e.g. opal/opal, where some calls have both paths, but I'm not sure why
name = "IO.read"
}
/** Gets the name for the method being called. */
string getName() { result = name }
/** Gets the name for a safer method that can be used instead. */
string getReplacement() {
result = "File.read" and name = "IO.read"
or
result = "File.open" and name = "Kernel.open"
}
/** Gets the argument that specifies the path to be accessed. */
DataFlow::Node getPathArgument() { result = this.getArgument(0) }
}

View File

@@ -8,6 +8,8 @@ private import codeql.ruby.ApiGraphs
private import codeql.ruby.CFG
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.RemoteFlowSources
private import codeql.ruby.frameworks.ActiveJob
private import codeql.ruby.frameworks.core.Module
module UnsafeDeserialization {
/**
@@ -199,4 +201,27 @@ module UnsafeDeserialization {
toNode = callNode
)
}
/**
* A argument in a call to `Module.const_get`, considered as a sink for unsafe
* deserialization.
*
* Calls to `Module.const_get` can return arbitrary classes which can then be
* instantiated.
*/
class ConstGetCallArgument extends Sink {
ConstGetCallArgument() { this = any(Module::ModuleConstGetCallCodeExecution c).getCode() }
}
/**
* A argument in a call to `ActiveJob::Serializers.deserialize`, considered as
* a sink for unsafe deserialization.
*
* This is roughly equivalent to a call to `Module.const_get`.
*/
class ActiveJobSerializersDeserializeArgument extends Sink {
ActiveJobSerializersDeserializeArgument() {
this = any(ActiveJob::Serializers::DeserializeCall c).getCode()
}
}
}

View File

@@ -50,7 +50,9 @@ module UrlRedirect {
/**
* A source of remote user input, considered as a flow source.
*/
class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
class HttpRequestInputAccessAsSource extends Source, Http::Server::RequestInputAccess {
HttpRequestInputAccessAsSource() { this.isThirdPartyControllable() }
}
/**
* A HTTP redirect response, considered as a flow sink.

View File

@@ -312,9 +312,11 @@ module ReflectedXss {
deprecated predicate isAdditionalXSSTaintStep = isAdditionalXssTaintStep/2;
/**
* A source of remote user input, considered as a flow source.
* A HTTP request input, considered as a flow source.
*/
class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
class HttpRequestInputAccessAsSource extends Source, Http::Server::RequestInputAccess {
HttpRequestInputAccessAsSource() { this.isThirdPartyControllable() }
}
}
/** DEPRECATED: Alias for ReflectedXss */

View File

@@ -202,7 +202,7 @@ private predicate isFork(State q, InputSymbol s1, InputSymbol s2, State r1, Stat
//
// We additionally require that the there exists another InfiniteRepetitionQuantifier `mid` on the path from `q` to itself.
// This is done to avoid flagging regular expressions such as `/(a?)*b/` - that only has polynomial runtime, and is detected by `js/polynomial-redos`.
// The below code is therefore a heuritic, that only flags regular expressions such as `/(a*)*b/`,
// The below code is therefore a heuristic, that only flags regular expressions such as `/(a*)*b/`,
// and does not flag regular expressions such as `/(a?b?)c/`, but the latter pattern is not used frequently.
r1 = r2 and
q1 = q2 and

View File

@@ -59,8 +59,8 @@ predicate matchesEpsilon(RegExpTerm t) {
/**
* A lookahead/lookbehind that matches the empty string.
*/
class EmptyPositiveSubPatttern extends RegExpSubPattern {
EmptyPositiveSubPatttern() {
class EmptyPositiveSubPattern extends RegExpSubPattern {
EmptyPositiveSubPattern() {
(
this instanceof RegExpPositiveLookahead
or
@@ -70,6 +70,9 @@ class EmptyPositiveSubPatttern extends RegExpSubPattern {
}
}
/** DEPRECATED: Use `EmptyPositiveSubPattern` instead. */
deprecated class EmptyPositiveSubPatttern = EmptyPositiveSubPattern;
/**
* A branch in a disjunction that is the root node in a literal, or a literal
* whose root node is not a disjunction.
@@ -133,7 +136,7 @@ private predicate isCanonicalTerm(RelevantRegExpTerm term, string str) {
}
/**
* Gets a string reperesentation of the flags used with the regular expression.
* Gets a string representation of the flags used with the regular expression.
* Only the flags that are relevant for the canonicalization are included.
*/
string getCanonicalizationFlags(RegExpTerm root) {
@@ -334,7 +337,7 @@ private module CharacterClasses {
)
}
private string lowercaseLetter() { result = "abdcefghijklmnopqrstuvwxyz".charAt(_) }
private string lowercaseLetter() { result = "abcdefghijklmnopqrstuvwxyz".charAt(_) }
private string upperCaseLetter() { result = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".charAt(_) }
@@ -697,9 +700,7 @@ predicate delta(State q1, EdgeLabel lbl, State q2) {
lbl = Epsilon() and q2 = Accept(getRoot(dollar))
)
or
exists(EmptyPositiveSubPatttern empty | q1 = before(empty) |
lbl = Epsilon() and q2 = after(empty)
)
exists(EmptyPositiveSubPattern empty | q1 = before(empty) | lbl = Epsilon() and q2 = after(empty))
}
/**
@@ -1028,7 +1029,7 @@ module ReDoSPruning<isCandidateSig/2 isCandidate> {
* as the suffix "X" will cause both the regular expressions to be rejected.
*
* The string `w` is repeated any number of times because it needs to be
* infinitely repeatedable for the attack to work.
* infinitely repeatable for the attack to work.
* For the regular expression `/((ab)+)*abab/` the accepting state is not reachable from the fork
* using epsilon transitions. But any attempt at repeating `w` will end in a state that accepts all suffixes.
*/

View File

@@ -7,7 +7,7 @@ import codeql.Locations
private import codeql.ruby.ast.Literal as Ast
/**
* Holds if `term` is an ecape class representing e.g. `\d`.
* Holds if `term` is an escape class representing e.g. `\d`.
* `clazz` is which character class it represents, e.g. "d" for `\d`.
*/
predicate isEscapeClass(RegExpTerm term, string clazz) {

View File

@@ -106,6 +106,18 @@ module PolynomialReDoS {
regexp.asExpr() = call.getReceiver() and
this.asExpr() = call.getArgument(0)
)
or
// a case-when statement
exists(CfgNodes::ExprNodes::CaseExprCfgNode caseWhen |
matchNode.asExpr() = caseWhen and
this.asExpr() = caseWhen.getValue()
|
regexp.asExpr() =
caseWhen.getBranch(_).(CfgNodes::ExprNodes::WhenClauseCfgNode).getPattern(_)
or
regexp.asExpr() =
caseWhen.getBranch(_).(CfgNodes::ExprNodes::InClauseCfgNode).getPattern()
)
)
)
}

View File

@@ -1,5 +1,5 @@
/**
* Provides precicates for reasoning about which strings are matched by a regular expression,
* Provides predicates for reasoning about which strings are matched by a regular expression,
* and for testing which capture groups are filled when a particular regexp matches a string.
*/

View File

@@ -76,7 +76,7 @@ class StateTuple extends TStateTuple {
StateTuple() { this = MkStateTuple(q1, q2, q3) }
/**
* Gest a string repesentation of this tuple.
* Gest a string representation of this tuple.
*/
string toString() { result = "(" + q1 + ", " + q2 + ", " + q3 + ")" }

View File

@@ -186,7 +186,7 @@ private module Cached {
jumpStep(nodeFrom, nodeTo) and
summary = JumpStep()
or
levelStep(nodeFrom, nodeTo) and
levelStepNoCall(nodeFrom, nodeTo) and
summary = LevelStep()
or
exists(TypeTrackerContent content |
@@ -216,6 +216,9 @@ private module Cached {
or
returnStep(nodeFrom, nodeTo) and
summary = ReturnStep()
or
levelStepCall(nodeFrom, nodeTo) and
summary = LevelStep()
}
}

View File

@@ -76,20 +76,28 @@ predicate simpleLocalFlowStep = DataFlowPrivate::localFlowStepTypeTracker/2;
*/
predicate jumpStep = DataFlowPrivate::jumpStep/2;
/**
* Holds if there is a summarized local flow step from `nodeFrom` to `nodeTo`,
* because there is direct flow from a parameter to a return. That is, summarized
* steps are not applied recursively.
*/
/** Holds if there is direct flow from `param` to a return. */
pragma[nomagic]
private predicate summarizedLocalStep(Node nodeFrom, Node nodeTo) {
exists(DataFlowPublic::ParameterNode param, DataFlowPrivate::ReturningNode returnNode |
private predicate flowThrough(DataFlowPublic::ParameterNode param) {
exists(DataFlowPrivate::ReturningNode returnNode |
DataFlowPrivate::LocalFlow::getParameterDefNode(param.getParameter())
.(TypeTrackingNode)
.flowsTo(returnNode) and
.flowsTo(returnNode)
)
}
/** Holds if there is a level step from `nodeFrom` to `nodeTo`, which may depend on the call graph. */
pragma[nomagic]
predicate levelStepCall(Node nodeFrom, Node nodeTo) {
exists(DataFlowPublic::ParameterNode param |
flowThrough(param) and
callStep(nodeTo.asExpr(), nodeFrom, param)
)
or
}
/** Holds if there is a level step from `nodeFrom` to `nodeTo`, which does not depend on the call graph. */
pragma[nomagic]
predicate levelStepNoCall(Node nodeFrom, Node nodeTo) {
exists(
SummarizedCallable callable, DataFlowPublic::CallNode call, SummaryComponentStack input,
SummaryComponentStack output
@@ -99,10 +107,65 @@ private predicate summarizedLocalStep(Node nodeFrom, Node nodeTo) {
nodeFrom = evaluateSummaryComponentStackLocal(callable, call, input) and
nodeTo = evaluateSummaryComponentStackLocal(callable, call, output)
)
or
localFieldStep(nodeFrom, nodeTo)
}
/** Holds if there is a level step from `nodeFrom` to `nodeTo`. */
predicate levelStep(Node nodeFrom, Node nodeTo) { summarizedLocalStep(nodeFrom, nodeTo) }
/**
* Gets a method of `mod`, with `instance` indicating if this is an instance method.
*
* Does not take inheritance or the various forms of inclusion into account.
*/
pragma[nomagic]
private MethodBase getAMethod(ModuleBase mod, boolean instance) {
not mod instanceof SingletonClass and
result = mod.getAMethod() and
if result instanceof SingletonMethod then instance = false else instance = true
or
exists(SingletonClass cls |
cls.getValue().(SelfVariableAccess).getVariable().getDeclaringScope() = mod and
result = cls.getAMethod().(Method) and
instance = false
)
}
/**
* Gets a value flowing into `field` in `mod`, with `instance` indicating if it's
* a field on an instance of `mod` (as opposed to the module object itself).
*/
pragma[nomagic]
private Node fieldPredecessor(ModuleBase mod, boolean instance, string field) {
exists(InstanceVariableWriteAccess access, AssignExpr assign |
access.getReceiver().getVariable().getDeclaringScope() = getAMethod(mod, instance) and
field = access.getVariable().getName() and
assign.getLeftOperand() = access and
result.asExpr().getExpr() = assign.getRightOperand()
)
}
/**
* Gets a reference to `field` in `mod`, with `instance` indicating if it's
* a field on an instance of `mod` (as opposed to the module object itself).
*/
pragma[nomagic]
private Node fieldSuccessor(ModuleBase mod, boolean instance, string field) {
exists(InstanceVariableReadAccess access |
access.getReceiver().getVariable().getDeclaringScope() = getAMethod(mod, instance) and
result.asExpr().getExpr() = access and
field = access.getVariable().getName()
)
}
/**
* Holds if `pred -> succ` should be used a level step, from a field assignment to
* a read within the same class.
*/
private predicate localFieldStep(Node pred, Node succ) {
exists(ModuleBase mod, boolean instance, string field |
pred = fieldPredecessor(mod, instance, field) and
succ = fieldSuccessor(mod, instance, field)
)
}
pragma[noinline]
private predicate argumentPositionMatch(
@@ -325,9 +388,18 @@ private predicate hasStoreSummary(
SummarizedCallable callable, DataFlow::ContentSet contents, SummaryComponentStack input,
SummaryComponentStack output
) {
callable.propagatesFlow(input, push(SummaryComponent::content(contents), output), true) and
not isNonLocal(input.head()) and
not isNonLocal(output.head())
not isNonLocal(output.head()) and
(
callable.propagatesFlow(input, push(SummaryComponent::content(contents), output), true)
or
// Allow the input to start with an arbitrary WithoutContent[X].
// Since type-tracking only tracks one content deep, and we're about to store into another content,
// we're already preventing the input from being in a content.
callable
.propagatesFlow(push(SummaryComponent::withoutContent(_), input),
push(SummaryComponent::content(contents), output), true)
)
}
pragma[nomagic]
@@ -460,6 +532,9 @@ private predicate dependsOnSummaryComponentStack(
callable.propagatesFlow(stack, _, true)
or
callable.propagatesFlow(_, stack, true)
or
// include store summaries as they may skip an initial step at the input
hasStoreSummary(callable, _, stack, _)
)
or
dependsOnSummaryComponentStackCons(callable, _, stack)

View File

@@ -23,7 +23,7 @@
### New Queries
* Added a new query, `rb/log-inection`, to detect cases where a malicious user may be able to forge log entries.
* Added a new query, `rb/log-injection`, to detect cases where a malicious user may be able to forge log entries.
* Added a new query, `rb/incomplete-multi-character-sanitization`. The query
finds string transformations that do not replace all occurrences of a
multi-character substring.
@@ -119,7 +119,7 @@
### New Queries
* A new query (`rb/request-forgery`) has been added. The query finds HTTP requests made with user-controlled URLs.
* A new query (`rb/csrf-protection-disabled`) has been added. The query finds cases where cross-site forgery protection is explictly disabled.
* A new query (`rb/csrf-protection-disabled`) has been added. The query finds cases where cross-site forgery protection is explicitly disabled.
### Query Metadata Changes

View File

@@ -0,0 +1,4 @@
---
category: newQuery
---
* Added a new query, `rb/non-constant-kernel-open`, to detect uses of Kernel.open and related methods with non-constant values.

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* The alert message of many queries have been changed to better follow the style guide and make the message consistent with other languages.

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* The `rb/path-injection` query now treats the `file:` argument of the Rails `render` method as a sink.

View File

@@ -3,7 +3,7 @@
### New Queries
* A new query (`rb/request-forgery`) has been added. The query finds HTTP requests made with user-controlled URLs.
* A new query (`rb/csrf-protection-disabled`) has been added. The query finds cases where cross-site forgery protection is explictly disabled.
* A new query (`rb/csrf-protection-disabled`) has been added. The query finds cases where cross-site forgery protection is explicitly disabled.
### Query Metadata Changes

View File

@@ -2,7 +2,7 @@
### New Queries
* Added a new query, `rb/log-inection`, to detect cases where a malicious user may be able to forge log entries.
* Added a new query, `rb/log-injection`, to detect cases where a malicious user may be able to forge log entries.
* Added a new query, `rb/incomplete-multi-character-sanitization`. The query
finds string transformations that do not replace all occurrences of a
multi-character substring.

View File

@@ -1,6 +1,6 @@
/**
* @name Manually checking http verb instead of using built in rails routes and protections
* @description Manually checking HTTP verbs is an indication that multiple requests are routed to the same controller action. This could lead to bypassing necessary authorization methods and other protections, like CSRF protection. Prefer using different controller actions for each HTTP method and relying Rails routing to handle mappting resources and verbs to specific methods.
* @description Manually checking HTTP verbs is an indication that multiple requests are routed to the same controller action. This could lead to bypassing necessary authorization methods and other protections, like CSRF protection. Prefer using different controller actions for each HTTP method and relying Rails routing to handle mapping resources and verbs to specific methods.
* @kind path-problem
* @problem.severity error
* @security-severity 5.0
@@ -93,4 +93,4 @@ class HttpVerbConfig extends TaintTracking::Configuration {
from HttpVerbConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink,
"Manually checking HTTP verbs is an indication that multiple requests are routed to the same controller action. This could lead to bypassing necessary authorization methods and other protections, like CSRF protection. Prefer using different controller actions for each HTTP method and relying Rails routing to handle mappting resources and verbs to specific methods."
"Manually checking HTTP verbs is an indication that multiple requests are routed to the same controller action. This could lead to bypassing necessary authorization methods and other protections, like CSRF protection. Prefer using different controller actions for each HTTP method and relying Rails routing to handle mapping resources and verbs to specific methods."

View File

@@ -0,0 +1,28 @@
/**
* @name Tainted nodes
* @description Nodes reachable from a remote flow source via default taint-tracking steps.
* @kind problem
* @problem.severity recommendation
* @id rb/meta/tainted-nodes
* @tags meta
* @precision very-low
*/
import internal.TaintMetrics
import codeql.ruby.DataFlow
import codeql.ruby.TaintTracking
class BasicTaintConfiguration extends TaintTracking::Configuration {
BasicTaintConfiguration() { this = "BasicTaintConfiguration" }
override predicate isSource(DataFlow::Node node) { node = relevantTaintSource(_) }
override predicate isSink(DataFlow::Node node) {
// To reduce noise from synthetic nodes, only count nodes that have an associated expression.
exists(node.asExpr().getExpr())
}
}
from DataFlow::Node node
where any(BasicTaintConfiguration cfg).hasFlow(_, node)
select node, "Tainted node"

View File

@@ -36,3 +36,10 @@ DataFlow::Node relevantTaintSink(string kind) {
kind = "UrlRedirect" and result instanceof UrlRedirect::Sink
)
}
/**
* Gets the root folder of the snapshot.
*
* This is selected as the location for project-wide metrics.
*/
Folder projectRoot() { result.getRelativePath() = "" }

View File

@@ -0,0 +1,46 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>If <code>Kernel.open</code> is given a file name that starts with a <code>|</code>
character, it will execute the remaining string as a shell command. If a
malicious user can control the file name, they can execute arbitrary code.
The same vulnerability applies to <code>IO.read</code>.
</p>
</overview>
<recommendation>
<p>Use <code>File.open</code> instead of <code>Kernel.open</code>, as the former
does not have this vulnerability. Similarly, use <code>File.read</code> instead
of <code>IO.read</code>.</p>
</recommendation>
<example>
<p>
The following example shows code that calls <code>Kernel.open</code> on a
user-supplied file path.
</p>
<sample src="examples/kernel_open.rb" />
<p>Instead, <code>File.open</code> should be used, as in the following example.</p>
<sample src="examples/file_open.rb" />
</example>
<references>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/Command_Injection">Command Injection</a>.
</li>
<li>
Example CVE: <a href="https://www.ruby-lang.org/en/news/2021/05/02/os-command-injection-in-rdoc/">Command Injection in RDoc</a>.
</li>
</references>
</qhelp>

View File

@@ -1,46 +1,4 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>If <code>Kernel.open</code> is given a file name that starts with a <code>|</code>
character, it will execute the remaining string as a shell command. If a
malicious user can control the file name, they can execute arbitrary code.
The same vulnerability applies to <code>IO.read</code>.
</p>
</overview>
<recommendation>
<p>Use <code>File.open</code> instead of <code>Kernel.open</code>, as the former
does not have this vulnerability. Similarly, use <code>File.read</code> instead
of <code>IO.read</code>.</p>
</recommendation>
<example>
<p>
The following example shows code that calls <code>Kernel.open</code> on a
user-supplied file path.
</p>
<sample src="examples/kernel_open.rb" />
<p>Instead, <code>File.open</code> should be used, as in the following example.</p>
<sample src="examples/file_open.rb" />
</example>
<references>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/Command_Injection">Command Injection</a>.
</li>
<li>
Example CVE: <a href="https://www.ruby-lang.org/en/news/2021/05/02/os-command-injection-in-rdoc/">Command Injection in RDoc</a>.
</li>
</references>
</qhelp>
<include src="KernelOpen.inc.qhelp" />
</qhelp>

View File

@@ -1,5 +1,5 @@
/**
* @name Use of `Kernel.open` or `IO.read`
* @name Use of `Kernel.open` or `IO.read` with user-controlled input
* @description Using `Kernel.open` or `IO.read` may allow a malicious
* user to execute arbitrary system commands.
* @kind path-problem
@@ -14,39 +14,12 @@
* external/cwe/cwe-073
*/
import codeql.ruby.AST
import codeql.ruby.ApiGraphs
import codeql.ruby.frameworks.core.Kernel::Kernel
import codeql.ruby.TaintTracking
import codeql.ruby.dataflow.BarrierGuards
import codeql.ruby.dataflow.RemoteFlowSources
import codeql.ruby.DataFlow
import codeql.ruby.TaintTracking
import codeql.ruby.dataflow.RemoteFlowSources
import codeql.ruby.dataflow.BarrierGuards
import DataFlow::PathGraph
/**
* A method call that has a suggested replacement.
*/
abstract class Replacement extends DataFlow::CallNode {
abstract string getFrom();
abstract string getTo();
}
class KernelOpenCall extends KernelMethodCall, Replacement {
KernelOpenCall() { this.getMethodName() = "open" }
override string getFrom() { result = "Kernel.open" }
override string getTo() { result = "File.open" }
}
class IOReadCall extends DataFlow::CallNode, Replacement {
IOReadCall() { this = API::getTopLevelMember("IO").getAMethodCall("read") }
override string getFrom() { result = "IO.read" }
override string getTo() { result = "File.read" }
}
import codeql.ruby.security.KernelOpenQuery
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "KernelOpen" }
@@ -54,9 +27,7 @@ class Configuration extends TaintTracking::Configuration {
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) {
exists(KernelOpenCall c | c.getArgument(0) = sink)
or
exists(IOReadCall c | c.getArgument(0) = sink)
sink = any(AmbiguousPathCall r).getPathArgument()
}
override predicate isSanitizer(DataFlow::Node node) {
@@ -73,5 +44,6 @@ where
sourceNode = source.getNode() and
call.getArgument(0) = sink.getNode()
select sink.getNode(), source, sink,
"This call to " + call.(Replacement).getFrom() + " depends on a $@. Replace it with " +
call.(Replacement).getTo() + ".", source.getNode(), "user-provided value"
"This call to " + call.(AmbiguousPathCall).getName() +
" depends on a $@. Consider replacing it with " + call.(AmbiguousPathCall).getReplacement() +
".", source.getNode(), "user-provided value"

View File

@@ -0,0 +1,4 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<include src="KernelOpen.inc.qhelp" />
</qhelp>

View File

@@ -0,0 +1,29 @@
/**
* @name Use of `Kernel.open` or `IO.read` with a non-constant value
* @description Using `Kernel.open` or `IO.read` may allow a malicious
* user to execute arbitrary system commands.
* @kind problem
* @problem.severity warning
* @security-severity 6.5
* @precision high
* @id rb/non-constant-kernel-open
* @tags correctness
* security
* external/cwe/cwe-078
* external/cwe/cwe-088
* external/cwe/cwe-073
*/
import codeql.ruby.security.KernelOpenQuery
import codeql.ruby.ast.Literal
from AmbiguousPathCall call
where
// there is not a constant string argument
not exists(call.getPathArgument().asExpr().getExpr().getConstantValue()) and
// if it's a format string, then the first argument is not a constant string
not call.getPathArgument().getALocalSource().asExpr().getExpr().(StringLiteral).getComponent(0)
instanceof StringTextComponent
select call,
"Call to " + call.getName() + " with a non-constant value. Consider replacing it with " +
call.getReplacement() + "."

View File

@@ -27,8 +27,6 @@ where
// NOTE: We compare the locations instead of DataFlow::Nodes directly, since for
// snippet `Excon.defaults[:ssl_verify_peer] = false`, `disablingNode = argumentNode`
// does NOT hold.
if disablingNode.getLocation() = origin.getLocation()
then ending = "."
else ending = " by the value from $@."
select request, "This request may run without certificate validation because it is $@" + ending,
disablingNode, "disabled here", origin, "here"
if disablingNode.getLocation() = origin.getLocation() then ending = "." else ending = " by $@."
select request, "This request may run without certificate validation because $@" + ending,
disablingNode, "the request is disabled", origin, "this value"

View File

@@ -20,5 +20,5 @@ import DataFlow::PathGraph
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Sensitive data returned by $@ is logged here.",
select sink.getNode(), source, sink, "This logs sensitive data returned by $@ as clear text.",
source.getNode(), source.getNode().(Source).describe()

View File

@@ -21,5 +21,5 @@ import DataFlow::PathGraph
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Sensitive data returned by $@ is stored here.",
select sink.getNode(), source, sink, "This stores sensitive data returned by $@ as clear text.",
source.getNode(), source.getNode().(Source).describe()

View File

@@ -12,7 +12,7 @@ to execute arbitrary code.
<recommendation>
<p>
Avoid deserialization of untrusted data if possible. If the architecture permits
it, use serialization formats that cannot represent arbitarary objects. For
it, use serialization formats that cannot represent arbitrary objects. For
libraries that support it, such as the Ruby standard library's <code>JSON</code>
module, ensure that the parser is configured to disable
deserialization of arbitrary objects.

View File

@@ -63,5 +63,6 @@ from
DataFlow::PathNode source, DataFlow::PathNode sink, PermissivePermissionsConfig conf,
FileSystemPermissionModification mod
where conf.hasFlowPath(source, sink) and mod.getAPermissionNode() = sink.getNode()
select source.getNode(), source, sink, "Overly permissive mask in $@ sets file to $@.", mod,
mod.toString(), source.getNode(), source.getNode().toString()
select source.getNode(), source, sink,
"This overly permissive mask used in $@ allows read or write access to others.", mod,
mod.toString()

View File

@@ -154,4 +154,5 @@ class HardcodedCredentialsConfiguration extends DataFlow::Configuration {
from DataFlow::PathNode source, DataFlow::PathNode sink, HardcodedCredentialsConfiguration conf
where conf.hasFlowPath(source, sink)
select source.getNode(), source, sink, "Use of $@.", source.getNode(), "hardcoded credentials"
select source.getNode(), source, sink, "This hardcoded value is $@.", sink.getNode(),
"used as credentials"

View File

@@ -4,7 +4,7 @@
<qhelp>
<overview>
<p>
Downloading executeables or other sensitive files over an unencrypted connection
Downloading executables or other sensitive files over an unencrypted connection
can leave a server open to man-in-the-middle attacks (MITM).
Such an attack can allow an attacker to insert arbitrary content
into the downloaded file, and in the worst case, allow the attacker to execute

View File

@@ -18,4 +18,5 @@ 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"
select sink.getNode(), source, sink, "Write to file system depends on $@.", source.getNode(),
"untrusted data"

View File

@@ -1,6 +1,3 @@
| array_flow.rb:3:16:3:35 | # $ hasValueFlow=0.1 | Missing result:hasValueFlow=0.1 |
| array_flow.rb:5:16:5:35 | # $ hasValueFlow=0.1 | Missing result:hasValueFlow=0.1 |
| array_flow.rb:83:13:83:30 | # $ hasValueFlow=9 | Missing result:hasValueFlow=9 |
| array_flow.rb:107:10:107:13 | ...[...] | Unexpected result: hasValueFlow=11.2 |
| array_flow.rb:179:28:179:46 | # $ hasValueFlow=19 | Missing result:hasValueFlow=19 |
| array_flow.rb:180:28:180:46 | # $ hasValueFlow=19 | Missing result:hasValueFlow=19 |

View File

@@ -16,11 +16,16 @@ track
| type_tracker.rb:2:5:5:7 | self in field= | type tracker with call steps | type_tracker.rb:7:5:9:7 | self in field |
| type_tracker.rb:2:5:5:7 | self in field= | type tracker without call steps | type_tracker.rb:2:5:5:7 | self in field= |
| type_tracker.rb:2:16:2:18 | val | type tracker with call steps | type_tracker.rb:2:16:2:18 | val |
| type_tracker.rb:2:16:2:18 | val | type tracker with call steps | type_tracker.rb:8:9:8:14 | @field |
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:2:5:5:7 | return return in field= |
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:2:16:2:18 | val |
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:2:16:2:18 | val |
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:2:16:2:18 | val |
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:3:14:3:23 | call to field |
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:7:5:9:7 | return return in field |
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:8:9:8:14 | @field |
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:14:5:14:13 | call to field= |
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:15:10:15:18 | call to field |
| type_tracker.rb:3:9:3:23 | call to puts | type tracker without call steps | type_tracker.rb:3:9:3:23 | call to puts |
| type_tracker.rb:3:14:3:23 | call to field | type tracker without call steps | type_tracker.rb:3:14:3:23 | call to field |
| type_tracker.rb:4:9:4:14 | @field | type tracker without call steps | type_tracker.rb:4:9:4:14 | @field |
@@ -55,6 +60,7 @@ track
| type_tracker.rb:14:5:14:13 | call to field= | type tracker without call steps | type_tracker.rb:14:5:14:13 | call to field= |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps | type_tracker.rb:2:16:2:18 | val |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps | type_tracker.rb:2:16:2:18 | val |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps | type_tracker.rb:8:9:8:14 | @field |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps with content attribute field | type_tracker.rb:7:5:9:7 | self (field) |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps with content attribute field | type_tracker.rb:7:5:9:7 | self in field |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker without call steps | type_tracker.rb:14:5:14:13 | call to field= |
@@ -368,11 +374,16 @@ trackEnd
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:16:2:18 | val |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:16:2:18 | val |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:16:2:18 | val |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:3:14:3:23 | call to field |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:4:9:4:20 | ... = ... |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:4:9:4:20 | ... = ... |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:4:18:4:20 | val |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:4:18:4:20 | val |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:7:5:9:7 | return return in field |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:8:9:8:14 | @field |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:8:9:8:14 | @field |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:14:5:14:13 | call to field= |
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:15:10:15:18 | call to field |
| type_tracker.rb:3:9:3:23 | call to puts | type_tracker.rb:3:9:3:23 | call to puts |
| type_tracker.rb:3:14:3:23 | call to field | type_tracker.rb:3:14:3:23 | call to field |
| type_tracker.rb:4:9:4:14 | @field | type_tracker.rb:4:9:4:14 | @field |
@@ -424,6 +435,7 @@ trackEnd
| type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:2:16:2:18 | val |
| type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:4:9:4:20 | ... = ... |
| type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:4:18:4:20 | val |
| type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:8:9:8:14 | @field |
| type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:14:5:14:13 | __synth__0 |
| type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:14:5:14:13 | call to field= |
| type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:14:5:14:23 | ... |

View File

@@ -1,17 +1,19 @@
actionControllerControllerClasses
| action_controller/input_access.rb:1:1:50:3 | UsersController |
| action_controller/params_flow.rb:1:1:151:3 | MyController |
| active_record/ActiveRecord.rb:23:1:39:3 | FooController |
| active_record/ActiveRecord.rb:41:1:64:3 | BarController |
| active_record/ActiveRecord.rb:66:1:98:3 | BazController |
| active_record/ActiveRecord.rb:100:1:108:3 | AnnotatedController |
| active_storage/active_storage.rb:39:1:45:3 | PostsController |
| app/controllers/comments_controller.rb:1:1:7:3 | CommentsController |
| app/controllers/comments_controller.rb:1:1:14:3 | CommentsController |
| app/controllers/foo/bars_controller.rb:3:1:46:3 | BarsController |
| app/controllers/photos_controller.rb:1:1:4:3 | PhotosController |
| app/controllers/posts_controller.rb:1:1:10:3 | PostsController |
| app/controllers/tags_controller.rb:1:1:2:3 | TagsController |
| app/controllers/users/notifications_controller.rb:2:3:5:5 | NotificationsController |
actionControllerActionMethods
| action_controller/input_access.rb:2:3:49:5 | index |
| action_controller/params_flow.rb:2:3:4:5 | m1 |
| action_controller/params_flow.rb:6:3:8:5 | m2 |
| action_controller/params_flow.rb:10:3:12:5 | m2 |
@@ -59,8 +61,8 @@ actionControllerActionMethods
| active_record/ActiveRecord.rb:101:3:103:5 | index |
| active_record/ActiveRecord.rb:105:3:107:5 | unsafe_action |
| active_storage/active_storage.rb:40:3:44:5 | create |
| app/controllers/comments_controller.rb:2:3:3:5 | index |
| app/controllers/comments_controller.rb:5:3:6:5 | show |
| app/controllers/comments_controller.rb:2:3:10:5 | index |
| app/controllers/comments_controller.rb:12:3:13:5 | show |
| app/controllers/foo/bars_controller.rb:5:3:7:5 | index |
| app/controllers/foo/bars_controller.rb:9:3:18:5 | show_debug |
| app/controllers/foo/bars_controller.rb:20:3:24:5 | show |
@@ -115,6 +117,7 @@ paramsCalls
| action_controller/params_flow.rb:144:10:144:15 | call to params |
| action_controller/params_flow.rb:145:32:145:37 | call to params |
| action_controller/params_flow.rb:148:22:148:27 | call to params |
| action_mailer/mailer.rb:3:10:3:15 | call to params |
| active_record/ActiveRecord.rb:28:30:28:35 | call to params |
| active_record/ActiveRecord.rb:29:29:29:34 | call to params |
| active_record/ActiveRecord.rb:30:31:30:36 | call to params |
@@ -189,6 +192,7 @@ paramsSources
| action_controller/params_flow.rb:144:10:144:15 | call to params |
| action_controller/params_flow.rb:145:32:145:37 | call to params |
| action_controller/params_flow.rb:148:22:148:27 | call to params |
| action_mailer/mailer.rb:3:10:3:15 | call to params |
| active_record/ActiveRecord.rb:28:30:28:35 | call to params |
| active_record/ActiveRecord.rb:29:29:29:34 | call to params |
| active_record/ActiveRecord.rb:30:31:30:36 | call to params |
@@ -220,6 +224,137 @@ paramsSources
| app/controllers/foo/bars_controller.rb:21:21:21:26 | call to params |
| app/controllers/foo/bars_controller.rb:22:10:22:15 | call to params |
| app/views/foo/bars/show.html.erb:5:9:5:14 | call to params |
httpInputAccesses
| action_controller/input_access.rb:3:5:3:18 | call to params | ActionDispatch::Request#params |
| action_controller/input_access.rb:4:5:4:22 | call to parameters | ActionDispatch::Request#parameters |
| action_controller/input_access.rb:5:5:5:15 | call to GET | ActionDispatch::Request#GET |
| action_controller/input_access.rb:6:5:6:16 | call to POST | ActionDispatch::Request#POST |
| action_controller/input_access.rb:7:5:7:28 | call to query_parameters | ActionDispatch::Request#query_parameters |
| action_controller/input_access.rb:8:5:8:30 | call to request_parameters | ActionDispatch::Request#request_parameters |
| action_controller/input_access.rb:9:5:9:31 | call to filtered_parameters | ActionDispatch::Request#filtered_parameters |
| action_controller/input_access.rb:11:5:11:25 | call to authorization | ActionDispatch::Request#authorization |
| action_controller/input_access.rb:12:5:12:23 | call to script_name | ActionDispatch::Request#script_name |
| action_controller/input_access.rb:13:5:13:21 | call to path_info | ActionDispatch::Request#path_info |
| action_controller/input_access.rb:14:5:14:22 | call to user_agent | ActionDispatch::Request#user_agent |
| action_controller/input_access.rb:15:5:15:19 | call to referer | ActionDispatch::Request#referer |
| action_controller/input_access.rb:16:5:16:20 | call to referrer | ActionDispatch::Request#referrer |
| action_controller/input_access.rb:17:5:17:26 | call to host_authority | ActionDispatch::Request#host_authority |
| action_controller/input_access.rb:18:5:18:24 | call to content_type | ActionDispatch::Request#content_type |
| action_controller/input_access.rb:19:5:19:16 | call to host | ActionDispatch::Request#host |
| action_controller/input_access.rb:20:5:20:20 | call to hostname | ActionDispatch::Request#hostname |
| action_controller/input_access.rb:21:5:21:27 | call to accept_encoding | ActionDispatch::Request#accept_encoding |
| action_controller/input_access.rb:22:5:22:27 | call to accept_language | ActionDispatch::Request#accept_language |
| action_controller/input_access.rb:23:5:23:25 | call to if_none_match | ActionDispatch::Request#if_none_match |
| action_controller/input_access.rb:24:5:24:31 | call to if_none_match_etags | ActionDispatch::Request#if_none_match_etags |
| action_controller/input_access.rb:25:5:25:29 | call to content_mime_type | ActionDispatch::Request#content_mime_type |
| action_controller/input_access.rb:27:5:27:21 | call to authority | ActionDispatch::Request#authority |
| action_controller/input_access.rb:28:5:28:16 | call to host | ActionDispatch::Request#host |
| action_controller/input_access.rb:29:5:29:26 | call to host_authority | ActionDispatch::Request#host_authority |
| action_controller/input_access.rb:30:5:30:26 | call to host_with_port | ActionDispatch::Request#host_with_port |
| action_controller/input_access.rb:31:5:31:20 | call to hostname | ActionDispatch::Request#hostname |
| action_controller/input_access.rb:32:5:32:25 | call to forwarded_for | ActionDispatch::Request#forwarded_for |
| action_controller/input_access.rb:33:5:33:26 | call to forwarded_host | ActionDispatch::Request#forwarded_host |
| action_controller/input_access.rb:34:5:34:16 | call to port | ActionDispatch::Request#port |
| action_controller/input_access.rb:35:5:35:26 | call to forwarded_port | ActionDispatch::Request#forwarded_port |
| action_controller/input_access.rb:37:5:37:22 | call to media_type | ActionDispatch::Request#media_type |
| action_controller/input_access.rb:38:5:38:29 | call to media_type_params | ActionDispatch::Request#media_type_params |
| action_controller/input_access.rb:39:5:39:27 | call to content_charset | ActionDispatch::Request#content_charset |
| action_controller/input_access.rb:40:5:40:20 | call to base_url | ActionDispatch::Request#base_url |
| action_controller/input_access.rb:42:5:42:16 | call to body | ActionDispatch::Request#body |
| action_controller/input_access.rb:43:5:43:20 | call to raw_post | ActionDispatch::Request#raw_post |
| action_controller/input_access.rb:45:5:45:30 | ...[...] | ActionDispatch::Request#env[] |
| action_controller/input_access.rb:47:5:47:39 | ...[...] | ActionDispatch::Request#env[] |
| action_controller/params_flow.rb:3:10:3:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:7:10:7:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:11:10:11:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:15:10:15:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:19:10:19:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:23:10:23:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:27:10:27:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:31:10:31:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:35:10:35:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:39:10:39:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:43:10:43:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:47:10:47:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:51:10:51:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:55:10:55:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:59:10:59:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:63:10:63:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:67:10:67:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:71:10:71:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:75:10:75:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:79:10:79:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:83:10:83:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:87:10:87:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:91:10:91:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:95:10:95:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:99:10:99:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:103:10:103:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:107:10:107:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:111:10:111:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:112:23:112:28 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:116:10:116:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:117:31:117:36 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:121:10:121:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:122:31:122:36 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:126:10:126:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:127:24:127:29 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:130:14:130:19 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:135:10:135:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:136:32:136:37 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:139:22:139:27 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:144:10:144:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:145:32:145:37 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:148:22:148:27 | call to params | ActionController::Metal#params |
| action_mailer/mailer.rb:3:10:3:15 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:28:30:28:35 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:29:29:29:34 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:30:31:30:36 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:32:21:32:26 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:34:34:34:39 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:35:23:35:28 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:35:38:35:43 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:43:10:43:15 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:50:11:50:16 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:54:12:54:17 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:59:12:59:17 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:62:15:62:20 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:68:21:68:26 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:72:18:72:23 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:76:24:76:29 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:76:49:76:54 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:80:25:80:30 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:80:50:80:55 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:88:21:88:26 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:92:27:92:32 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:92:52:92:57 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:96:28:96:33 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:96:53:96:58 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:106:59:106:64 | call to params | ActionController::Metal#params |
| active_storage/active_storage.rb:41:21:41:26 | call to params | ActionController::Metal#params |
| active_storage/active_storage.rb:42:24:42:29 | call to params | ActionController::Metal#params |
| app/controllers/comments_controller.rb:3:5:3:18 | call to params | ActionDispatch::Request#params |
| app/controllers/comments_controller.rb:4:5:4:22 | call to parameters | ActionDispatch::Request#parameters |
| app/controllers/comments_controller.rb:5:5:5:15 | call to GET | ActionDispatch::Request#GET |
| app/controllers/comments_controller.rb:6:5:6:16 | call to POST | ActionDispatch::Request#POST |
| app/controllers/comments_controller.rb:7:5:7:28 | call to query_parameters | ActionDispatch::Request#query_parameters |
| app/controllers/comments_controller.rb:8:5:8:30 | call to request_parameters | ActionDispatch::Request#request_parameters |
| app/controllers/comments_controller.rb:9:5:9:31 | call to filtered_parameters | ActionDispatch::Request#filtered_parameters |
| app/controllers/foo/bars_controller.rb:10:27:10:33 | call to cookies | ActionController::Metal#cookies |
| app/controllers/foo/bars_controller.rb:13:21:13:26 | call to params | ActionController::Metal#params |
| app/controllers/foo/bars_controller.rb:14:10:14:15 | call to params | ActionController::Metal#params |
| app/controllers/foo/bars_controller.rb:21:21:21:26 | call to params | ActionController::Metal#params |
| app/controllers/foo/bars_controller.rb:22:10:22:15 | call to params | ActionController::Metal#params |
| app/graphql/mutations/dummy.rb:5:24:5:25 | id | GraphQL RoutedParameter |
| app/graphql/mutations/dummy.rb:9:17:9:25 | something | GraphQL RoutedParameter |
| app/graphql/resolvers/dummy_resolver.rb:6:24:6:25 | id | GraphQL RoutedParameter |
| app/graphql/resolvers/dummy_resolver.rb:10:17:10:25 | something | GraphQL RoutedParameter |
| app/graphql/types/query_type.rb:10:18:10:23 | number | GraphQL RoutedParameter |
| app/graphql/types/query_type.rb:18:23:18:33 | blah_number | GraphQL RoutedParameter |
| app/graphql/types/query_type.rb:27:20:27:25 | **args | GraphQL RoutedParameter |
| app/graphql/types/query_type.rb:36:34:36:37 | arg1 | GraphQL RoutedParameter |
| app/graphql/types/query_type.rb:36:41:36:46 | **rest | GraphQL RoutedParameter |
| app/views/foo/bars/show.html.erb:5:9:5:14 | call to params | ActionController::Metal#params |
cookiesCalls
| app/controllers/foo/bars_controller.rb:10:27:10:33 | call to cookies |
cookiesSources

View File

@@ -1,6 +1,8 @@
private import codeql.ruby.AST
private import codeql.ruby.frameworks.ActionController
private import codeql.ruby.frameworks.Rails
private import codeql.ruby.frameworks.ActionView
private import codeql.ruby.Concepts
query predicate actionControllerControllerClasses(ActionControllerControllerClass cls) { any() }
@@ -10,6 +12,10 @@ query predicate paramsCalls(Rails::ParamsCall c) { any() }
query predicate paramsSources(ParamsSource src) { any() }
query predicate httpInputAccesses(Http::Server::RequestInputAccess a, string sourceType) {
sourceType = a.getSourceType()
}
query predicate cookiesCalls(Rails::CookiesCall c) { any() }
query predicate cookiesSources(CookiesSource src) { any() }

View File

@@ -36,8 +36,8 @@ actionDispatchRoutes
actionDispatchControllerMethods
| app/config/routes.rb:2:3:8:5 | call to resources | app/controllers/posts_controller.rb:2:3:3:5 | index |
| app/config/routes.rb:2:3:8:5 | call to resources | app/controllers/posts_controller.rb:5:3:6:5 | show |
| app/config/routes.rb:3:5:6:7 | call to resources | app/controllers/comments_controller.rb:2:3:3:5 | index |
| app/config/routes.rb:3:5:6:7 | call to resources | app/controllers/comments_controller.rb:5:3:6:5 | show |
| app/config/routes.rb:3:5:6:7 | call to resources | app/controllers/comments_controller.rb:2:3:10:5 | index |
| app/config/routes.rb:3:5:6:7 | call to resources | app/controllers/comments_controller.rb:12:3:13:5 | show |
| app/config/routes.rb:7:5:7:37 | call to post | app/controllers/posts_controller.rb:8:3:9:5 | upvote |
| app/config/routes.rb:27:3:27:48 | call to match | app/controllers/photos_controller.rb:2:3:3:5 | show |
| app/config/routes.rb:28:3:28:50 | call to match | app/controllers/photos_controller.rb:2:3:3:5 | show |

View File

@@ -0,0 +1,50 @@
class UsersController < ActionController::Base
def index
request.params
request.parameters
request.GET
request.POST
request.query_parameters
request.request_parameters
request.filtered_parameters
request.authorization
request.script_name
request.path_info
request.user_agent
request.referer
request.referrer
request.host_authority
request.content_type
request.host
request.hostname
request.accept_encoding
request.accept_language
request.if_none_match
request.if_none_match_etags
request.content_mime_type
request.authority
request.host
request.host_authority
request.host_with_port
request.hostname
request.forwarded_for
request.forwarded_host
request.port
request.forwarded_port
request.media_type
request.media_type_params
request.content_charset
request.base_url
request.body
request.raw_post
request.env["HTTP_ACCEPT"]
request.env["NOT_USER_CONTROLLED"]
request.filtered_env["HTTP_ACCEPT"]
request.filtered_env["NOT_USER_CONTROLLED"]
end
end

View File

@@ -0,0 +1,5 @@
class MyMailer < ActionMailer::Base
def foo
sink params[:foo] # $hasTaintFlow
end
end

View File

@@ -0,0 +1,9 @@
failures
edges
| mailer.rb:3:10:3:15 | call to params : | mailer.rb:3:10:3:21 | ...[...] |
nodes
| mailer.rb:3:10:3:15 | call to params : | semmle.label | call to params : |
| mailer.rb:3:10:3:21 | ...[...] | semmle.label | ...[...] |
subpaths
#select
| mailer.rb:3:10:3:21 | ...[...] | mailer.rb:3:10:3:15 | call to params : | mailer.rb:3:10:3:21 | ...[...] | $@ | mailer.rb:3:10:3:15 | call to params : | call to params : |

View File

@@ -0,0 +1,18 @@
/**
* @kind path-problem
*/
import ruby
import TestUtilities.InlineFlowTest
import PathGraph
import codeql.ruby.frameworks.Rails
class ParamsTaintFlowConf extends DefaultTaintFlowConf {
override predicate isSource(DataFlow::Node n) {
n.asExpr().getExpr() instanceof Rails::ParamsCall
}
}
from DataFlow::PathNode source, DataFlow::PathNode sink, ParamsTaintFlowConf conf
where conf.hasFlowPath(source, sink)
select sink, source, sink, "$@", source, source.toString()

View File

@@ -136,6 +136,14 @@ edges
| active_support.rb:191:34:191:34 | a : | active_support.rb:191:7:191:35 | call to new : |
| active_support.rb:192:7:192:7 | x : | active_support.rb:192:7:192:16 | call to to_param : |
| active_support.rb:192:7:192:16 | call to to_param : | active_support.rb:193:8:193:8 | y |
| active_support.rb:197:7:197:16 | call to source : | active_support.rb:198:20:198:20 | a : |
| active_support.rb:198:7:198:21 | call to new : | active_support.rb:199:7:199:7 | x : |
| active_support.rb:198:20:198:20 | a : | active_support.rb:198:7:198:21 | call to new : |
| active_support.rb:199:7:199:7 | x : | active_support.rb:199:7:199:17 | call to existence : |
| active_support.rb:199:7:199:17 | call to existence : | active_support.rb:200:8:200:8 | y |
| active_support.rb:199:7:199:17 | call to existence : | active_support.rb:201:7:201:7 | y : |
| active_support.rb:201:7:201:7 | y : | active_support.rb:201:7:201:17 | call to existence : |
| active_support.rb:201:7:201:17 | call to existence : | active_support.rb:202:8:202:8 | z |
nodes
| active_support.rb:9:9:9:18 | call to source : | semmle.label | call to source : |
| active_support.rb:10:10:10:10 | x : | semmle.label | x : |
@@ -310,6 +318,15 @@ nodes
| active_support.rb:192:7:192:7 | x : | semmle.label | x : |
| active_support.rb:192:7:192:16 | call to to_param : | semmle.label | call to to_param : |
| active_support.rb:193:8:193:8 | y | semmle.label | y |
| active_support.rb:197:7:197:16 | call to source : | semmle.label | call to source : |
| active_support.rb:198:7:198:21 | call to new : | semmle.label | call to new : |
| active_support.rb:198:20:198:20 | a : | semmle.label | a : |
| active_support.rb:199:7:199:7 | x : | semmle.label | x : |
| active_support.rb:199:7:199:17 | call to existence : | semmle.label | call to existence : |
| active_support.rb:200:8:200:8 | y | semmle.label | y |
| active_support.rb:201:7:201:7 | y : | semmle.label | y : |
| active_support.rb:201:7:201:17 | call to existence : | semmle.label | call to existence : |
| active_support.rb:202:8:202:8 | z | semmle.label | z |
subpaths
#select
| active_support.rb:106:10:106:13 | ...[...] | active_support.rb:104:10:104:17 | call to source : | active_support.rb:106:10:106:13 | ...[...] | $@ | active_support.rb:104:10:104:17 | call to source : | call to source : |

View File

@@ -192,3 +192,12 @@ def m_safe_buffer_to_param
y = x.to_param
sink y # $hasTaintFlow=a
end
def m_pathname_existence
a = source "a"
x = Pathname.new(a)
y = x.existence
sink y # $hasTaintFlow=a
z = y.existence
sink z # $hasTaintFlow=a
end

View File

@@ -1,7 +1,14 @@
class CommentsController < ApplicationController
def index
request.params
request.parameters
request.GET
request.POST
request.query_parameters
request.request_parameters
request.filtered_parameters
end
def show
end
end
end

View File

@@ -120,6 +120,13 @@ hello.rb:
# 18| HelloWorld
#-----| super -> Greeting
instance_fields.rb:
# 11| A_target
#-----| super -> Object
# 26| B_target
#-----| super -> Object
modules.rb:
# 1| Empty

View File

@@ -219,6 +219,10 @@ getTarget
| hello.rb:14:16:14:20 | call to hello | hello.rb:2:5:4:7 | hello |
| hello.rb:20:16:20:20 | call to super | hello.rb:13:5:15:7 | message |
| hello.rb:20:30:20:34 | call to world | hello.rb:5:5:7:7 | world |
| instance_fields.rb:4:22:4:35 | call to new | calls.rb:117:5:117:16 | new |
| instance_fields.rb:7:13:7:25 | call to target | instance_fields.rb:12:5:13:7 | target |
| instance_fields.rb:19:22:19:35 | call to new | calls.rb:117:5:117:16 | new |
| instance_fields.rb:22:13:22:25 | call to target | instance_fields.rb:27:5:28:7 | target |
| modules.rb:12:5:12:26 | call to puts | calls.rb:102:5:102:30 | puts |
| modules.rb:22:3:22:19 | call to puts | calls.rb:102:5:102:30 | puts |
| modules.rb:33:3:33:25 | call to puts | calls.rb:102:5:102:30 | puts |
@@ -435,6 +439,12 @@ publicMethod
| hello.rb:5:5:7:7 | world |
| hello.rb:13:5:15:7 | message |
| hello.rb:19:5:21:7 | message |
| instance_fields.rb:3:9:5:11 | create |
| instance_fields.rb:6:9:8:11 | use |
| instance_fields.rb:12:5:13:7 | target |
| instance_fields.rb:18:9:20:11 | create |
| instance_fields.rb:21:9:23:11 | use |
| instance_fields.rb:27:5:28:7 | target |
| modules.rb:9:5:10:7 | method_in_foo_bar |
| modules.rb:16:3:17:5 | method_in_foo |
| modules.rb:27:3:28:5 | method_in_another_definition_of_foo |

View File

@@ -0,0 +1,29 @@
class A
class << self
def create
@field = ::A_target.new
end
def use
@field.target
end
end
end
class A_target
def target
end
end
class B
class << self
def create
@field = ::B_target.new
end
def use
@field.target
end
end
end
class B_target
def target
end
end

View File

@@ -47,6 +47,8 @@ getMethod
| hello.rb:1:1:8:3 | EnglishWords | world | hello.rb:5:5:7:7 | world |
| hello.rb:11:1:16:3 | Greeting | message | hello.rb:13:5:15:7 | message |
| hello.rb:18:1:22:3 | HelloWorld | message | hello.rb:19:5:21:7 | message |
| instance_fields.rb:11:1:14:3 | A_target | target | instance_fields.rb:12:5:13:7 | target |
| instance_fields.rb:26:1:29:3 | B_target | target | instance_fields.rb:27:5:28:7 | target |
| modules.rb:4:1:24:3 | Foo | method_in_another_definition_of_foo | modules.rb:27:3:28:5 | method_in_another_definition_of_foo |
| modules.rb:4:1:24:3 | Foo | method_in_foo | modules.rb:16:3:17:5 | method_in_foo |
| modules.rb:5:3:14:5 | Foo::Bar | method_in_another_definition_of_foo_bar | modules.rb:52:3:53:5 | method_in_another_definition_of_foo_bar |
@@ -430,6 +432,14 @@ lookupMethod
| hello.rb:18:1:22:3 | HelloWorld | puts | calls.rb:102:5:102:30 | puts |
| hello.rb:18:1:22:3 | HelloWorld | to_s | calls.rb:172:5:173:7 | to_s |
| hello.rb:18:1:22:3 | HelloWorld | world | hello.rb:5:5:7:7 | world |
| instance_fields.rb:11:1:14:3 | A_target | new | calls.rb:117:5:117:16 | new |
| instance_fields.rb:11:1:14:3 | A_target | puts | calls.rb:102:5:102:30 | puts |
| instance_fields.rb:11:1:14:3 | A_target | target | instance_fields.rb:12:5:13:7 | target |
| instance_fields.rb:11:1:14:3 | A_target | to_s | calls.rb:172:5:173:7 | to_s |
| instance_fields.rb:26:1:29:3 | B_target | new | calls.rb:117:5:117:16 | new |
| instance_fields.rb:26:1:29:3 | B_target | puts | calls.rb:102:5:102:30 | puts |
| instance_fields.rb:26:1:29:3 | B_target | target | instance_fields.rb:27:5:28:7 | target |
| instance_fields.rb:26:1:29:3 | B_target | to_s | calls.rb:172:5:173:7 | to_s |
| modules.rb:4:1:24:3 | Foo | method_in_another_definition_of_foo | modules.rb:27:3:28:5 | method_in_another_definition_of_foo |
| modules.rb:4:1:24:3 | Foo | method_in_foo | modules.rb:16:3:17:5 | method_in_foo |
| modules.rb:5:3:14:5 | Foo::Bar | method_in_another_definition_of_foo_bar | modules.rb:52:3:53:5 | method_in_another_definition_of_foo_bar |
@@ -852,6 +862,22 @@ enclosingMethod
| hello.rb:20:30:20:34 | self | hello.rb:19:5:21:7 | message |
| hello.rb:20:38:20:40 | "!" | hello.rb:19:5:21:7 | message |
| hello.rb:20:39:20:39 | ! | hello.rb:19:5:21:7 | message |
| instance_fields.rb:4:13:4:18 | @field | instance_fields.rb:3:9:5:11 | create |
| instance_fields.rb:4:13:4:18 | self | instance_fields.rb:3:9:5:11 | create |
| instance_fields.rb:4:13:4:35 | ... = ... | instance_fields.rb:3:9:5:11 | create |
| instance_fields.rb:4:22:4:31 | A_target | instance_fields.rb:3:9:5:11 | create |
| instance_fields.rb:4:22:4:35 | call to new | instance_fields.rb:3:9:5:11 | create |
| instance_fields.rb:7:13:7:18 | @field | instance_fields.rb:6:9:8:11 | use |
| instance_fields.rb:7:13:7:18 | self | instance_fields.rb:6:9:8:11 | use |
| instance_fields.rb:7:13:7:25 | call to target | instance_fields.rb:6:9:8:11 | use |
| instance_fields.rb:19:13:19:18 | @field | instance_fields.rb:18:9:20:11 | create |
| instance_fields.rb:19:13:19:18 | self | instance_fields.rb:18:9:20:11 | create |
| instance_fields.rb:19:13:19:35 | ... = ... | instance_fields.rb:18:9:20:11 | create |
| instance_fields.rb:19:22:19:31 | B_target | instance_fields.rb:18:9:20:11 | create |
| instance_fields.rb:19:22:19:35 | call to new | instance_fields.rb:18:9:20:11 | create |
| instance_fields.rb:22:13:22:18 | @field | instance_fields.rb:21:9:23:11 | use |
| instance_fields.rb:22:13:22:18 | self | instance_fields.rb:21:9:23:11 | use |
| instance_fields.rb:22:13:22:25 | call to target | instance_fields.rb:21:9:23:11 | use |
| private.rb:84:7:84:32 | call to puts | private.rb:83:11:85:5 | m1 |
| private.rb:84:7:84:32 | self | private.rb:83:11:85:5 | m1 |
| private.rb:84:12:84:32 | "PrivateOverride1#m1" | private.rb:83:11:85:5 | m1 |

Some files were not shown because too many files have changed in this diff Show More