Merge remote-tracking branch 'upstream/main' into 'rc/3.14'

This commit is contained in:
Arthur Baars
2024-06-28 19:50:35 +02:00
772 changed files with 16846 additions and 17035 deletions

View 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.

View File

@@ -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

View File

@@ -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()
}
}
/**

View File

@@ -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(

View File

@@ -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
}
}

View File

@@ -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)
}
/**

View File

@@ -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)
}
}
/**

View File

@@ -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)
}
}
/**

View File

@@ -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()
)