mirror of
https://github.com/github/codeql.git
synced 2026-04-30 03:05:15 +02:00
Merge remote-tracking branch 'upstream/main' into 'rc/3.14'
This commit is contained in:
4
ruby/ql/lib/change-notes/2024-05-15-cleartext-sources.md
Normal file
4
ruby/ql/lib/change-notes/2024-05-15-cleartext-sources.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The `CleartextSources.qll` library, used by `rb/clear-text-logging-sensitive-data` and `rb/clear-text-logging-sensitive-data`, has been updated to consider heuristics for additional categories of sensitive data.
|
||||
@@ -725,6 +725,7 @@ private module Cached {
|
||||
newtype TOptionalContentSet =
|
||||
TSingletonContent(Content c) or
|
||||
TAnyElementContent() or
|
||||
TAnyContent() or
|
||||
TKnownOrUnknownElementContent(Content::KnownElementContent c) or
|
||||
TElementLowerBoundContent(int lower, boolean includeUnknown) {
|
||||
FlowSummaryImpl::ParsePositions::isParsedElementLowerBoundPosition(_, includeUnknown, lower)
|
||||
@@ -736,7 +737,7 @@ private module Cached {
|
||||
|
||||
cached
|
||||
class TContentSet =
|
||||
TSingletonContent or TAnyElementContent or TKnownOrUnknownElementContent or
|
||||
TSingletonContent or TAnyElementContent or TAnyContent or TKnownOrUnknownElementContent or
|
||||
TElementLowerBoundContent or TElementContentOfTypeContent;
|
||||
|
||||
private predicate trackKnownValue(ConstantValue cv) {
|
||||
@@ -2086,7 +2087,6 @@ private predicate compatibleTypesNonSymRefl(DataFlowType t1, DataFlowType t2) {
|
||||
* Holds if `t1` and `t2` are compatible, that is, whether data can flow from
|
||||
* a node of type `t1` to a node of type `t2`.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate compatibleTypes(DataFlowType t1, DataFlowType t2) {
|
||||
t1 = t2
|
||||
or
|
||||
|
||||
@@ -689,6 +689,9 @@ class ContentSet extends TContentSet {
|
||||
/** Holds if this content set represents all `ElementContent`s. */
|
||||
predicate isAnyElement() { this = TAnyElementContent() }
|
||||
|
||||
/** Holds if this content set represents all contents. */
|
||||
predicate isAny() { this = TAnyContent() }
|
||||
|
||||
/**
|
||||
* Holds if this content set represents a specific known element index, or an
|
||||
* unknown element index.
|
||||
@@ -737,6 +740,9 @@ class ContentSet extends TContentSet {
|
||||
this.isAnyElement() and
|
||||
result = "any element"
|
||||
or
|
||||
this.isAny() and
|
||||
result = "any"
|
||||
or
|
||||
exists(Content::KnownElementContent c |
|
||||
this.isKnownOrUnknownElement(c) and
|
||||
result = c + " or unknown"
|
||||
@@ -790,13 +796,8 @@ class ContentSet extends TContentSet {
|
||||
result = TUnknownElementContent()
|
||||
}
|
||||
|
||||
/** Gets a content that may be read from when reading from this set. */
|
||||
Content getAReadContent() {
|
||||
this.isSingleton(result)
|
||||
or
|
||||
this.isAnyElement() and
|
||||
result instanceof Content::ElementContent
|
||||
or
|
||||
pragma[nomagic]
|
||||
private Content getAnElementReadContent() {
|
||||
exists(Content::KnownElementContent c | this.isKnownOrUnknownElement(c) |
|
||||
result = c or
|
||||
result = TSplatContent(c.getIndex().getInt(), _) or
|
||||
@@ -832,6 +833,19 @@ class ContentSet extends TContentSet {
|
||||
result = TUnknownElementContent()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a content that may be read from when reading from this set. */
|
||||
Content getAReadContent() {
|
||||
this.isSingleton(result)
|
||||
or
|
||||
this.isAnyElement() and
|
||||
result instanceof Content::ElementContent
|
||||
or
|
||||
this.isAny() and
|
||||
exists(result)
|
||||
or
|
||||
result = this.getAnElementReadContent()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -79,6 +79,46 @@ private module UnicodeBypassValidationConfig implements DataFlow::StateConfigSig
|
||||
|
||||
predicate isSource(DataFlow::Node source, FlowState state) {
|
||||
source instanceof RemoteFlowSource and state = PreValidationState()
|
||||
or
|
||||
(
|
||||
exists(Escaping escaping | source = escaping.getOutput())
|
||||
or
|
||||
source instanceof RegexExecution
|
||||
or
|
||||
// String Manipulation Method Calls
|
||||
// https://ruby-doc.org/core-2.7.0/String.html
|
||||
// String Manipulation Method Calls
|
||||
// https://ruby-doc.org/core-2.7.0/String.html
|
||||
exists(DataFlow::CallNode cn |
|
||||
cn.getMethodName() =
|
||||
[
|
||||
[
|
||||
"ljust", "lstrip", "succ", "next", "rjust", "capitalize", "chomp", "gsub", "chop",
|
||||
"downcase", "swapcase", "uprcase", "scrub", "slice", "squeeze", "strip", "sub",
|
||||
"tr", "tr_s", "reverse"
|
||||
] + ["", "!"], "concat", "dump", "each_line", "replace", "insert", "inspect", "lines",
|
||||
"partition", "prepend", "replace", "rpartition", "scan", "split", "undump",
|
||||
"unpack" + ["", "1"]
|
||||
] and
|
||||
source = cn
|
||||
)
|
||||
or
|
||||
exists(DataFlow::CallNode cn |
|
||||
cn.getMethodName() =
|
||||
[
|
||||
"casecmp" + ["", "?"], "center", "count", "each_char", "index", "rindex", "sum",
|
||||
["delete", "delete_prefix", "delete_suffix"] + ["", "!"],
|
||||
["start_with", "end_with" + "eql", "include"] + ["?", "!"], "match" + ["", "?"],
|
||||
] and
|
||||
source = cn.getReceiver()
|
||||
)
|
||||
or
|
||||
exists(DataFlow::CallNode cn |
|
||||
cn = API::getTopLevelMember("CGI").getAMethodCall("escapeHTML") and
|
||||
source = cn
|
||||
)
|
||||
) and
|
||||
state = PostValidationState()
|
||||
}
|
||||
|
||||
predicate isAdditionalFlowStep(
|
||||
|
||||
@@ -279,19 +279,23 @@ module Sinatra {
|
||||
filter.getApp() = route.getApp() and
|
||||
// the filter applies to all routes
|
||||
not filter.hasPattern() and
|
||||
blockPostUpdate(pred, filter.getBody()) and
|
||||
blockSelfParameterNode(succ, route.getBody().asExpr().getExpr())
|
||||
blockPostSelf(pred, filter.getBody()) and
|
||||
blockSelf(succ, route.getBody().asExpr().getExpr())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if `n` is a post-update node for the block `b`. */
|
||||
private predicate blockPostUpdate(DataFlow::PostUpdateNode n, DataFlow::BlockNode b) {
|
||||
n.getPreUpdateNode() = b
|
||||
/** Holds if `n` is a post-update node referencing `self` in the block `b`. */
|
||||
private predicate blockPostSelf(DataFlow::PostUpdateNode n, DataFlow::BlockNode b) {
|
||||
exists(SelfVariableAccessCfgNode self |
|
||||
n.getPreUpdateNode().asExpr() = self and
|
||||
self.getScope() = b.asExpr().getAstNode()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `n` is a `self` parameter belonging to block `b`. */
|
||||
private predicate blockSelfParameterNode(DataFlowPrivate::LambdaSelfReferenceNode n, Block b) {
|
||||
n.getCallable() = b
|
||||
/** Holds if `n` is a node referencing `self` in the block `b`. */
|
||||
private predicate blockSelf(DataFlow::VariableAccessNode self, Block b) {
|
||||
self.getExprNode().getBasicBlock().getScope() = b and
|
||||
self.asVariableAccessAstNode().getVariable() instanceof SelfVariable
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,13 +86,13 @@ module Routing {
|
||||
* ```
|
||||
*/
|
||||
private class TopLevelRouteBlock extends RouteBlock, TTopLevelRouteBlock {
|
||||
MethodCall call;
|
||||
MethodCall methodCall;
|
||||
// Routing blocks create scopes which define the namespace for controllers and paths,
|
||||
// though they can be overridden in various ways.
|
||||
// The namespaces can differ, so we track them separately.
|
||||
Block block;
|
||||
|
||||
TopLevelRouteBlock() { this = TTopLevelRouteBlock(_, call, block) }
|
||||
TopLevelRouteBlock() { this = TTopLevelRouteBlock(_, methodCall, block) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "TopLevelRouteBlock" }
|
||||
|
||||
@@ -102,9 +102,9 @@ module Routing {
|
||||
|
||||
override RouteBlock getParent() { none() }
|
||||
|
||||
override string toString() { result = call.toString() }
|
||||
override string toString() { result = methodCall.toString() }
|
||||
|
||||
override Location getLocation() { result = call.getLocation() }
|
||||
override Location getLocation() { result = methodCall.getLocation() }
|
||||
|
||||
override string getPathComponent() { none() }
|
||||
|
||||
@@ -122,9 +122,9 @@ module Routing {
|
||||
*/
|
||||
private class ConstraintsRouteBlock extends NestedRouteBlock, TConstraintsRouteBlock {
|
||||
private Block block;
|
||||
private MethodCall call;
|
||||
private MethodCall methodCall;
|
||||
|
||||
ConstraintsRouteBlock() { this = TConstraintsRouteBlock(parent, call, block) }
|
||||
ConstraintsRouteBlock() { this = TConstraintsRouteBlock(parent, methodCall, block) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "ConstraintsRouteBlock" }
|
||||
|
||||
@@ -134,9 +134,9 @@ module Routing {
|
||||
|
||||
override string getControllerComponent() { result = "" }
|
||||
|
||||
override string toString() { result = call.toString() }
|
||||
override string toString() { result = methodCall.toString() }
|
||||
|
||||
override Location getLocation() { result = call.getLocation() }
|
||||
override Location getLocation() { result = methodCall.getLocation() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,31 +149,56 @@ module Routing {
|
||||
* https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Scoping.html#method-i-scope
|
||||
*/
|
||||
private class ScopeRouteBlock extends NestedRouteBlock, TScopeRouteBlock {
|
||||
private MethodCall call;
|
||||
private MethodCall methodCall;
|
||||
private Block block;
|
||||
|
||||
ScopeRouteBlock() { this = TScopeRouteBlock(parent, call, block) }
|
||||
ScopeRouteBlock() { this = TScopeRouteBlock(parent, methodCall, block) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "ScopeRouteBlock" }
|
||||
|
||||
override Stmt getAStmt() { result = block.getAStmt() }
|
||||
|
||||
override string toString() { result = call.toString() }
|
||||
override string toString() { result = methodCall.toString() }
|
||||
|
||||
override Location getLocation() { result = call.getLocation() }
|
||||
override Location getLocation() { result = methodCall.getLocation() }
|
||||
|
||||
override string getPathComponent() {
|
||||
call.getKeywordArgument("path").getConstantValue().isStringlikeValue(result)
|
||||
methodCall.getKeywordArgument("path").getConstantValue().isStringlikeValue(result)
|
||||
or
|
||||
not exists(call.getKeywordArgument("path")) and
|
||||
call.getArgument(0).getConstantValue().isStringlikeValue(result)
|
||||
not exists(methodCall.getKeywordArgument("path")) and
|
||||
methodCall.getArgument(0).getConstantValue().isStringlikeValue(result)
|
||||
}
|
||||
|
||||
override string getControllerComponent() {
|
||||
call.getKeywordArgument(["controller", "module"]).getConstantValue().isStringlikeValue(result)
|
||||
methodCall
|
||||
.getKeywordArgument(["controller", "module"])
|
||||
.getConstantValue()
|
||||
.isStringlikeValue(result)
|
||||
}
|
||||
}
|
||||
|
||||
private Expr getActionFromMethodCall(MethodCall methodCall) {
|
||||
result =
|
||||
[
|
||||
// e.g. `get "/comments", to: "comments#index"
|
||||
methodCall.getKeywordArgument("to"),
|
||||
// e.g. `get "/comments" => "comments#index"
|
||||
methodCall.getArgument(0).(Pair).getValue()
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a string representation of the controller-action pair that is routed
|
||||
* to by this method call.
|
||||
*/
|
||||
private string getActionStringFromMethodCall(MethodCall methodCall) {
|
||||
getActionFromMethodCall(methodCall).getConstantValue().isStringlikeValue(result)
|
||||
or
|
||||
// TODO: use the redirect call argument to resolve the redirect target
|
||||
getActionFromMethodCall(methodCall).(MethodCall).getMethodName() = "redirect" and
|
||||
result = "<redirect>#<redirect>"
|
||||
}
|
||||
|
||||
/**
|
||||
* A route block defined by a call to `resources`.
|
||||
* ```rb
|
||||
@@ -184,10 +209,10 @@ module Routing {
|
||||
* https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Resources.html#method-i-resources
|
||||
*/
|
||||
private class ResourcesRouteBlock extends NestedRouteBlock, TResourcesRouteBlock {
|
||||
private MethodCall call;
|
||||
private MethodCall methodCall;
|
||||
private Block block;
|
||||
|
||||
ResourcesRouteBlock() { this = TResourcesRouteBlock(parent, call, block) }
|
||||
ResourcesRouteBlock() { this = TResourcesRouteBlock(parent, methodCall, block) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "ResourcesRouteBlock" }
|
||||
|
||||
@@ -196,19 +221,21 @@ module Routing {
|
||||
/**
|
||||
* Gets the `resources` call that gives rise to this route block.
|
||||
*/
|
||||
MethodCall getDefiningMethodCall() { result = call }
|
||||
MethodCall getDefiningMethodCall() { result = methodCall }
|
||||
|
||||
override string getPathComponent() {
|
||||
exists(string resource | call.getArgument(0).getConstantValue().isStringlikeValue(resource) |
|
||||
exists(string resource |
|
||||
methodCall.getArgument(0).getConstantValue().isStringlikeValue(resource)
|
||||
|
|
||||
result = resource + "/:" + singularize(resource) + "_id"
|
||||
)
|
||||
}
|
||||
|
||||
override string getControllerComponent() { result = "" }
|
||||
|
||||
override string toString() { result = call.toString() }
|
||||
override string toString() { result = methodCall.toString() }
|
||||
|
||||
override Location getLocation() { result = call.getLocation() }
|
||||
override Location getLocation() { result = methodCall.getLocation() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,10 +277,10 @@ module Routing {
|
||||
* https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Scoping.html#method-i-namespace
|
||||
*/
|
||||
private class NamespaceRouteBlock extends NestedRouteBlock, TNamespaceRouteBlock {
|
||||
private MethodCall call;
|
||||
private MethodCall methodCall;
|
||||
private Block block;
|
||||
|
||||
NamespaceRouteBlock() { this = TNamespaceRouteBlock(parent, call, block) }
|
||||
NamespaceRouteBlock() { this = TNamespaceRouteBlock(parent, methodCall, block) }
|
||||
|
||||
override Stmt getAStmt() { result = block.getAStmt() }
|
||||
|
||||
@@ -262,12 +289,12 @@ module Routing {
|
||||
override string getControllerComponent() { result = this.getNamespace() }
|
||||
|
||||
private string getNamespace() {
|
||||
call.getArgument(0).getConstantValue().isStringlikeValue(result)
|
||||
methodCall.getArgument(0).getConstantValue().isStringlikeValue(result)
|
||||
}
|
||||
|
||||
override string toString() { result = call.toString() }
|
||||
override string toString() { result = methodCall.toString() }
|
||||
|
||||
override Location getLocation() { result = call.getLocation() }
|
||||
override Location getLocation() { result = methodCall.getLocation() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -340,20 +367,20 @@ module Routing {
|
||||
*/
|
||||
string getAPrimaryQlClass() { result = "RouteImpl" }
|
||||
|
||||
MethodCall method;
|
||||
MethodCall methodCall;
|
||||
|
||||
/** Gets a string representation of this route. */
|
||||
string toString() { result = method.toString() }
|
||||
string toString() { result = methodCall.toString() }
|
||||
|
||||
/**
|
||||
* Gets the location of the method call that defines this route.
|
||||
*/
|
||||
Location getLocation() { result = method.getLocation() }
|
||||
Location getLocation() { result = methodCall.getLocation() }
|
||||
|
||||
/**
|
||||
* Gets the method call that defines this route.
|
||||
*/
|
||||
MethodCall getDefiningMethodCall() { result = method }
|
||||
MethodCall getDefiningMethodCall() { result = methodCall }
|
||||
|
||||
/**
|
||||
* Get the last component of the path. For example, in
|
||||
@@ -473,20 +500,20 @@ module Routing {
|
||||
private class ExplicitRoute extends RouteImpl, TExplicitRoute {
|
||||
RouteBlock parentBlock;
|
||||
|
||||
ExplicitRoute() { this = TExplicitRoute(parentBlock, method) }
|
||||
ExplicitRoute() { this = TExplicitRoute(parentBlock, methodCall) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "ExplicitRoute" }
|
||||
|
||||
override RouteBlock getParentBlock() { result = parentBlock }
|
||||
|
||||
override string getLastPathComponent() {
|
||||
method.getArgument(0).getConstantValue().isStringlikeValue(result)
|
||||
methodCall.getArgument(0).getConstantValue().isStringlikeValue(result)
|
||||
}
|
||||
|
||||
override string getLastControllerComponent() {
|
||||
method.getKeywordArgument("controller").getConstantValue().isStringlikeValue(result)
|
||||
methodCall.getKeywordArgument("controller").getConstantValue().isStringlikeValue(result)
|
||||
or
|
||||
not exists(method.getKeywordArgument("controller")) and
|
||||
not exists(methodCall.getKeywordArgument("controller")) and
|
||||
(
|
||||
result = extractController(this.getActionString())
|
||||
or
|
||||
@@ -507,18 +534,13 @@ module Routing {
|
||||
)
|
||||
}
|
||||
|
||||
private string getActionString() {
|
||||
method.getKeywordArgument("to").getConstantValue().isStringlikeValue(result)
|
||||
or
|
||||
method.getKeywordArgument("to").(MethodCall).getMethodName() = "redirect" and
|
||||
result = "<redirect>#<redirect>"
|
||||
}
|
||||
private string getActionString() { result = getActionStringFromMethodCall(methodCall) }
|
||||
|
||||
override string getAction() {
|
||||
// get "/photos", action: "index"
|
||||
method.getKeywordArgument("action").getConstantValue().isStringlikeValue(result)
|
||||
methodCall.getKeywordArgument("action").getConstantValue().isStringlikeValue(result)
|
||||
or
|
||||
not exists(method.getKeywordArgument("action")) and
|
||||
not exists(methodCall.getKeywordArgument("action")) and
|
||||
(
|
||||
// get "/photos", to: "photos#index"
|
||||
// get "/photos", to: redirect("some_url")
|
||||
@@ -531,11 +553,11 @@ module Routing {
|
||||
or
|
||||
// get :some_action
|
||||
not exists(this.getActionString()) and
|
||||
method.getArgument(0).getConstantValue().isStringlikeValue(result)
|
||||
methodCall.getArgument(0).getConstantValue().isStringlikeValue(result)
|
||||
)
|
||||
}
|
||||
|
||||
override string getHttpMethod() { result = method.getMethodName().toString() }
|
||||
override string getHttpMethod() { result = methodCall.getMethodName().toString() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -577,8 +599,8 @@ module Routing {
|
||||
|
||||
ResourcesRoute() {
|
||||
exists(string resource |
|
||||
this = TResourcesRoute(parent, method, action) and
|
||||
method.getArgument(0).getConstantValue().isStringlikeValue(resource) and
|
||||
this = TResourcesRoute(parent, methodCall, action) and
|
||||
methodCall.getArgument(0).getConstantValue().isStringlikeValue(resource) and
|
||||
isDefaultResourceRoute(resource, httpMethod, pathComponent, action)
|
||||
)
|
||||
}
|
||||
@@ -590,7 +612,7 @@ module Routing {
|
||||
override string getLastPathComponent() { result = pathComponent }
|
||||
|
||||
override string getLastControllerComponent() {
|
||||
method.getArgument(0).getConstantValue().isStringlikeValue(result)
|
||||
methodCall.getArgument(0).getConstantValue().isStringlikeValue(result)
|
||||
}
|
||||
|
||||
override string getAction() { result = action }
|
||||
@@ -615,8 +637,8 @@ module Routing {
|
||||
|
||||
SingularResourceRoute() {
|
||||
exists(string resource |
|
||||
this = TResourceRoute(parent, method, action) and
|
||||
method.getArgument(0).getConstantValue().isStringlikeValue(resource) and
|
||||
this = TResourceRoute(parent, methodCall, action) and
|
||||
methodCall.getArgument(0).getConstantValue().isStringlikeValue(resource) and
|
||||
isDefaultSingularResourceRoute(resource, httpMethod, pathComponent, action)
|
||||
)
|
||||
}
|
||||
@@ -628,7 +650,7 @@ module Routing {
|
||||
override string getLastPathComponent() { result = pathComponent }
|
||||
|
||||
override string getLastControllerComponent() {
|
||||
method.getArgument(0).getConstantValue().isStringlikeValue(result)
|
||||
methodCall.getArgument(0).getConstantValue().isStringlikeValue(result)
|
||||
}
|
||||
|
||||
override string getAction() { result = action }
|
||||
@@ -652,24 +674,23 @@ module Routing {
|
||||
private class MatchRoute extends RouteImpl, TMatchRoute {
|
||||
private RouteBlock parent;
|
||||
|
||||
MatchRoute() { this = TMatchRoute(parent, method) }
|
||||
MatchRoute() { this = TMatchRoute(parent, methodCall) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "MatchRoute" }
|
||||
|
||||
override RouteBlock getParentBlock() { result = parent }
|
||||
|
||||
override string getLastPathComponent() {
|
||||
[method.getArgument(0), method.getArgument(0).(Pair).getKey()]
|
||||
[methodCall.getArgument(0), methodCall.getArgument(0).(Pair).getKey()]
|
||||
.getConstantValue()
|
||||
.isStringlikeValue(result)
|
||||
}
|
||||
|
||||
override string getLastControllerComponent() {
|
||||
result = extractController(getActionStringFromMethodCall(methodCall)) or
|
||||
methodCall.getKeywordArgument("controller").getConstantValue().isStringlikeValue(result) or
|
||||
result =
|
||||
extractController(method.getKeywordArgument("to").getConstantValue().getStringlikeValue()) or
|
||||
method.getKeywordArgument("controller").getConstantValue().isStringlikeValue(result) or
|
||||
result =
|
||||
extractController(method
|
||||
extractController(methodCall
|
||||
.getArgument(0)
|
||||
.(Pair)
|
||||
.getValue()
|
||||
@@ -679,7 +700,7 @@ module Routing {
|
||||
|
||||
override string getHttpMethod() {
|
||||
exists(string via |
|
||||
method.getKeywordArgument("via").getConstantValue().isStringlikeValue(via)
|
||||
methodCall.getKeywordArgument("via").getConstantValue().isStringlikeValue(via)
|
||||
|
|
||||
via = "all" and result = anyHttpMethod()
|
||||
or
|
||||
@@ -687,7 +708,7 @@ module Routing {
|
||||
)
|
||||
or
|
||||
result =
|
||||
method
|
||||
methodCall
|
||||
.getKeywordArgument("via")
|
||||
.(ArrayLiteral)
|
||||
.getElement(_)
|
||||
@@ -696,11 +717,10 @@ module Routing {
|
||||
}
|
||||
|
||||
override string getAction() {
|
||||
result = extractAction(getActionStringFromMethodCall(methodCall)) or
|
||||
methodCall.getKeywordArgument("action").getConstantValue().isStringlikeValue(result) or
|
||||
result =
|
||||
extractAction(method.getKeywordArgument("to").getConstantValue().getStringlikeValue()) or
|
||||
method.getKeywordArgument("action").getConstantValue().isStringlikeValue(result) or
|
||||
result =
|
||||
extractAction(method
|
||||
extractAction(methodCall
|
||||
.getArgument(0)
|
||||
.(Pair)
|
||||
.getValue()
|
||||
@@ -821,58 +841,58 @@ module Routing {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the (resource, method, path, action) combination would be generated by a call to `resources :<resource>`.
|
||||
* Holds if the (resource, httpMethod, path, action) combination would be generated by a call to `resources :<resource>`.
|
||||
*/
|
||||
bindingset[resource]
|
||||
private predicate isDefaultResourceRoute(
|
||||
string resource, string method, string path, string action
|
||||
string resource, string httpMethod, string path, string action
|
||||
) {
|
||||
action = "create" and
|
||||
(method = "post" and path = "/" + resource)
|
||||
(httpMethod = "post" and path = "/" + resource)
|
||||
or
|
||||
action = "index" and
|
||||
(method = "get" and path = "/" + resource)
|
||||
(httpMethod = "get" and path = "/" + resource)
|
||||
or
|
||||
action = "new" and
|
||||
(method = "get" and path = "/" + resource + "/new")
|
||||
(httpMethod = "get" and path = "/" + resource + "/new")
|
||||
or
|
||||
action = "edit" and
|
||||
(method = "get" and path = "/" + resource + ":id/edit")
|
||||
(httpMethod = "get" and path = "/" + resource + ":id/edit")
|
||||
or
|
||||
action = "show" and
|
||||
(method = "get" and path = "/" + resource + "/:id")
|
||||
(httpMethod = "get" and path = "/" + resource + "/:id")
|
||||
or
|
||||
action = "update" and
|
||||
(method in ["put", "patch"] and path = "/" + resource + "/:id")
|
||||
(httpMethod in ["put", "patch"] and path = "/" + resource + "/:id")
|
||||
or
|
||||
action = "destroy" and
|
||||
(method = "delete" and path = "/" + resource + "/:id")
|
||||
(httpMethod = "delete" and path = "/" + resource + "/:id")
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the (resource, method, path, action) combination would be generated by a call to `resource :<resource>`.
|
||||
* Holds if the (resource, httpMethod, path, action) combination would be generated by a call to `resource :<resource>`.
|
||||
*/
|
||||
bindingset[resource]
|
||||
private predicate isDefaultSingularResourceRoute(
|
||||
string resource, string method, string path, string action
|
||||
string resource, string httpMethod, string path, string action
|
||||
) {
|
||||
action = "create" and
|
||||
(method = "post" and path = "/" + resource)
|
||||
(httpMethod = "post" and path = "/" + resource)
|
||||
or
|
||||
action = "new" and
|
||||
(method = "get" and path = "/" + resource + "/new")
|
||||
(httpMethod = "get" and path = "/" + resource + "/new")
|
||||
or
|
||||
action = "edit" and
|
||||
(method = "get" and path = "/" + resource + "/edit")
|
||||
(httpMethod = "get" and path = "/" + resource + "/edit")
|
||||
or
|
||||
action = "show" and
|
||||
(method = "get" and path = "/" + resource)
|
||||
(httpMethod = "get" and path = "/" + resource)
|
||||
or
|
||||
action = "update" and
|
||||
(method in ["put", "patch"] and path = "/" + resource)
|
||||
(httpMethod in ["put", "patch"] and path = "/" + resource)
|
||||
or
|
||||
action = "destroy" and
|
||||
(method = "delete" and path = "/" + resource)
|
||||
(httpMethod = "delete" and path = "/" + resource)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -44,6 +44,11 @@ private module Config implements DataFlow::ConfigSig {
|
||||
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
CL::isAdditionalTaintStep(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet cs) {
|
||||
cs.isAny() and
|
||||
isSink(node)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,6 +43,11 @@ private module Config implements DataFlow::ConfigSig {
|
||||
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
CS::isAdditionalTaintStep(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet cs) {
|
||||
cs.isAny() and
|
||||
isSink(node)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,6 +9,7 @@ private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.TaintTracking::TaintTracking
|
||||
private import codeql.ruby.dataflow.RemoteFlowSources
|
||||
private import SensitiveDataHeuristics::HeuristicNames
|
||||
private import SensitiveDataHeuristics
|
||||
private import codeql.ruby.CFG
|
||||
private import codeql.ruby.dataflow.SSA
|
||||
|
||||
@@ -39,6 +40,34 @@ module CleartextSources {
|
||||
re.getConstantValue().getStringlikeValue() = [".*", ".+"]
|
||||
}
|
||||
|
||||
/** Holds if `c` is a sensitive data classification that is relevant to consider for Cleartext Storage queries. */
|
||||
private predicate isRelevantClassification(SensitiveDataClassification c) {
|
||||
c =
|
||||
[
|
||||
SensitiveDataClassification::password(), SensitiveDataClassification::certificate(),
|
||||
SensitiveDataClassification::secret(), SensitiveDataClassification::private()
|
||||
]
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private string getCombinedRelevantSensitiveRegexp() {
|
||||
// Combine all the maybe-sensitive regexps into one using non-capturing groups and |.
|
||||
result =
|
||||
"(?:" +
|
||||
strictconcat(string r, SensitiveDataClassification c |
|
||||
r = maybeSensitiveRegexp(c) and isRelevantClassification(c)
|
||||
|
|
||||
r, ")|(?:"
|
||||
) + ")"
|
||||
}
|
||||
|
||||
/** Holds if the given name indicates the presence of sensitive data that is relevant to consider for Cleartext Storage queries. */
|
||||
bindingset[name]
|
||||
private predicate nameIndicatesRelevantSensitiveData(string name) {
|
||||
name.regexpMatch(getCombinedRelevantSensitiveRegexp()) and
|
||||
not name.regexpMatch(notSensitiveRegexp())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `re` may be a regular expression that can be used to sanitize
|
||||
* sensitive data with a call to `gsub`.
|
||||
@@ -92,17 +121,17 @@ module CleartextSources {
|
||||
}
|
||||
|
||||
/**
|
||||
* A call that might obfuscate a password, for example through hashing.
|
||||
* A call that might obfuscate sensitive data, for example through hashing.
|
||||
*/
|
||||
private class ObfuscatorCall extends Sanitizer, DataFlow::CallNode {
|
||||
ObfuscatorCall() { nameIsNotSensitive(this.getMethodName()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow node that does not contain a clear-text password, according to its syntactic name.
|
||||
* A data flow node that does not contain clear-text sensitive data, according to its syntactic name.
|
||||
*/
|
||||
private class NameGuidedNonCleartextPassword extends NonCleartextPassword {
|
||||
NameGuidedNonCleartextPassword() {
|
||||
private class NameGuidedNonCleartextSensitive extends NonCleartextSensitive {
|
||||
NameGuidedNonCleartextSensitive() {
|
||||
exists(string name | nameIsNotSensitive(name) |
|
||||
// accessing a non-sensitive variable
|
||||
this.asExpr().getExpr().(VariableReadAccess).getVariable().getName() = name
|
||||
@@ -129,18 +158,23 @@ module CleartextSources {
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow node that receives flow that is not a clear-text password.
|
||||
* A data flow node that receives flow that is not clear-text sensitive data.
|
||||
*/
|
||||
class NonCleartextPasswordFlow extends NonCleartextPassword {
|
||||
NonCleartextPasswordFlow() {
|
||||
any(NonCleartextPassword other).(DataFlow::LocalSourceNode).flowsTo(this)
|
||||
class NonCleartextSensitiveFlow extends NonCleartextSensitive {
|
||||
NonCleartextSensitiveFlow() {
|
||||
any(NonCleartextSensitive other).(DataFlow::LocalSourceNode).flowsTo(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow node that does not contain a clear-text password.
|
||||
* DEPRECATED: Use NonCleartextSensitiveFlow instead.
|
||||
*/
|
||||
abstract private class NonCleartextPassword extends DataFlow::Node { }
|
||||
deprecated class NonCleartextPasswordFlow = NonCleartextSensitiveFlow;
|
||||
|
||||
/**
|
||||
* A data flow node that does not contain clear-text sensitive data.
|
||||
*/
|
||||
abstract private class NonCleartextSensitive extends DataFlow::Node { }
|
||||
|
||||
// `writeNode` assigns pair with key `name` to `val`
|
||||
private predicate hashKeyWrite(DataFlow::CallNode writeNode, string name, DataFlow::Node val) {
|
||||
@@ -153,18 +187,18 @@ module CleartextSources {
|
||||
}
|
||||
|
||||
/**
|
||||
* A value written to a hash entry with a key that may contain password information.
|
||||
* A value written to a hash entry with a key that may contain sensitive information.
|
||||
*/
|
||||
private class HashKeyWritePasswordSource extends Source {
|
||||
private class HashKeyWriteSensitiveSource extends Source {
|
||||
private string name;
|
||||
private DataFlow::ExprNode recv;
|
||||
|
||||
HashKeyWritePasswordSource() {
|
||||
HashKeyWriteSensitiveSource() {
|
||||
exists(DataFlow::CallNode writeNode |
|
||||
name.regexpMatch(maybePassword()) and
|
||||
nameIndicatesRelevantSensitiveData(name) and
|
||||
not nameIsNotSensitive(name) and
|
||||
// avoid safe values assigned to presumably unsafe names
|
||||
not this instanceof NonCleartextPassword and
|
||||
not this instanceof NonCleartextSensitive and
|
||||
// hash[name] = val
|
||||
hashKeyWrite(writeNode, name, this) and
|
||||
recv = writeNode.getReceiver()
|
||||
@@ -177,7 +211,7 @@ module CleartextSources {
|
||||
string getName() { result = name }
|
||||
|
||||
/**
|
||||
* Gets the name of the hash variable that this password source is assigned
|
||||
* Gets the name of the hash variable that this sensitive source is assigned
|
||||
* to, if applicable.
|
||||
*/
|
||||
LocalVariable getVariable() {
|
||||
@@ -186,17 +220,17 @@ module CleartextSources {
|
||||
}
|
||||
|
||||
/**
|
||||
* An entry into a hash literal that may contain a password
|
||||
* An entry into a hash literal that may contain sensitive data
|
||||
*/
|
||||
private class HashLiteralPasswordSource extends Source {
|
||||
private class HashLiteralSensitiveSource extends Source {
|
||||
private string name;
|
||||
|
||||
HashLiteralPasswordSource() {
|
||||
HashLiteralSensitiveSource() {
|
||||
exists(CfgNodes::ExprNodes::HashLiteralCfgNode lit |
|
||||
name.regexpMatch(maybePassword()) and
|
||||
nameIndicatesRelevantSensitiveData(name) and
|
||||
not nameIsNotSensitive(name) and
|
||||
// avoid safe values assigned to presumably unsafe names
|
||||
not this instanceof NonCleartextPassword and
|
||||
not this instanceof NonCleartextSensitive and
|
||||
// hash = { name: val }
|
||||
exists(CfgNodes::ExprNodes::PairCfgNode p | p = lit.getAKeyValuePair() |
|
||||
p.getKey().getConstantValue().getStringlikeValue() = name and
|
||||
@@ -208,14 +242,14 @@ module CleartextSources {
|
||||
override string describe() { result = "a write to " + name }
|
||||
}
|
||||
|
||||
/** An assignment that may assign a password to a variable */
|
||||
private class AssignPasswordVariableSource extends Source {
|
||||
/** An assignment that may assign sensitive data to a variable */
|
||||
private class AssignSensitiveVariableSource extends Source {
|
||||
string name;
|
||||
|
||||
AssignPasswordVariableSource() {
|
||||
AssignSensitiveVariableSource() {
|
||||
// avoid safe values assigned to presumably unsafe names
|
||||
not this instanceof NonCleartextPassword and
|
||||
name.regexpMatch(maybePassword()) and
|
||||
not this instanceof NonCleartextSensitive and
|
||||
nameIndicatesRelevantSensitiveData(name) and
|
||||
not nameIsNotSensitive(name) and
|
||||
exists(Assignment a |
|
||||
this.asExpr().getExpr() = a.getRightOperand() and
|
||||
@@ -226,14 +260,14 @@ module CleartextSources {
|
||||
override string describe() { result = "an assignment to " + name }
|
||||
}
|
||||
|
||||
/** A parameter that may contain a password. */
|
||||
private class ParameterPasswordSource extends Source {
|
||||
/** A parameter that may contain sensitive data. */
|
||||
private class ParameterSensitiveSource extends Source {
|
||||
private string name;
|
||||
|
||||
ParameterPasswordSource() {
|
||||
name.regexpMatch(maybePassword()) and
|
||||
ParameterSensitiveSource() {
|
||||
nameIndicatesRelevantSensitiveData(name) and
|
||||
not nameIsNotSensitive(name) and
|
||||
not this instanceof NonCleartextPassword and
|
||||
not this instanceof NonCleartextSensitive and
|
||||
exists(Parameter p, LocalVariable v |
|
||||
v = p.getAVariable() and
|
||||
v.getName() = name and
|
||||
@@ -260,10 +294,10 @@ module CleartextSources {
|
||||
deprecated predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
exists(string name, ElementReference ref, LocalVariable hashVar |
|
||||
// from `hsh[password] = "changeme"` to a `hsh[password]` read
|
||||
nodeFrom.(HashKeyWritePasswordSource).getName() = name and
|
||||
nodeFrom.(HashKeyWriteSensitiveSource).getName() = name and
|
||||
nodeTo.asExpr().getExpr() = ref and
|
||||
ref.getArgument(0).getConstantValue().getStringlikeValue() = name and
|
||||
nodeFrom.(HashKeyWritePasswordSource).getVariable() = hashVar and
|
||||
nodeFrom.(HashKeyWriteSensitiveSource).getVariable() = hashVar and
|
||||
ref.getReceiver().(VariableReadAccess).getVariable() = hashVar and
|
||||
nodeFrom.asExpr().getASuccessor*() = nodeTo.asExpr()
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user