mirror of
https://github.com/github/codeql.git
synced 2026-04-24 16:25:15 +02:00
Merge pull request #18467 from github/js/shared-dataflow-branch
JS: Migrate to shared data flow library (targeting main!) 🚀
This commit is contained in:
@@ -15,13 +15,13 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.regexp.PolynomialReDoSQuery
|
||||
import DataFlow::PathGraph
|
||||
import PolynomialReDoSFlow::PathGraph
|
||||
|
||||
from
|
||||
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, Sink sinkNode,
|
||||
PolynomialReDoSFlow::PathNode source, PolynomialReDoSFlow::PathNode sink, Sink sinkNode,
|
||||
PolynomialBackTrackingTerm regexp
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
PolynomialReDoSFlow::flowPath(source, sink) and
|
||||
sinkNode = sink.getNode() and
|
||||
regexp = sinkNode.getRegExp() and
|
||||
not (
|
||||
|
||||
@@ -11,10 +11,12 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.ExternalAPIUsedWithUntrustedDataQuery
|
||||
import DataFlow::PathGraph
|
||||
import ExternalAPIUsedWithUntrustedDataFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from
|
||||
ExternalAPIUsedWithUntrustedDataFlow::PathNode source,
|
||||
ExternalAPIUsedWithUntrustedDataFlow::PathNode sink
|
||||
where ExternalAPIUsedWithUntrustedDataFlow::flowPath(source, sink)
|
||||
select sink, source, sink,
|
||||
"Call to " + sink.getNode().(Sink).getApiName() + " with untrusted data from $@.", source,
|
||||
source.toString()
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.TaintedPathQuery
|
||||
import DataFlow::PathGraph
|
||||
import DataFlow::DeduplicatePathGraph<TaintedPathFlow::PathNode, TaintedPathFlow::PathGraph>
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from PathNode source, PathNode sink
|
||||
where TaintedPathFlow::flowPath(source.getAnOriginalPathNode(), sink.getAnOriginalPathNode())
|
||||
select sink.getNode(), source, sink, "This path depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.ZipSlipQuery
|
||||
import DataFlow::PathGraph
|
||||
import DataFlow::DeduplicatePathGraph<ZipSlipFlow::PathNode, ZipSlipFlow::PathGraph>
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from PathNode source, PathNode sink
|
||||
where ZipSlipFlow::flowPath(source.getAnOriginalPathNode(), sink.getAnOriginalPathNode())
|
||||
select source.getNode(), source, sink,
|
||||
"Unsanitized archive entry, which may contain '..', is used in a $@.", sink.getNode(),
|
||||
"file system operation"
|
||||
|
||||
@@ -12,10 +12,11 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow::PathGraph
|
||||
import semmle.javascript.security.dataflow.TemplateObjectInjectionQuery
|
||||
import DataFlow::DeduplicatePathGraph<TemplateObjectInjectionFlow::PathNode, TemplateObjectInjectionFlow::PathGraph>
|
||||
|
||||
from DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from PathNode source, PathNode sink
|
||||
where
|
||||
TemplateObjectInjectionFlow::flowPath(source.getAnOriginalPathNode(), sink.getAnOriginalPathNode())
|
||||
select sink.getNode(), source, sink, "Template object depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -15,16 +15,16 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.CommandInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
import CommandInjectionFlow::PathGraph
|
||||
|
||||
from
|
||||
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node highlight,
|
||||
Source sourceNode
|
||||
CommandInjectionFlow::PathNode source, CommandInjectionFlow::PathNode sink,
|
||||
DataFlow::Node highlight, Source sourceNode
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
CommandInjectionFlow::flowPath(source, sink) and
|
||||
(
|
||||
if cfg.isSinkWithHighlight(sink.getNode(), _)
|
||||
then cfg.isSinkWithHighlight(sink.getNode(), highlight)
|
||||
if isSinkWithHighlight(sink.getNode(), _)
|
||||
then isSinkWithHighlight(sink.getNode(), highlight)
|
||||
else highlight = sink.getNode()
|
||||
) and
|
||||
sourceNode = source.getNode()
|
||||
|
||||
@@ -15,14 +15,16 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow::PathGraph
|
||||
import semmle.javascript.security.dataflow.IndirectCommandInjectionQuery
|
||||
import IndirectCommandInjectionFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node highlight
|
||||
from
|
||||
IndirectCommandInjectionFlow::PathNode source, IndirectCommandInjectionFlow::PathNode sink,
|
||||
DataFlow::Node highlight
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
if cfg.isSinkWithHighlight(sink.getNode(), _)
|
||||
then cfg.isSinkWithHighlight(sink.getNode(), highlight)
|
||||
IndirectCommandInjectionFlow::flowPath(source, sink) and
|
||||
if IndirectCommandInjectionConfig::isSinkWithHighlight(sink.getNode(), _)
|
||||
then IndirectCommandInjectionConfig::isSinkWithHighlight(sink.getNode(), highlight)
|
||||
else highlight = sink.getNode()
|
||||
select highlight, source, sink, "This command depends on an unsanitized $@.", source.getNode(),
|
||||
source.getNode().(Source).describe()
|
||||
|
||||
@@ -14,11 +14,14 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow::PathGraph
|
||||
import semmle.javascript.security.dataflow.SecondOrderCommandInjectionQuery
|
||||
import DataFlow::DeduplicatePathGraph<SecondOrderCommandInjectionFlow::PathNode, SecondOrderCommandInjectionFlow::PathGraph>
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, Sink sinkNode
|
||||
where cfg.hasFlowPath(source, sink) and sinkNode = sink.getNode()
|
||||
from PathNode source, PathNode sink, Sink sinkNode
|
||||
where
|
||||
SecondOrderCommandInjectionFlow::flowPath(source.getAnOriginalPathNode(),
|
||||
sink.getAnOriginalPathNode()) and
|
||||
sinkNode = sink.getNode()
|
||||
select sink.getNode(), source, sink,
|
||||
"Command line argument that depends on $@ can execute an arbitrary command if " +
|
||||
sinkNode.getVulnerableArgumentExample() + " is used with " + sinkNode.getCommand() + ".",
|
||||
|
||||
@@ -14,17 +14,18 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow::PathGraph
|
||||
import semmle.javascript.security.dataflow.ShellCommandInjectionFromEnvironmentQuery
|
||||
import ShellCommandInjectionFromEnvironmentFlow::PathGraph
|
||||
|
||||
from
|
||||
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node highlight,
|
||||
ShellCommandInjectionFromEnvironmentFlow::PathNode source,
|
||||
ShellCommandInjectionFromEnvironmentFlow::PathNode sink, DataFlow::Node highlight,
|
||||
Source sourceNode
|
||||
where
|
||||
sourceNode = source.getNode() and
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
if cfg.isSinkWithHighlight(sink.getNode(), _)
|
||||
then cfg.isSinkWithHighlight(sink.getNode(), highlight)
|
||||
ShellCommandInjectionFromEnvironmentFlow::flowPath(source, sink) and
|
||||
if ShellCommandInjectionFromEnvironmentConfig::isSinkWithHighlight(sink.getNode(), _)
|
||||
then ShellCommandInjectionFromEnvironmentConfig::isSinkWithHighlight(sink.getNode(), highlight)
|
||||
else highlight = sink.getNode()
|
||||
select highlight, source, sink, "This shell command depends on an uncontrolled $@.", sourceNode,
|
||||
sourceNode.getSourceType()
|
||||
|
||||
@@ -15,10 +15,12 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.UnsafeShellCommandConstructionQuery
|
||||
import DataFlow::PathGraph
|
||||
import UnsafeShellCommandConstructionFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, Sink sinkNode
|
||||
where cfg.hasFlowPath(source, sink) and sinkNode = sink.getNode()
|
||||
from
|
||||
UnsafeShellCommandConstructionFlow::PathNode source,
|
||||
UnsafeShellCommandConstructionFlow::PathNode sink, Sink sinkNode
|
||||
where UnsafeShellCommandConstructionFlow::flowPath(source, sink) and sinkNode = sink.getNode()
|
||||
select sinkNode.getAlertLocation(), source, sink,
|
||||
"This " + sinkNode.getSinkType() + " which depends on $@ is later used in a $@.",
|
||||
source.getNode(), "library input", sinkNode.getCommandExecution(), "shell command"
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.ExceptionXssQuery
|
||||
import DataFlow::PathGraph
|
||||
import DataFlow::DeduplicatePathGraph<ExceptionXssFlow::PathNode, ExceptionXssFlow::PathGraph>
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from PathNode source, PathNode sink
|
||||
where ExceptionXssFlow::flowPath(source.getAnOriginalPathNode(), sink.getAnOriginalPathNode())
|
||||
select sink.getNode(), source, sink,
|
||||
"$@ is reinterpreted as HTML without escaping meta-characters.", source.getNode(),
|
||||
source.getNode().(Source).getDescription()
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.ReflectedXssQuery
|
||||
import DataFlow::PathGraph
|
||||
import ReflectedXssFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from ReflectedXssFlow::PathNode source, ReflectedXssFlow::PathNode sink
|
||||
where ReflectedXssFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Cross-site scripting vulnerability due to a $@.",
|
||||
source.getNode(), "user-provided value"
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.StoredXssQuery
|
||||
import DataFlow::PathGraph
|
||||
import StoredXssFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from StoredXssFlow::PathNode source, StoredXssFlow::PathNode sink
|
||||
where StoredXssFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Stored cross-site scripting vulnerability due to $@.",
|
||||
source.getNode(), "stored value"
|
||||
|
||||
@@ -13,11 +13,13 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow::PathGraph
|
||||
import semmle.javascript.security.dataflow.UnsafeHtmlConstructionQuery
|
||||
import DataFlow::DeduplicatePathGraph<UnsafeHtmlConstructionFlow::PathNode, UnsafeHtmlConstructionFlow::PathGraph>
|
||||
|
||||
from DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, Sink sinkNode
|
||||
where cfg.hasFlowPath(source, sink) and sink.getNode() = sinkNode
|
||||
from PathNode source, PathNode sink, Sink sinkNode
|
||||
where
|
||||
UnsafeHtmlConstructionFlow::flowPath(source.getAnOriginalPathNode(), sink.getAnOriginalPathNode()) and
|
||||
sink.getNode() = sinkNode
|
||||
select sinkNode, source, sink,
|
||||
"This " + sinkNode.describe() + " which depends on $@ might later allow $@.", source.getNode(),
|
||||
"library input", sinkNode.getSink(), sinkNode.getVulnerabilityKind().toLowerCase()
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.UnsafeJQueryPluginQuery
|
||||
import DataFlow::PathGraph
|
||||
import UnsafeJQueryPluginFlow::PathGraph
|
||||
|
||||
from
|
||||
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink,
|
||||
UnsafeJQueryPluginFlow::PathNode source, UnsafeJQueryPluginFlow::PathNode sink,
|
||||
JQuery::JQueryPluginMethod plugin
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
UnsafeJQueryPluginFlow::flowPath(source, sink) and
|
||||
source.getNode().(Source).getPlugin() = plugin
|
||||
select sink.getNode(), source, sink, "Potential XSS vulnerability in the $@.", plugin,
|
||||
"'$.fn." + plugin.getPluginName() + "' plugin"
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.DomBasedXssQuery
|
||||
import DataFlow::PathGraph
|
||||
import DataFlow::DeduplicatePathGraph<DomBasedXssFlow::PathNode, DomBasedXssFlow::PathGraph>
|
||||
|
||||
from DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from PathNode source, PathNode sink
|
||||
where DomBasedXssFlow::flowPath(source.getAnOriginalPathNode(), sink.getAnOriginalPathNode())
|
||||
select sink.getNode(), source, sink,
|
||||
sink.getNode().(Sink).getVulnerabilityKind() + " vulnerability due to $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -14,9 +14,11 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.XssThroughDomQuery
|
||||
import DataFlow::PathGraph
|
||||
import XssThroughDomFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from XssThroughDomFlow::PathNode source, XssThroughDomFlow::PathNode sink
|
||||
where
|
||||
XssThroughDomFlow::flowPath(source, sink) and
|
||||
not isIgnoredSourceSinkPair(source.getNode(), sink.getNode())
|
||||
select sink.getNode(), source, sink,
|
||||
"$@ is reinterpreted as HTML without escaping meta-characters.", source.getNode(), "DOM text"
|
||||
|
||||
@@ -14,17 +14,23 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.SqlInjectionQuery as SqlInjection
|
||||
import semmle.javascript.security.dataflow.NosqlInjectionQuery as NosqlInjection
|
||||
import DataFlow::PathGraph
|
||||
import semmle.javascript.security.dataflow.SqlInjectionQuery as Sql
|
||||
import semmle.javascript.security.dataflow.NosqlInjectionQuery as Nosql
|
||||
|
||||
from DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, string type
|
||||
module Merged =
|
||||
DataFlow::MergePathGraph<Sql::SqlInjectionFlow::PathNode, Nosql::NosqlInjectionFlow::PathNode,
|
||||
Sql::SqlInjectionFlow::PathGraph, Nosql::NosqlInjectionFlow::PathGraph>;
|
||||
|
||||
import DataFlow::DeduplicatePathGraph<Merged::PathNode, Merged::PathGraph>
|
||||
|
||||
from PathNode source, PathNode sink, string type
|
||||
where
|
||||
(
|
||||
cfg instanceof SqlInjection::Configuration and type = "string"
|
||||
or
|
||||
cfg instanceof NosqlInjection::Configuration and type = "object"
|
||||
) and
|
||||
cfg.hasFlowPath(source, sink)
|
||||
Sql::SqlInjectionFlow::flowPath(source.getAnOriginalPathNode().asPathNode1(),
|
||||
sink.getAnOriginalPathNode().asPathNode1()) and
|
||||
type = "string"
|
||||
or
|
||||
Nosql::NosqlInjectionFlow::flowPath(source.getAnOriginalPathNode().asPathNode2(),
|
||||
sink.getAnOriginalPathNode().asPathNode2()) and
|
||||
type = "object"
|
||||
select sink.getNode(), source, sink, "This query " + type + " depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.CodeInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
import CodeInjectionFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from CodeInjectionFlow::PathNode source, CodeInjectionFlow::PathNode sink
|
||||
where CodeInjectionFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, sink.getNode().(Sink).getMessagePrefix() + " depends on a $@.",
|
||||
source.getNode(), "user-provided value"
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.ImproperCodeSanitizationQuery
|
||||
import DataFlow::PathGraph
|
||||
private import semmle.javascript.heuristics.HeuristicSinks
|
||||
private import semmle.javascript.security.dataflow.CodeInjectionCustomizations
|
||||
import ImproperCodeSanitizationFlow::PathGraph
|
||||
|
||||
/**
|
||||
* Gets a type-tracked instance of `RemoteFlowSource` using type-tracker `t`.
|
||||
@@ -60,9 +60,9 @@ private DataFlow::Node endsInCodeInjectionSink() {
|
||||
result = endsInCodeInjectionSink(DataFlow::TypeBackTracker::end())
|
||||
}
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
from ImproperCodeSanitizationFlow::PathNode source, ImproperCodeSanitizationFlow::PathNode sink
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
ImproperCodeSanitizationFlow::flowPath(source, sink) and
|
||||
// Basic detection of duplicate results with `js/code-injection`.
|
||||
not (
|
||||
sink.getNode().(StringOps::ConcatenationLeaf).getRoot() = endsInCodeInjectionSink() and
|
||||
|
||||
@@ -14,11 +14,13 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow::PathGraph
|
||||
import semmle.javascript.security.dataflow.UnsafeCodeConstruction::UnsafeCodeConstruction
|
||||
import UnsafeCodeConstructionFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, Sink sinkNode
|
||||
where cfg.hasFlowPath(source, sink) and sinkNode = sink.getNode()
|
||||
from
|
||||
UnsafeCodeConstructionFlow::PathNode source, UnsafeCodeConstructionFlow::PathNode sink,
|
||||
Sink sinkNode
|
||||
where UnsafeCodeConstructionFlow::flowPath(source, sink) and sinkNode = sink.getNode()
|
||||
select sink.getNode(), source, sink,
|
||||
"This " + sinkNode.getSinkType() + " which depends on $@ is later $@.", source.getNode(),
|
||||
"library input", sinkNode.getCodeSink(), "interpreted as code"
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.UnsafeDynamicMethodAccessQuery
|
||||
import DataFlow::PathGraph
|
||||
import UnsafeDynamicMethodAccessFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from UnsafeDynamicMethodAccessFlow::PathNode source, UnsafeDynamicMethodAccessFlow::PathNode sink
|
||||
where UnsafeDynamicMethodAccessFlow::flowPath(source, sink)
|
||||
select sink, source, sink,
|
||||
"This method is invoked using a $@, which may allow remote code execution.", source.getNode(),
|
||||
"user-controlled value"
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow::PathGraph
|
||||
import semmle.javascript.security.dataflow.IncompleteHtmlAttributeSanitizationQuery
|
||||
import semmle.javascript.security.IncompleteBlacklistSanitizer
|
||||
import DataFlow::DeduplicatePathGraph<IncompleteHtmlAttributeSanitizationFlow::PathNode, IncompleteHtmlAttributeSanitizationFlow::PathGraph>
|
||||
|
||||
/**
|
||||
* Gets a pretty string of the dangerous characters for `sink`.
|
||||
@@ -31,8 +31,10 @@ string prettyPrintDangerousCharaters(Sink sink) {
|
||||
).regexpReplaceAll(",(?=[^,]+$)", " or")
|
||||
}
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from PathNode source, PathNode sink
|
||||
where
|
||||
IncompleteHtmlAttributeSanitizationFlow::flowPath(source.getAnOriginalPathNode(),
|
||||
sink.getAnOriginalPathNode())
|
||||
select sink.getNode(), source, sink,
|
||||
// this message is slightly sub-optimal as we do not have an easy way
|
||||
// to get the flow labels that reach the sink, so the message includes
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow::PathGraph
|
||||
import semmle.javascript.security.dataflow.LogInjectionQuery
|
||||
import LogInjectionFlow::PathGraph
|
||||
|
||||
from LogInjectionConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from LogInjectionFlow::PathNode source, LogInjectionFlow::PathNode sink
|
||||
where LogInjectionFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Log entry depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.TaintedFormatStringQuery
|
||||
import DataFlow::PathGraph
|
||||
import TaintedFormatStringFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from TaintedFormatStringFlow::PathNode source, TaintedFormatStringFlow::PathNode sink
|
||||
where TaintedFormatStringFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Format string depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.FileAccessToHttpQuery
|
||||
import DataFlow::PathGraph
|
||||
import FileAccessToHttpFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from FileAccessToHttpFlow::PathNode source, FileAccessToHttpFlow::PathNode sink
|
||||
where FileAccessToHttpFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Outbound network request depends on $@.", source.getNode(),
|
||||
"file data"
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.PostMessageStarQuery
|
||||
import DataFlow::PathGraph
|
||||
import PostMessageStarFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from PostMessageStarFlow::PathNode source, PostMessageStarFlow::PathNode sink
|
||||
where PostMessageStarFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "$@ is sent to another window without origin restriction.",
|
||||
source.getNode(), "Sensitive data"
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.StackTraceExposureQuery
|
||||
import DataFlow::PathGraph
|
||||
import StackTraceExposureFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from StackTraceExposureFlow::PathNode source, StackTraceExposureFlow::PathNode sink
|
||||
where StackTraceExposureFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This information exposed to the user depends on $@.",
|
||||
source.getNode(), "stack trace information"
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.BuildArtifactLeakQuery
|
||||
import DataFlow::PathGraph
|
||||
import BuildArtifactLeakFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from BuildArtifactLeakFlow::PathNode source, BuildArtifactLeakFlow::PathNode sink
|
||||
where BuildArtifactLeakFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This creates a build artifact that depends on $@.",
|
||||
source.getNode(),
|
||||
"sensitive data returned by" + source.getNode().(CleartextLogging::Source).describe()
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.CleartextLoggingQuery
|
||||
import DataFlow::PathGraph
|
||||
import CleartextLoggingFlow::PathGraph
|
||||
|
||||
/**
|
||||
* Holds if `tl` is used in a browser environment.
|
||||
@@ -33,9 +33,9 @@ predicate inBrowserEnvironment(TopLevel tl) {
|
||||
)
|
||||
}
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
from CleartextLoggingFlow::PathNode source, CleartextLoggingFlow::PathNode sink
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
CleartextLoggingFlow::flowPath(source, sink) and
|
||||
// ignore logging to the browser console (even though it is not a good practice)
|
||||
not inBrowserEnvironment(sink.getNode().asExpr().getTopLevel())
|
||||
select sink.getNode(), source, sink, "This logs sensitive data returned by $@ as clear text.",
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.CleartextStorageQuery
|
||||
import DataFlow::PathGraph
|
||||
import ClearTextStorageFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from ClearTextStorageFlow::PathNode source, ClearTextStorageFlow::PathNode sink
|
||||
where ClearTextStorageFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This stores sensitive data returned by $@ as clear text.",
|
||||
source.getNode(), source.getNode().(Source).describe()
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.BrokenCryptoAlgorithmQuery
|
||||
import semmle.javascript.security.SensitiveActions
|
||||
import DataFlow::PathGraph
|
||||
import BrokenCryptoAlgorithmFlow::PathGraph
|
||||
|
||||
from
|
||||
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, Source sourceNode,
|
||||
Sink sinkNode
|
||||
BrokenCryptoAlgorithmFlow::PathNode source, BrokenCryptoAlgorithmFlow::PathNode sink,
|
||||
Source sourceNode, Sink sinkNode
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
BrokenCryptoAlgorithmFlow::flowPath(source, sink) and
|
||||
sourceNode = source.getNode() and
|
||||
sinkNode = sink.getNode() and
|
||||
not sourceNode instanceof CleartextPasswordExpr // flagged by js/insufficient-password-hash
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.InsecureRandomnessQuery
|
||||
import DataFlow::PathGraph
|
||||
import InsecureRandomnessFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from InsecureRandomnessFlow::PathNode source, InsecureRandomnessFlow::PathNode sink
|
||||
where InsecureRandomnessFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"This uses a cryptographically insecure random number generated at $@ in a security context.",
|
||||
source.getNode(), source.getNode().toString()
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.CorsMisconfigurationForCredentialsQuery
|
||||
import DataFlow::PathGraph
|
||||
import CorsMisconfigurationFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from CorsMisconfigurationFlow::PathNode source, CorsMisconfigurationFlow::PathNode sink
|
||||
where CorsMisconfigurationFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "$@ leak vulnerability due to a $@.",
|
||||
sink.getNode().(Sink).getCredentialsHeader(), "Credential", source.getNode(),
|
||||
"misconfigured CORS header value"
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow::PathGraph
|
||||
import semmle.javascript.security.dataflow.InsecureTemporaryFileQuery
|
||||
import InsecureTemporaryFileFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from InsecureTemporaryFileFlow::PathNode source, InsecureTemporaryFileFlow::PathNode sink
|
||||
where InsecureTemporaryFileFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Insecure creation of file in $@.", source.getNode(),
|
||||
"the os temp dir"
|
||||
|
||||
@@ -11,14 +11,13 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow::PathGraph
|
||||
import semmle.javascript.security.dataflow.DeepObjectResourceExhaustionQuery
|
||||
import DataFlow::DeduplicatePathGraph<DeepObjectResourceExhaustionFlow::PathNode, DeepObjectResourceExhaustionFlow::PathGraph>
|
||||
|
||||
from
|
||||
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node link,
|
||||
string reason
|
||||
from PathNode source, PathNode sink, DataFlow::Node link, string reason
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
DeepObjectResourceExhaustionFlow::flowPath(source.getAnOriginalPathNode(),
|
||||
sink.getAnOriginalPathNode()) and
|
||||
sink.getNode().(Sink).hasReason(link, reason)
|
||||
select sink, source, sink, "Denial of service caused by processing $@ with $@.", source.getNode(),
|
||||
"user input", link, reason
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.RemotePropertyInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
import RemotePropertyInjectionFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from RemotePropertyInjectionFlow::PathNode source, RemotePropertyInjectionFlow::PathNode sink
|
||||
where RemotePropertyInjectionFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, sink.getNode().(Sink).getMessage() + " depends on a $@.",
|
||||
source.getNode(), "user-provided value"
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.UnsafeDeserializationQuery
|
||||
import DataFlow::PathGraph
|
||||
import UnsafeDeserializationFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from UnsafeDeserializationFlow::PathNode source, UnsafeDeserializationFlow::PathNode sink
|
||||
where UnsafeDeserializationFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Unsafe deserialization depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -14,10 +14,12 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.HardcodedDataInterpretedAsCodeQuery
|
||||
import DataFlow::PathGraph
|
||||
import DataFlow::DeduplicatePathGraph<HardcodedDataInterpretedAsCodeFlow::PathNode, HardcodedDataInterpretedAsCodeFlow::PathGraph>
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from PathNode source, PathNode sink
|
||||
where
|
||||
HardcodedDataInterpretedAsCodeFlow::flowPath(source.getAnOriginalPathNode(),
|
||||
sink.getAnOriginalPathNode())
|
||||
select sink.getNode(), source, sink,
|
||||
"$@ is interpreted as " + sink.getNode().(Sink).getKind() + ".", source.getNode(),
|
||||
"Hard-coded data"
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.ClientSideUrlRedirectQuery
|
||||
import DataFlow::PathGraph
|
||||
import DataFlow::DeduplicatePathGraph<ClientSideUrlRedirectFlow::PathNode, ClientSideUrlRedirectFlow::PathGraph>
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from PathNode source, PathNode sink
|
||||
where
|
||||
ClientSideUrlRedirectFlow::flowPath(source.getAnOriginalPathNode(), sink.getAnOriginalPathNode())
|
||||
select sink.getNode(), source, sink, "Untrusted URL redirection depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.ServerSideUrlRedirectQuery
|
||||
import DataFlow::PathGraph
|
||||
import ServerSideUrlRedirectFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from ServerSideUrlRedirectFlow::PathNode source, ServerSideUrlRedirectFlow::PathNode sink
|
||||
where ServerSideUrlRedirectFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Untrusted URL redirection depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.XxeQuery
|
||||
import DataFlow::PathGraph
|
||||
import XxeFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from XxeFlow::PathNode source, XxeFlow::PathNode sink
|
||||
where XxeFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"XML parsing depends on a $@ without guarding against external entity expansion.",
|
||||
source.getNode(), "user-provided value"
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.HostHeaderPoisoningInEmailGenerationQuery
|
||||
import DataFlow::PathGraph
|
||||
import HostHeaderPoisoningFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from HostHeaderPoisoningFlow::PathNode source, HostHeaderPoisoningFlow::PathNode sink
|
||||
where HostHeaderPoisoningFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Links in this email can be hijacked by poisoning the $@.",
|
||||
source.getNode(), "HTTP host header"
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.XpathInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
import XpathInjectionFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from XpathInjectionFlow::PathNode source, XpathInjectionFlow::PathNode sink
|
||||
where XpathInjectionFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "XPath expression depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.RegExpInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
import RegExpInjectionFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from RegExpInjectionFlow::PathNode source, RegExpInjectionFlow::PathNode sink
|
||||
where RegExpInjectionFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This regular expression is constructed from a $@.",
|
||||
source.getNode(), source.getNode().(Source).describe()
|
||||
|
||||
@@ -13,10 +13,12 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.UnvalidatedDynamicMethodCallQuery
|
||||
import DataFlow::PathGraph
|
||||
import DataFlow::DeduplicatePathGraph<UnvalidatedDynamicMethodCallFlow::PathNode, UnvalidatedDynamicMethodCallFlow::PathGraph>
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from PathNode source, PathNode sink
|
||||
where
|
||||
UnvalidatedDynamicMethodCallFlow::flowPath(source.getAnOriginalPathNode(),
|
||||
sink.getAnOriginalPathNode())
|
||||
select sink.getNode(), source, sink,
|
||||
"Invocation of method with $@ name may dispatch to unexpected target and cause an exception.",
|
||||
source.getNode(), "user-controlled"
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow::PathGraph
|
||||
import semmle.javascript.security.dataflow.ResourceExhaustionQuery
|
||||
import ResourceExhaustionFlow::PathGraph
|
||||
|
||||
from Configuration dataflow, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where dataflow.hasFlowPath(source, sink)
|
||||
from ResourceExhaustionFlow::PathNode source, ResourceExhaustionFlow::PathNode sink
|
||||
where ResourceExhaustionFlow::flowPath(source, sink)
|
||||
select sink, source, sink, sink.getNode().(Sink).getProblemDescription() + " from a $@.", source,
|
||||
"user-provided value"
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.XmlBombQuery
|
||||
import DataFlow::PathGraph
|
||||
import XmlBombFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from XmlBombFlow::PathNode source, XmlBombFlow::PathNode sink
|
||||
where XmlBombFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"XML parsing depends on a $@ without guarding against uncontrolled entity expansion.",
|
||||
source.getNode(), "user-provided value"
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.HardcodedCredentialsQuery
|
||||
import DataFlow::PathGraph
|
||||
import HardcodedCredentials::PathGraph
|
||||
|
||||
bindingset[s]
|
||||
predicate looksLikeATemplate(string s) { s.regexpMatch(".*((\\{\\{.*\\}\\})|(<.*>)|(\\(.*\\))).*") }
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, string value
|
||||
from HardcodedCredentials::PathNode source, HardcodedCredentials::PathNode sink, string value
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
HardcodedCredentials::flowPath(source, sink) and
|
||||
// use source value in message if it's available
|
||||
if source.getNode().asExpr() instanceof ConstantString
|
||||
then
|
||||
|
||||
@@ -13,11 +13,13 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.ConditionalBypassQuery
|
||||
import DataFlow::PathGraph
|
||||
import ConditionalBypassFlow::PathGraph
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, SensitiveAction action
|
||||
from
|
||||
ConditionalBypassFlow::PathNode source, ConditionalBypassFlow::PathNode sink,
|
||||
SensitiveAction action
|
||||
where
|
||||
isTaintedGuardForSensitiveAction(sink, source, action) and
|
||||
not isEarlyAbortGuard(sink, action)
|
||||
isTaintedGuardNodeForSensitiveAction(sink, source, action) and
|
||||
not isEarlyAbortGuardNode(sink, action)
|
||||
select sink.getNode(), source, sink, "This condition guards a sensitive $@, but a $@ controls it.",
|
||||
action, "action", source.getNode(), "user-provided value"
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.InsecureDownloadQuery
|
||||
import DataFlow::PathGraph
|
||||
import DataFlow::DeduplicatePathGraph<InsecureDownloadFlow::PathNode, InsecureDownloadFlow::PathGraph>
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from PathNode source, PathNode sink
|
||||
where InsecureDownloadFlow::flowPath(source.getAnOriginalPathNode(), sink.getAnOriginalPathNode())
|
||||
select sink.getNode(), source, sink, "$@ of sensitive file from $@.",
|
||||
sink.getNode().(Sink).getDownloadCall(), "Download", source.getNode(), "HTTP source"
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.LoopBoundInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
import LoopBoundInjectionFlow::PathGraph
|
||||
|
||||
from Configuration dataflow, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where dataflow.hasFlowPath(source, sink)
|
||||
from LoopBoundInjectionFlow::PathNode source, LoopBoundInjectionFlow::PathNode sink
|
||||
where LoopBoundInjectionFlow::flowPath(source, sink)
|
||||
select sink, source, sink,
|
||||
"Iteration over a user-controlled object with a potentially unbounded .length property from a $@.",
|
||||
source, "user-provided value"
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.TypeConfusionThroughParameterTamperingQuery
|
||||
import DataFlow::PathGraph
|
||||
import TypeConfusionFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from TypeConfusionFlow::PathNode source, TypeConfusionFlow::PathNode sink
|
||||
where TypeConfusionFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"Potential type confusion as $@ may be either an array or a string.", source.getNode(),
|
||||
"this HTTP request parameter"
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.HttpToFileAccessQuery
|
||||
import DataFlow::PathGraph
|
||||
import HttpToFileAccessFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from HttpToFileAccessFlow::PathNode source, HttpToFileAccessFlow::PathNode sink
|
||||
where HttpToFileAccessFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Write to file system depends on $@.", source.getNode(),
|
||||
"Untrusted data"
|
||||
|
||||
@@ -19,10 +19,13 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.PrototypePollutingAssignmentQuery
|
||||
import DataFlow::PathGraph
|
||||
import PrototypePollutingAssignmentFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from
|
||||
PrototypePollutingAssignmentFlow::PathNode source, PrototypePollutingAssignmentFlow::PathNode sink
|
||||
where
|
||||
PrototypePollutingAssignmentFlow::flowPath(source, sink) and
|
||||
not isIgnoredLibraryFlow(source.getNode(), sink.getNode())
|
||||
select sink, source, sink,
|
||||
"This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@.",
|
||||
source.getNode(), source.getNode().(Source).describe()
|
||||
|
||||
@@ -17,11 +17,10 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow
|
||||
import PathGraph
|
||||
import semmle.javascript.DynamicPropertyAccess
|
||||
private import semmle.javascript.dataflow.InferredTypes
|
||||
|
||||
// WIN: gained TP in Lucifier/r.js:2757, though not sure why it wasn't flagged to start with.
|
||||
/**
|
||||
* A call of form `x.split(".")` where `x` is a parameter.
|
||||
*
|
||||
@@ -30,14 +29,14 @@ private import semmle.javascript.dataflow.InferredTypes
|
||||
class SplitCall extends StringSplitCall {
|
||||
SplitCall() {
|
||||
this.getSeparator() = "." and
|
||||
this.getBaseString().getALocalSource() instanceof ParameterNode
|
||||
this.getBaseString().getALocalSource() instanceof DataFlow::ParameterNode
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred -> succ` should preserve polluted property names.
|
||||
*/
|
||||
predicate copyArrayStep(SourceNode pred, SourceNode succ) {
|
||||
predicate copyArrayStep(DataFlow::SourceNode pred, DataFlow::SourceNode succ) {
|
||||
// x -> [...x]
|
||||
exists(SpreadElement spread |
|
||||
pred.flowsTo(spread.getOperand().flow()) and
|
||||
@@ -45,7 +44,7 @@ predicate copyArrayStep(SourceNode pred, SourceNode succ) {
|
||||
)
|
||||
or
|
||||
// `x -> y` in `y.push( x[i] )`
|
||||
exists(MethodCallNode push |
|
||||
exists(DataFlow::MethodCallNode push |
|
||||
push = succ.getAMethodCall("push") and
|
||||
(
|
||||
getAnEnumeratedArrayElement(pred).flowsTo(push.getAnArgument())
|
||||
@@ -55,7 +54,7 @@ predicate copyArrayStep(SourceNode pred, SourceNode succ) {
|
||||
)
|
||||
or
|
||||
// x -> x.concat(...)
|
||||
exists(MethodCallNode concat_ |
|
||||
exists(DataFlow::MethodCallNode concat_ |
|
||||
concat_.getMethodName() = "concat" and
|
||||
(pred = concat_.getReceiver() or pred = concat_.getAnArgument()) and
|
||||
succ = concat_
|
||||
@@ -66,21 +65,21 @@ predicate copyArrayStep(SourceNode pred, SourceNode succ) {
|
||||
* Holds if `node` may refer to a `SplitCall` or a copy thereof, possibly
|
||||
* returned through a function call.
|
||||
*/
|
||||
predicate isSplitArray(SourceNode node) {
|
||||
predicate isSplitArray(DataFlow::SourceNode node) {
|
||||
node instanceof SplitCall
|
||||
or
|
||||
exists(SourceNode pred | isSplitArray(pred) |
|
||||
exists(DataFlow::SourceNode pred | isSplitArray(pred) |
|
||||
copyArrayStep(pred, node)
|
||||
or
|
||||
pred.flowsToExpr(node.(CallNode).getACallee().getAReturnedExpr())
|
||||
pred.flowsToExpr(node.(DataFlow::CallNode).getACallee().getAReturnedExpr())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A property name originating from a `x.split(".")` call.
|
||||
*/
|
||||
class SplitPropName extends SourceNode {
|
||||
SourceNode array;
|
||||
class SplitPropName extends DataFlow::SourceNode {
|
||||
DataFlow::SourceNode array;
|
||||
|
||||
SplitPropName() {
|
||||
isSplitArray(array) and
|
||||
@@ -90,7 +89,7 @@ class SplitPropName extends SourceNode {
|
||||
/**
|
||||
* Gets the array from which this property name was obtained (the result from `split`).
|
||||
*/
|
||||
SourceNode getArray() { result = array }
|
||||
DataFlow::SourceNode getArray() { result = array }
|
||||
|
||||
/** Gets an element accessed on the same underlying array. */
|
||||
SplitPropName getAnAlias() { result.getArray() = this.getArray() }
|
||||
@@ -117,18 +116,18 @@ predicate isPollutedPropNameSource(DataFlow::Node node) {
|
||||
* Holds if `node` may flow from a source of polluted propery names, possibly
|
||||
* into function calls (but not returns).
|
||||
*/
|
||||
predicate isPollutedPropName(Node node) {
|
||||
predicate isPollutedPropName(DataFlow::Node node) {
|
||||
isPollutedPropNameSource(node)
|
||||
or
|
||||
exists(Node pred | isPollutedPropName(pred) |
|
||||
exists(DataFlow::Node pred | isPollutedPropName(pred) |
|
||||
node = pred.getASuccessor()
|
||||
or
|
||||
argumentPassingStep(_, pred, _, node)
|
||||
DataFlow::argumentPassingStep(_, pred, _, node)
|
||||
or
|
||||
// Handle one level of callbacks
|
||||
exists(FunctionNode function, ParameterNode callback, int i |
|
||||
exists(DataFlow::FunctionNode function, DataFlow::ParameterNode callback, int i |
|
||||
pred = callback.getAnInvocation().getArgument(i) and
|
||||
argumentPassingStep(_, function, _, callback) and
|
||||
DataFlow::argumentPassingStep(_, function, _, callback) and
|
||||
node = function.getParameter(i)
|
||||
)
|
||||
)
|
||||
@@ -138,8 +137,8 @@ predicate isPollutedPropName(Node node) {
|
||||
* Holds if `node` may refer to `Object.prototype` obtained through dynamic property
|
||||
* read of a property obtained through property enumeration.
|
||||
*/
|
||||
predicate isPotentiallyObjectPrototype(SourceNode node) {
|
||||
exists(Node base, Node key |
|
||||
predicate isPotentiallyObjectPrototype(DataFlow::SourceNode node) {
|
||||
exists(DataFlow::Node base, DataFlow::Node key |
|
||||
dynamicPropReadStep(base, key, node) and
|
||||
isPollutedPropName(key) and
|
||||
// Ignore cases where the properties of `base` are enumerated, to avoid FPs
|
||||
@@ -149,8 +148,8 @@ predicate isPotentiallyObjectPrototype(SourceNode node) {
|
||||
not arePropertiesEnumerated(base.getALocalSource())
|
||||
)
|
||||
or
|
||||
exists(Node use | isPotentiallyObjectPrototype(use.getALocalSource()) |
|
||||
argumentPassingStep(_, use, _, node)
|
||||
exists(DataFlow::Node use | isPotentiallyObjectPrototype(use.getALocalSource()) |
|
||||
DataFlow::argumentPassingStep(_, use, _, node)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -193,14 +192,6 @@ string unsafePropName() {
|
||||
result = "constructor"
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow label representing an unsafe property name, or an object obtained
|
||||
* by using such a property in a dynamic read.
|
||||
*/
|
||||
class UnsafePropLabel extends FlowLabel {
|
||||
UnsafePropLabel() { this = unsafePropName() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks data from property enumerations to dynamic property writes.
|
||||
*
|
||||
@@ -233,11 +224,13 @@ class UnsafePropLabel extends FlowLabel {
|
||||
* for coinciding paths afterwards. This means this configuration can't be used as
|
||||
* a standalone configuration like in most path queries.
|
||||
*/
|
||||
class PropNameTracking extends DataFlow::Configuration {
|
||||
PropNameTracking() { this = "PropNameTracking" }
|
||||
module PropNameTrackingConfig implements DataFlow::StateConfigSig {
|
||||
class FlowState extends string {
|
||||
FlowState() { this = unsafePropName() }
|
||||
}
|
||||
|
||||
override predicate isSource(DataFlow::Node node, FlowLabel label) {
|
||||
label instanceof UnsafePropLabel and
|
||||
predicate isSource(DataFlow::Node node, FlowState state) {
|
||||
exists(state) and
|
||||
(
|
||||
isPollutedPropNameSource(node)
|
||||
or
|
||||
@@ -245,8 +238,8 @@ class PropNameTracking extends DataFlow::Configuration {
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node node, FlowLabel label) {
|
||||
label instanceof UnsafePropLabel and
|
||||
predicate isSink(DataFlow::Node node, FlowState state) {
|
||||
exists(state) and
|
||||
(
|
||||
dynamicPropWrite(node, _, _) or
|
||||
dynamicPropWrite(_, node, _) or
|
||||
@@ -254,51 +247,67 @@ class PropNameTracking extends DataFlow::Configuration {
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isAdditionalFlowStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, FlowLabel predlbl, FlowLabel succlbl
|
||||
predicate isBarrier(DataFlow::Node node, FlowState state) {
|
||||
node = DataFlow::MakeStateBarrierGuard<FlowState, BarrierGuard>::getABarrierNode(state)
|
||||
}
|
||||
|
||||
predicate isAdditionalFlowStep(
|
||||
DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2
|
||||
) {
|
||||
predlbl instanceof UnsafePropLabel and
|
||||
succlbl = predlbl and
|
||||
exists(state1) and
|
||||
state2 = state1 and
|
||||
(
|
||||
// Step through `p -> x[p]`
|
||||
exists(PropRead read |
|
||||
pred = read.getPropertyNameExpr().flow() and
|
||||
exists(DataFlow::PropRead read |
|
||||
node1 = read.getPropertyNameExpr().flow() and
|
||||
not read.(DynamicPropRead).hasDominatingAssignment() and
|
||||
succ = read
|
||||
node2 = read
|
||||
)
|
||||
or
|
||||
// Step through `x -> x[p]`
|
||||
exists(DynamicPropRead read |
|
||||
not read.hasDominatingAssignment() and
|
||||
pred = read.getBase() and
|
||||
succ = read
|
||||
node1 = read.getBase() and
|
||||
node2 = read
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isBarrier(DataFlow::Node node) {
|
||||
super.isBarrier(node)
|
||||
or
|
||||
node instanceof DataFlow::VarAccessBarrier
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
node instanceof DataFlow::VarAccessBarrier or
|
||||
node = DataFlow::MakeBarrierGuard<BarrierGuard>::getABarrierNode()
|
||||
}
|
||||
|
||||
override predicate isBarrierGuard(DataFlow::BarrierGuardNode node) {
|
||||
node instanceof DenyListEqualityGuard or
|
||||
node instanceof AllowListEqualityGuard or
|
||||
node instanceof HasOwnPropertyGuard or
|
||||
node instanceof InExprGuard or
|
||||
node instanceof InstanceOfGuard or
|
||||
node instanceof TypeofGuard or
|
||||
node instanceof DenyListInclusionGuard or
|
||||
node instanceof AllowListInclusionGuard or
|
||||
node instanceof IsPlainObjectGuard
|
||||
int accessPathLimit() {
|
||||
// Speed up the query. For the pattern we're looking for the value rarely
|
||||
// flows through any contents, apart from a capture content.
|
||||
result = 1
|
||||
}
|
||||
}
|
||||
|
||||
class FlowState = PropNameTrackingConfig::FlowState;
|
||||
|
||||
module PropNameTracking = DataFlow::GlobalWithState<PropNameTrackingConfig>;
|
||||
|
||||
/**
|
||||
* A barrier guard for prototype pollution.
|
||||
*/
|
||||
abstract class BarrierGuard extends DataFlow::Node {
|
||||
/**
|
||||
* Holds if this node acts as a barrier for data flow, blocking further flow from `e` if `this` evaluates to `outcome`.
|
||||
*/
|
||||
predicate blocksExpr(boolean outcome, Expr e) { none() }
|
||||
|
||||
/**
|
||||
* Holds if this node acts as a barrier for `state`, blocking further flow from `e` if `this` evaluates to `outcome`.
|
||||
*/
|
||||
predicate blocksExpr(boolean outcome, Expr e, FlowState state) { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A sanitizer guard of form `x === "__proto__"` or `x === "constructor"`.
|
||||
*/
|
||||
class DenyListEqualityGuard extends DataFlow::LabeledBarrierGuardNode, ValueNode {
|
||||
class DenyListEqualityGuard extends BarrierGuard, DataFlow::ValueNode {
|
||||
override EqualityTest astNode;
|
||||
string propName;
|
||||
|
||||
@@ -307,17 +316,17 @@ class DenyListEqualityGuard extends DataFlow::LabeledBarrierGuardNode, ValueNode
|
||||
propName = unsafePropName()
|
||||
}
|
||||
|
||||
override predicate blocks(boolean outcome, Expr e, FlowLabel label) {
|
||||
override predicate blocksExpr(boolean outcome, Expr e, FlowState state) {
|
||||
e = astNode.getAnOperand() and
|
||||
outcome = astNode.getPolarity().booleanNot() and
|
||||
label = propName
|
||||
state = propName
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An equality test with something other than `__proto__` or `constructor`.
|
||||
*/
|
||||
class AllowListEqualityGuard extends DataFlow::LabeledBarrierGuardNode, ValueNode {
|
||||
class AllowListEqualityGuard extends BarrierGuard, DataFlow::ValueNode {
|
||||
override EqualityTest astNode;
|
||||
|
||||
AllowListEqualityGuard() {
|
||||
@@ -325,10 +334,9 @@ class AllowListEqualityGuard extends DataFlow::LabeledBarrierGuardNode, ValueNod
|
||||
astNode.getAnOperand() instanceof Literal
|
||||
}
|
||||
|
||||
override predicate blocks(boolean outcome, Expr e, FlowLabel label) {
|
||||
override predicate blocksExpr(boolean outcome, Expr e) {
|
||||
e = astNode.getAnOperand() and
|
||||
outcome = astNode.getPolarity() and
|
||||
label instanceof UnsafePropLabel
|
||||
outcome = astNode.getPolarity()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,7 +347,7 @@ class AllowListEqualityGuard extends DataFlow::LabeledBarrierGuardNode, ValueNod
|
||||
* but the destination object generally doesn't. It is therefore only a sanitizer when
|
||||
* used on the destination object.
|
||||
*/
|
||||
class HasOwnPropertyGuard extends DataFlow::BarrierGuardNode instanceof HasOwnPropertyCall {
|
||||
class HasOwnPropertyGuard extends BarrierGuard instanceof HasOwnPropertyCall {
|
||||
HasOwnPropertyGuard() {
|
||||
// Try to avoid `src.hasOwnProperty` by requiring that the receiver
|
||||
// does not locally have its properties enumerated. Typically there is no
|
||||
@@ -347,7 +355,7 @@ class HasOwnPropertyGuard extends DataFlow::BarrierGuardNode instanceof HasOwnPr
|
||||
not arePropertiesEnumerated(super.getObject().getALocalSource())
|
||||
}
|
||||
|
||||
override predicate blocks(boolean outcome, Expr e) {
|
||||
override predicate blocksExpr(boolean outcome, Expr e) {
|
||||
e = super.getProperty().asExpr() and outcome = true
|
||||
}
|
||||
}
|
||||
@@ -358,7 +366,7 @@ class HasOwnPropertyGuard extends DataFlow::BarrierGuardNode instanceof HasOwnPr
|
||||
* Since `"__proto__" in obj` and `"constructor" in obj` is true for most objects,
|
||||
* this is seen as a sanitizer for `key` in the false outcome.
|
||||
*/
|
||||
class InExprGuard extends DataFlow::BarrierGuardNode, DataFlow::ValueNode {
|
||||
class InExprGuard extends BarrierGuard, DataFlow::ValueNode {
|
||||
override InExpr astNode;
|
||||
|
||||
InExprGuard() {
|
||||
@@ -366,7 +374,7 @@ class InExprGuard extends DataFlow::BarrierGuardNode, DataFlow::ValueNode {
|
||||
not arePropertiesEnumerated(astNode.getRightOperand().flow().getALocalSource())
|
||||
}
|
||||
|
||||
override predicate blocks(boolean outcome, Expr e) {
|
||||
override predicate blocksExpr(boolean outcome, Expr e) {
|
||||
e = astNode.getLeftOperand() and outcome = false
|
||||
}
|
||||
}
|
||||
@@ -374,41 +382,41 @@ class InExprGuard extends DataFlow::BarrierGuardNode, DataFlow::ValueNode {
|
||||
/**
|
||||
* A sanitizer guard for `instanceof` expressions.
|
||||
*
|
||||
* `Object.prototype instanceof X` is never true, so this blocks the `__proto__` label.
|
||||
* `Object.prototype instanceof X` is never true, so this blocks the `__proto__` state.
|
||||
*
|
||||
* It is still possible to get to `Function.prototype` through `constructor.constructor.prototype`
|
||||
* so we do not block the `constructor` label.
|
||||
* so we do not block the `constructor` state.
|
||||
*/
|
||||
class InstanceOfGuard extends DataFlow::LabeledBarrierGuardNode, DataFlow::ValueNode {
|
||||
class InstanceOfGuard extends BarrierGuard, DataFlow::ValueNode {
|
||||
override InstanceOfExpr astNode;
|
||||
|
||||
override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel label) {
|
||||
e = astNode.getLeftOperand() and outcome = true and label = "__proto__"
|
||||
override predicate blocksExpr(boolean outcome, Expr e, FlowState state) {
|
||||
e = astNode.getLeftOperand() and outcome = true and state = "__proto__"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizer guard of form `typeof x === "object"` or `typeof x === "function"`.
|
||||
*
|
||||
* The former blocks the `constructor` label as that payload must pass through a function,
|
||||
* and the latter blocks the `__proto__` label as that only passes through objects.
|
||||
* The former blocks the `constructor` state as that payload must pass through a function,
|
||||
* and the latter blocks the `__proto__` state as that only passes through objects.
|
||||
*/
|
||||
class TypeofGuard extends DataFlow::LabeledBarrierGuardNode, DataFlow::ValueNode {
|
||||
class TypeofGuard extends BarrierGuard, DataFlow::ValueNode {
|
||||
override EqualityTest astNode;
|
||||
Expr operand;
|
||||
TypeofTag tag;
|
||||
|
||||
TypeofGuard() { TaintTracking::isTypeofGuard(astNode, operand, tag) }
|
||||
|
||||
override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel label) {
|
||||
override predicate blocksExpr(boolean outcome, Expr e, FlowState state) {
|
||||
e = operand and
|
||||
outcome = astNode.getPolarity() and
|
||||
(
|
||||
tag = "object" and
|
||||
label = "constructor"
|
||||
state = "constructor"
|
||||
or
|
||||
tag = "function" and
|
||||
label = "__proto__"
|
||||
state = "__proto__"
|
||||
)
|
||||
or
|
||||
e = operand and
|
||||
@@ -417,10 +425,10 @@ class TypeofGuard extends DataFlow::LabeledBarrierGuardNode, DataFlow::ValueNode
|
||||
// If something is not an object, sanitize object, as both must end
|
||||
// in non-function prototype object.
|
||||
tag = "object" and
|
||||
label instanceof UnsafePropLabel
|
||||
exists(state)
|
||||
or
|
||||
tag = "function" and
|
||||
label = "constructor"
|
||||
state = "constructor"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -428,27 +436,27 @@ class TypeofGuard extends DataFlow::LabeledBarrierGuardNode, DataFlow::ValueNode
|
||||
/**
|
||||
* A check of form `["__proto__"].includes(x)` or similar.
|
||||
*/
|
||||
class DenyListInclusionGuard extends DataFlow::LabeledBarrierGuardNode, InclusionTest {
|
||||
UnsafePropLabel label;
|
||||
class DenyListInclusionGuard extends BarrierGuard, InclusionTest {
|
||||
string blockedProp;
|
||||
|
||||
DenyListInclusionGuard() {
|
||||
exists(DataFlow::ArrayCreationNode array |
|
||||
array.getAnElement().getStringValue() = label and
|
||||
array.getAnElement().getStringValue() = blockedProp and
|
||||
array.flowsTo(this.getContainerNode())
|
||||
)
|
||||
}
|
||||
|
||||
override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel lbl) {
|
||||
override predicate blocksExpr(boolean outcome, Expr e, FlowState state) {
|
||||
outcome = this.getPolarity().booleanNot() and
|
||||
e = this.getContainedNode().asExpr() and
|
||||
label = lbl
|
||||
blockedProp = state
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A check of form `xs.includes(x)` or similar, which sanitizes `x` in the true case.
|
||||
*/
|
||||
class AllowListInclusionGuard extends DataFlow::LabeledBarrierGuardNode {
|
||||
class AllowListInclusionGuard extends BarrierGuard {
|
||||
AllowListInclusionGuard() {
|
||||
this instanceof TaintTracking::PositiveIndexOfSanitizer
|
||||
or
|
||||
@@ -456,9 +464,8 @@ class AllowListInclusionGuard extends DataFlow::LabeledBarrierGuardNode {
|
||||
not this = any(MembershipCandidate::ObjectPropertyNameMembershipCandidate c).getTest() // handled with more precision in `HasOwnPropertyGuard`
|
||||
}
|
||||
|
||||
override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel lbl) {
|
||||
this.(TaintTracking::AdditionalSanitizerGuardNode).sanitizes(outcome, e) and
|
||||
lbl instanceof UnsafePropLabel
|
||||
override predicate blocksExpr(boolean outcome, Expr e) {
|
||||
this.(TaintTracking::AdditionalBarrierGuard).blocksExpr(outcome, e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -467,17 +474,17 @@ class AllowListInclusionGuard extends DataFlow::LabeledBarrierGuardNode {
|
||||
* payload in the true case, since it rejects objects with a non-standard `constructor`
|
||||
* property.
|
||||
*/
|
||||
class IsPlainObjectGuard extends DataFlow::LabeledBarrierGuardNode, DataFlow::CallNode {
|
||||
class IsPlainObjectGuard extends BarrierGuard, DataFlow::CallNode {
|
||||
IsPlainObjectGuard() {
|
||||
exists(string name | name = "is-plain-object" or name = "is-extendable" |
|
||||
this = moduleImport(name).getACall()
|
||||
this = DataFlow::moduleImport(name).getACall()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel lbl) {
|
||||
override predicate blocksExpr(boolean outcome, Expr e, FlowState state) {
|
||||
e = this.getArgument(0).asExpr() and
|
||||
outcome = true and
|
||||
lbl = "constructor"
|
||||
state = "constructor"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,26 +514,26 @@ string deriveExprName(DataFlow::Node node) {
|
||||
* In most cases this will result in an alert, the exception being the case where
|
||||
* `base` does not have a prototype at all.
|
||||
*/
|
||||
predicate isPrototypePollutingAssignment(Node base, Node prop, Node rhs, Node propNameSource) {
|
||||
predicate isPrototypePollutingAssignment(
|
||||
DataFlow::Node base, DataFlow::Node prop, DataFlow::Node rhs, DataFlow::Node propNameSource
|
||||
) {
|
||||
dynamicPropWrite(base, prop, rhs) and
|
||||
isPollutedPropNameSource(propNameSource) and
|
||||
exists(PropNameTracking cfg |
|
||||
cfg.hasFlow(propNameSource, base) and
|
||||
if propNameSource instanceof EnumeratedPropName
|
||||
then
|
||||
cfg.hasFlow(propNameSource, prop) and
|
||||
cfg.hasFlow([propNameSource, AccessPath::getAnAliasedSourceNode(propNameSource)]
|
||||
.(EnumeratedPropName)
|
||||
.getASourceProp(), rhs)
|
||||
else (
|
||||
cfg.hasFlow(propNameSource.(SplitPropName).getAnAlias(), prop) and
|
||||
rhs.getALocalSource() instanceof ParameterNode
|
||||
)
|
||||
PropNameTracking::flow(propNameSource, base) and
|
||||
if propNameSource instanceof EnumeratedPropName
|
||||
then
|
||||
PropNameTracking::flow(propNameSource, prop) and
|
||||
PropNameTracking::flow([propNameSource, AccessPath::getAnAliasedSourceNode(propNameSource)]
|
||||
.(EnumeratedPropName)
|
||||
.getASourceProp(), rhs)
|
||||
else (
|
||||
PropNameTracking::flow(propNameSource.(SplitPropName).getAnAlias(), prop) and
|
||||
rhs.getALocalSource() instanceof DataFlow::ParameterNode
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a data flow node leading to the base of a prototype-polluting assignment. */
|
||||
private DataFlow::SourceNode getANodeLeadingToBase(DataFlow::TypeBackTracker t, Node base) {
|
||||
private DataFlow::SourceNode getANodeLeadingToBase(DataFlow::TypeBackTracker t, DataFlow::Node base) {
|
||||
t.start() and
|
||||
isPrototypePollutingAssignment(base, _, _, _) and
|
||||
result = base.getALocalSource()
|
||||
@@ -542,7 +549,9 @@ private DataFlow::SourceNode getANodeLeadingToBase(DataFlow::TypeBackTracker t,
|
||||
* This dynamic read is where the reference to a built-in prototype object is obtained,
|
||||
* and we need this to ensure that this object actually has a prototype.
|
||||
*/
|
||||
private DataFlow::SourceNode getANodeLeadingToBaseBase(DataFlow::TypeBackTracker t, Node base) {
|
||||
private DataFlow::SourceNode getANodeLeadingToBaseBase(
|
||||
DataFlow::TypeBackTracker t, DataFlow::Node base
|
||||
) {
|
||||
exists(DynamicPropRead read |
|
||||
read = getANodeLeadingToBase(t, base) and
|
||||
result = read.getBase().getALocalSource()
|
||||
@@ -553,29 +562,31 @@ private DataFlow::SourceNode getANodeLeadingToBaseBase(DataFlow::TypeBackTracker
|
||||
)
|
||||
}
|
||||
|
||||
DataFlow::SourceNode getANodeLeadingToBaseBase(Node base) {
|
||||
DataFlow::SourceNode getANodeLeadingToBaseBase(DataFlow::Node base) {
|
||||
result = getANodeLeadingToBaseBase(DataFlow::TypeBackTracker::end(), base)
|
||||
}
|
||||
|
||||
/** A call to `Object.create(null)`. */
|
||||
class ObjectCreateNullCall extends CallNode {
|
||||
class ObjectCreateNullCall extends DataFlow::CallNode {
|
||||
ObjectCreateNullCall() {
|
||||
this = globalVarRef("Object").getAMemberCall("create") and
|
||||
this = DataFlow::globalVarRef("Object").getAMemberCall("create") and
|
||||
this.getArgument(0).asExpr() instanceof NullLiteral
|
||||
}
|
||||
}
|
||||
|
||||
import DataFlow::DeduplicatePathGraph<PropNameTracking::PathNode, PropNameTracking::PathGraph>
|
||||
|
||||
from
|
||||
PropNameTracking cfg, DataFlow::PathNode source, DataFlow::PathNode sink, Node propNameSource,
|
||||
Node base, string msg, Node col1, Node col2
|
||||
PathNode source, PathNode sink, DataFlow::Node propNameSource, DataFlow::Node base, string msg,
|
||||
DataFlow::Node col1, DataFlow::Node col2
|
||||
where
|
||||
isPollutedPropName(propNameSource) and
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
PropNameTracking::flowPath(source.getAnOriginalPathNode(), sink.getAnOriginalPathNode()) and
|
||||
isPrototypePollutingAssignment(base, _, _, propNameSource) and
|
||||
sink.getNode() = base and
|
||||
source.getNode() = propNameSource and
|
||||
(
|
||||
getANodeLeadingToBaseBase(base) instanceof ObjectLiteralNode
|
||||
getANodeLeadingToBaseBase(base) instanceof DataFlow::ObjectLiteralNode
|
||||
or
|
||||
not getANodeLeadingToBaseBase(base) instanceof ObjectCreateNullCall
|
||||
) and
|
||||
|
||||
@@ -19,13 +19,11 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.PrototypePollutionQuery
|
||||
import DataFlow::PathGraph
|
||||
import DataFlow::DeduplicatePathGraph<PrototypePollutionFlow::PathNode, PrototypePollutionFlow::PathGraph>
|
||||
|
||||
from
|
||||
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, string moduleName,
|
||||
Locatable dependencyLoc
|
||||
from PathNode source, PathNode sink, string moduleName, Locatable dependencyLoc
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
PrototypePollutionFlow::flowPath(source.getAnOriginalPathNode(), sink.getAnOriginalPathNode()) and
|
||||
sink.getNode().(Sink).dependencyInfo(moduleName, dependencyLoc)
|
||||
select sink.getNode(), source, sink,
|
||||
"Prototype pollution caused by merging a $@ using a vulnerable version of $@.", source,
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.InsufficientPasswordHashQuery
|
||||
import DataFlow::PathGraph
|
||||
import InsufficientPasswordHashFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from InsufficientPasswordHashFlow::PathNode source, InsufficientPasswordHashFlow::PathNode sink
|
||||
where InsufficientPasswordHashFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Password from $@ is hashed insecurely.", source.getNode(),
|
||||
source.getNode().(Source).describe()
|
||||
|
||||
@@ -13,11 +13,13 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.ClientSideRequestForgeryQuery
|
||||
import DataFlow::PathGraph
|
||||
import ClientSideRequestForgeryFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node request
|
||||
from
|
||||
ClientSideRequestForgeryFlow::PathNode source, ClientSideRequestForgeryFlow::PathNode sink,
|
||||
DataFlow::Node request
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
ClientSideRequestForgeryFlow::flowPath(source, sink) and
|
||||
request = sink.getNode().(Sink).getARequest()
|
||||
select request, source, sink, "The $@ of this request depends on a $@.", sink.getNode(),
|
||||
sink.getNode().(Sink).getKind(), source, "user-provided value"
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.RequestForgeryQuery
|
||||
import DataFlow::PathGraph
|
||||
import RequestForgeryFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node request
|
||||
from RequestForgeryFlow::PathNode source, RequestForgeryFlow::PathNode sink, DataFlow::Node request
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
RequestForgeryFlow::flowPath(source, sink) and
|
||||
request = sink.getNode().(Sink).getARequest()
|
||||
select request, source, sink, "The $@ of this request depends on a $@.", sink.getNode(),
|
||||
sink.getNode().(Sink).getKind(), source, "user-provided value"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @name Code injection
|
||||
* @name Code injection from dynamically imported code
|
||||
* @description Interpreting unsanitized user input as code allows a malicious user arbitrary
|
||||
* code execution.
|
||||
* @kind path-problem
|
||||
@@ -15,13 +15,11 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow
|
||||
import DataFlow::PathGraph
|
||||
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
abstract class Barrier extends DataFlow::Node { }
|
||||
|
||||
/** A non-first leaf in a string-concatenation. Seen as a sanitizer for dynamic import code injection. */
|
||||
class NonFirstStringConcatLeaf extends Sanitizer {
|
||||
class NonFirstStringConcatLeaf extends Barrier {
|
||||
NonFirstStringConcatLeaf() {
|
||||
exists(StringOps::ConcatenationRoot root |
|
||||
this = root.getALeaf() and
|
||||
@@ -51,39 +49,51 @@ class WorkerThreads extends DataFlow::Node {
|
||||
}
|
||||
}
|
||||
|
||||
class UrlConstructorLabel extends FlowLabel {
|
||||
UrlConstructorLabel() { this = "UrlConstructorLabel" }
|
||||
}
|
||||
newtype TFlowState =
|
||||
TTaint() or
|
||||
TUrlConstructor()
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for reasoning about code injection vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "CodeInjection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof DynamicImport }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink, FlowLabel label) {
|
||||
sink instanceof WorkerThreads and label instanceof UrlConstructorLabel
|
||||
module CodeInjectionConfig implements DataFlow::StateConfigSig {
|
||||
class FlowState extends TFlowState {
|
||||
string toString() {
|
||||
this = TTaint() and result = "taint"
|
||||
or
|
||||
this = TUrlConstructor() and result = "url-constructor"
|
||||
}
|
||||
}
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
predicate isSource(DataFlow::Node source, FlowState state) {
|
||||
source instanceof ActiveThreatModelSource and state = TTaint()
|
||||
}
|
||||
|
||||
override predicate isAdditionalFlowStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, FlowLabel predlbl, FlowLabel succlbl
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof DynamicImport }
|
||||
|
||||
predicate isSink(DataFlow::Node sink, FlowState state) {
|
||||
sink instanceof WorkerThreads and state = TUrlConstructor()
|
||||
}
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Barrier }
|
||||
|
||||
predicate isAdditionalFlowStep(
|
||||
DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2
|
||||
) {
|
||||
exists(DataFlow::NewNode newUrl | succ = newUrl |
|
||||
exists(DataFlow::NewNode newUrl | node2 = newUrl |
|
||||
newUrl = DataFlow::globalVarRef("URL").getAnInstantiation() and
|
||||
pred = newUrl.getArgument(0)
|
||||
node1 = newUrl.getArgument(0)
|
||||
) and
|
||||
predlbl instanceof StandardFlowLabel and
|
||||
succlbl instanceof UrlConstructorLabel
|
||||
state1 = TTaint() and
|
||||
state2 = TUrlConstructor()
|
||||
}
|
||||
}
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
module CodeInjectionFlow = TaintTracking::GlobalWithState<CodeInjectionConfig>;
|
||||
|
||||
import CodeInjectionFlow::PathGraph
|
||||
|
||||
from CodeInjectionFlow::PathNode source, CodeInjectionFlow::PathNode sink
|
||||
where CodeInjectionFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This command line depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -11,33 +11,32 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/** A taint tracking configuration for unsafe environment injection. */
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "envInjection" }
|
||||
module EnvValueAndKeyInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof ActiveThreatModelSource }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
sink = keyOfEnv() or
|
||||
sink = valueOfEnv()
|
||||
}
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
exists(DataFlow::InvokeNode ikn |
|
||||
ikn = DataFlow::globalVarRef("Object").getAMemberInvocation("keys")
|
||||
|
|
||||
pred = ikn.getArgument(0) and
|
||||
node1 = ikn.getArgument(0) and
|
||||
(
|
||||
succ = ikn.getAChainedMethodCall(["filter", "map"]) or
|
||||
succ = ikn or
|
||||
succ = ikn.getAChainedMethodCall("forEach").getABoundCallbackParameter(0, 0)
|
||||
node2 = ikn.getAChainedMethodCall(["filter", "map"]) or
|
||||
node2 = ikn or
|
||||
node2 = ikn.getAChainedMethodCall("forEach").getABoundCallbackParameter(0, 0)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module EnvValueAndKeyInjectionFlow = TaintTracking::Global<EnvValueAndKeyInjectionConfig>;
|
||||
|
||||
DataFlow::Node keyOfEnv() {
|
||||
result =
|
||||
NodeJSLib::process().getAPropertyRead("env").getAPropertyWrite().getPropertyNameExpr().flow()
|
||||
@@ -56,13 +55,15 @@ private predicate readToProcessEnv(DataFlow::Node envKey, DataFlow::Node envValu
|
||||
)
|
||||
}
|
||||
|
||||
import EnvValueAndKeyInjectionFlow::PathGraph
|
||||
|
||||
from
|
||||
Configuration cfgForValue, Configuration cfgForKey, DataFlow::PathNode source,
|
||||
DataFlow::PathNode envKey, DataFlow::PathNode envValue
|
||||
EnvValueAndKeyInjectionFlow::PathNode source, EnvValueAndKeyInjectionFlow::PathNode envKey,
|
||||
EnvValueAndKeyInjectionFlow::PathNode envValue
|
||||
where
|
||||
cfgForValue.hasFlowPath(source, envKey) and
|
||||
EnvValueAndKeyInjectionFlow::flowPath(source, envKey) and
|
||||
envKey.getNode() = keyOfEnv() and
|
||||
cfgForKey.hasFlowPath(source, envValue) and
|
||||
EnvValueAndKeyInjectionFlow::flowPath(source, envValue) and
|
||||
envValue.getNode() = valueOfEnv() and
|
||||
readToProcessEnv(envKey.getNode(), envValue.getNode())
|
||||
select envKey.getNode(), source, envKey, "arbitrary environment variable assignment from this $@.",
|
||||
|
||||
@@ -11,20 +11,21 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/** A taint tracking configuration for unsafe environment injection. */
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "envInjection" }
|
||||
module EnvValueInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
sink = API::moduleImport("process").getMember("env").getAMember().asSink()
|
||||
}
|
||||
}
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
module EnvValueInjectionFlow = TaintTracking::Global<EnvValueInjectionConfig>;
|
||||
|
||||
import EnvValueInjectionFlow::PathGraph
|
||||
|
||||
from EnvValueInjectionFlow::PathNode source, EnvValueInjectionFlow::PathNode sink
|
||||
where EnvValueInjectionFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "this environment variable assignment is $@.",
|
||||
source.getNode(), "user controllable"
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
import javascript
|
||||
import DataFlow
|
||||
import DataFlow::PathGraph
|
||||
|
||||
class PredictableResultSource extends DataFlow::Node {
|
||||
PredictableResultSource() {
|
||||
@@ -38,14 +37,16 @@ class TokenAssignmentValueSink extends DataFlow::Node {
|
||||
}
|
||||
}
|
||||
|
||||
class TokenBuiltFromUuidConfig extends TaintTracking::Configuration {
|
||||
TokenBuiltFromUuidConfig() { this = "TokenBuiltFromUuidConfig" }
|
||||
module TokenBuiltFromUuidConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof PredictableResultSource }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof PredictableResultSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof TokenAssignmentValueSink }
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof TokenAssignmentValueSink }
|
||||
}
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, TokenBuiltFromUuidConfig config
|
||||
where config.hasFlowPath(source, sink)
|
||||
module TokenBuiltFromUuidFlow = TaintTracking::Global<TokenBuiltFromUuidConfig>;
|
||||
|
||||
import TokenBuiltFromUuidFlow::PathGraph
|
||||
|
||||
from TokenBuiltFromUuidFlow::PathNode source, TokenBuiltFromUuidFlow::PathNode sink
|
||||
where TokenBuiltFromUuidFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Token built from $@.", source.getNode(), "predictable value"
|
||||
|
||||
@@ -11,30 +11,29 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow::PathGraph
|
||||
import JWT
|
||||
|
||||
class ConfigurationUnverifiedDecode extends TaintTracking::Configuration {
|
||||
ConfigurationUnverifiedDecode() { this = "jsonwebtoken without any signature verification" }
|
||||
module UnverifiedDecodeConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof ActiveThreatModelSource }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink = unverifiedDecode() }
|
||||
predicate isSink(DataFlow::Node sink) { sink = unverifiedDecode() }
|
||||
}
|
||||
|
||||
class ConfigurationVerifiedDecode extends TaintTracking::Configuration {
|
||||
ConfigurationVerifiedDecode() { this = "jsonwebtoken with signature verification" }
|
||||
module UnverifiedDecodeFlow = TaintTracking::Global<UnverifiedDecodeConfig>;
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
module VerifiedDecodeConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof ActiveThreatModelSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink = verifiedDecode() }
|
||||
predicate isSink(DataFlow::Node sink) { sink = verifiedDecode() }
|
||||
}
|
||||
|
||||
from ConfigurationUnverifiedDecode cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
module VerifiedDecodeFlow = TaintTracking::Global<VerifiedDecodeConfig>;
|
||||
|
||||
import UnverifiedDecodeFlow::PathGraph
|
||||
|
||||
from UnverifiedDecodeFlow::PathNode source, UnverifiedDecodeFlow::PathNode sink
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
not exists(ConfigurationVerifiedDecode cfg2 |
|
||||
cfg2.hasFlowPath(any(DataFlow::PathNode p | p.getNode() = source.getNode()), _)
|
||||
)
|
||||
UnverifiedDecodeFlow::flowPath(source, sink) and
|
||||
not VerifiedDecodeFlow::flow(source.getNode(), _)
|
||||
select source.getNode(), source, sink, "Decoding JWT $@.", sink.getNode(),
|
||||
"without signature verification"
|
||||
|
||||
@@ -11,29 +11,25 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow::PathGraph
|
||||
import JWT
|
||||
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "jsonwebtoken without any signature verification" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
module DecodeWithoutVerificationConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) {
|
||||
source = [unverifiedDecode(), verifiedDecode()].getALocalSource()
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
sink = unverifiedDecode()
|
||||
or
|
||||
sink = verifiedDecode()
|
||||
}
|
||||
}
|
||||
|
||||
module DecodeWithoutVerificationFlow = TaintTracking::Global<DecodeWithoutVerificationConfig>;
|
||||
|
||||
/** Holds if `source` flows to the first parameter of jsonwebtoken.verify */
|
||||
predicate isSafe(Configuration cfg, DataFlow::Node source) {
|
||||
exists(DataFlow::Node sink |
|
||||
cfg.hasFlow(source, sink) and
|
||||
sink = verifiedDecode()
|
||||
)
|
||||
predicate isSafe(DataFlow::Node source) {
|
||||
DecodeWithoutVerificationFlow::flow(source, verifiedDecode())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,15 +37,17 @@ predicate isSafe(Configuration cfg, DataFlow::Node source) {
|
||||
* - `source` does not flow to the first parameter of `jsonwebtoken.verify`, and
|
||||
* - `source` flows to the first parameter of `jsonwebtoken.decode`
|
||||
*/
|
||||
predicate isVulnerable(Configuration cfg, DataFlow::Node source, DataFlow::Node sink) {
|
||||
not isSafe(cfg, source) and // i.e., source does not flow to a verify call
|
||||
cfg.hasFlow(source, sink) and // but it does flow to something else
|
||||
predicate isVulnerable(DataFlow::Node source, DataFlow::Node sink) {
|
||||
not isSafe(source) and // i.e., source does not flow to a verify call
|
||||
DecodeWithoutVerificationFlow::flow(source, sink) and // but it does flow to something else
|
||||
sink = unverifiedDecode() // and that something else is a call to decode.
|
||||
}
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
import DecodeWithoutVerificationFlow::PathGraph
|
||||
|
||||
from DecodeWithoutVerificationFlow::PathNode source, DecodeWithoutVerificationFlow::PathNode sink
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
isVulnerable(cfg, source.getNode(), sink.getNode())
|
||||
DecodeWithoutVerificationFlow::flowPath(source, sink) and
|
||||
isVulnerable(source.getNode(), sink.getNode())
|
||||
select source.getNode(), source, sink, "Decoding JWT $@.", sink.getNode(),
|
||||
"without signature verification"
|
||||
|
||||
@@ -12,24 +12,25 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow::PathGraph
|
||||
import DecompressionBombs
|
||||
|
||||
class BombConfiguration extends TaintTracking::Configuration {
|
||||
BombConfiguration() { this = "DecompressionBombs" }
|
||||
module DecompressionBombConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof DecompressionBomb::Sink }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof DecompressionBomb::Sink }
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
exists(DecompressionBomb::AdditionalTaintStep addstep |
|
||||
addstep.isAdditionalTaintStep(pred, succ)
|
||||
addstep.isAdditionalTaintStep(node1, node2)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from BombConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
module DecompressionBombFlow = TaintTracking::Global<DecompressionBombConfig>;
|
||||
|
||||
import DecompressionBombFlow::PathGraph
|
||||
|
||||
from DecompressionBombFlow::PathNode source, DecompressionBombFlow::PathNode sink
|
||||
where DecompressionBombFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This Decompression depends on a $@.", source.getNode(),
|
||||
"potentially untrusted source"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import javascript
|
||||
import experimental.semmle.javascript.FormParsers
|
||||
import experimental.semmle.javascript.ReadableStream
|
||||
import DataFlow::PathGraph
|
||||
|
||||
module DecompressionBomb {
|
||||
/**
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
|
||||
import javascript
|
||||
import SSRF
|
||||
import DataFlow::PathGraph
|
||||
import SsrfFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node request
|
||||
from SsrfFlow::PathNode source, SsrfFlow::PathNode sink, DataFlow::Node request
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and request = sink.getNode().(RequestForgery::Sink).getARequest()
|
||||
SsrfFlow::flowPath(source, sink) and request = sink.getNode().(RequestForgery::Sink).getARequest()
|
||||
select sink, source, sink, "The URL of this request depends on a user-provided value."
|
||||
|
||||
@@ -2,42 +2,41 @@ import javascript
|
||||
import semmle.javascript.security.dataflow.RequestForgeryCustomizations
|
||||
import semmle.javascript.security.dataflow.UrlConcatenation
|
||||
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "SSRF" }
|
||||
module SsrfConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof RequestForgery::Source }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RequestForgery::Source }
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof RequestForgery::Sink }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof RequestForgery::Sink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
super.isSanitizer(node) or
|
||||
node instanceof RequestForgery::Sanitizer
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
node instanceof RequestForgery::Sanitizer or
|
||||
node = DataFlow::MakeBarrierGuard<BarrierGuard>::getABarrierNode()
|
||||
}
|
||||
|
||||
private predicate hasSanitizingSubstring(DataFlow::Node nd) {
|
||||
nd.getStringValue().regexpMatch(".*[?#].*")
|
||||
or
|
||||
this.hasSanitizingSubstring(StringConcatenation::getAnOperand(nd))
|
||||
hasSanitizingSubstring(StringConcatenation::getAnOperand(nd))
|
||||
or
|
||||
this.hasSanitizingSubstring(nd.getAPredecessor())
|
||||
hasSanitizingSubstring(nd.getAPredecessor())
|
||||
}
|
||||
|
||||
private predicate strictSanitizingPrefixEdge(DataFlow::Node source, DataFlow::Node sink) {
|
||||
exists(DataFlow::Node operator, int n |
|
||||
StringConcatenation::taintStep(source, sink, operator, n) and
|
||||
this.hasSanitizingSubstring(StringConcatenation::getOperand(operator, [0 .. n - 1]))
|
||||
hasSanitizingSubstring(StringConcatenation::getOperand(operator, [0 .. n - 1]))
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSanitizerOut(DataFlow::Node node) {
|
||||
this.strictSanitizingPrefixEdge(node, _)
|
||||
}
|
||||
predicate isBarrierOut(DataFlow::Node node) { strictSanitizingPrefixEdge(node, _) }
|
||||
}
|
||||
|
||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode nd) {
|
||||
nd instanceof IntegerCheck or
|
||||
nd instanceof ValidatorCheck or
|
||||
nd instanceof TernaryOperatorSanitizerGuard
|
||||
}
|
||||
module SsrfFlow = TaintTracking::Global<SsrfConfig>;
|
||||
|
||||
/**
|
||||
* DEPRECATED. Use the `SsrfFlow` module instead.
|
||||
*/
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "SSRF" }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,14 +55,14 @@ class Configuration extends TaintTracking::Configuration {
|
||||
class TernaryOperatorSanitizer extends RequestForgery::Sanitizer {
|
||||
TernaryOperatorSanitizer() {
|
||||
exists(
|
||||
TaintTracking::SanitizerGuardNode guard, IfStmt ifStmt, DataFlow::Node taintedInput,
|
||||
TaintTracking::AdditionalBarrierGuard guard, IfStmt ifStmt, DataFlow::Node taintedInput,
|
||||
boolean outcome, Stmt r, DataFlow::Node falseNode
|
||||
|
|
||||
ifStmt.getCondition().flow().getAPredecessor+() = guard and
|
||||
ifStmt.getCondition().flow().getAPredecessor+() = falseNode and
|
||||
falseNode.asExpr().(BooleanLiteral).mayHaveBooleanValue(false) and
|
||||
not ifStmt.getCondition() instanceof LogicalBinaryExpr and
|
||||
guard.sanitizes(outcome, taintedInput.asExpr()) and
|
||||
guard.blocksExpr(outcome, taintedInput.asExpr()) and
|
||||
(
|
||||
outcome = true and r = ifStmt.getThen() and not ifStmt.getCondition() instanceof LogNotExpr
|
||||
or
|
||||
@@ -81,6 +80,12 @@ class TernaryOperatorSanitizer extends RequestForgery::Sanitizer {
|
||||
}
|
||||
}
|
||||
|
||||
/** A barrier guard for this SSRF query. */
|
||||
abstract class BarrierGuard extends DataFlow::Node {
|
||||
/** Holds if flow through `e` should be blocked, provided this evaluates to `outcome`. */
|
||||
abstract predicate blocksExpr(boolean outcome, Expr e);
|
||||
}
|
||||
|
||||
/**
|
||||
* This sanitizer guard is another way of modeling the example from above
|
||||
* In this case:
|
||||
@@ -95,8 +100,8 @@ class TernaryOperatorSanitizer extends RequestForgery::Sanitizer {
|
||||
* Thats why we model this sanitizer guard which says that
|
||||
* the result of the ternary operator execution is a sanitizer guard.
|
||||
*/
|
||||
class TernaryOperatorSanitizerGuard extends TaintTracking::SanitizerGuardNode {
|
||||
TaintTracking::SanitizerGuardNode originalGuard;
|
||||
class TernaryOperatorSanitizerGuard extends BarrierGuard {
|
||||
TaintTracking::AdditionalBarrierGuard originalGuard;
|
||||
|
||||
TernaryOperatorSanitizerGuard() {
|
||||
this.getAPredecessor+().asExpr().(BooleanLiteral).mayHaveBooleanValue(false) and
|
||||
@@ -104,13 +109,13 @@ class TernaryOperatorSanitizerGuard extends TaintTracking::SanitizerGuardNode {
|
||||
not this.asExpr() instanceof LogicalBinaryExpr
|
||||
}
|
||||
|
||||
override predicate sanitizes(boolean outcome, Expr e) {
|
||||
override predicate blocksExpr(boolean outcome, Expr e) {
|
||||
not this.asExpr() instanceof LogNotExpr and
|
||||
originalGuard.sanitizes(outcome, e)
|
||||
originalGuard.blocksExpr(outcome, e)
|
||||
or
|
||||
exists(boolean originalOutcome |
|
||||
this.asExpr() instanceof LogNotExpr and
|
||||
originalGuard.sanitizes(originalOutcome, e) and
|
||||
originalGuard.blocksExpr(originalOutcome, e) and
|
||||
(
|
||||
originalOutcome = true and outcome = false
|
||||
or
|
||||
@@ -123,10 +128,10 @@ class TernaryOperatorSanitizerGuard extends TaintTracking::SanitizerGuardNode {
|
||||
/**
|
||||
* A call to Number.isInteger seen as a sanitizer guard because a number can't be used to exploit a SSRF.
|
||||
*/
|
||||
class IntegerCheck extends TaintTracking::SanitizerGuardNode, DataFlow::CallNode {
|
||||
class IntegerCheck extends DataFlow::CallNode, BarrierGuard {
|
||||
IntegerCheck() { this = DataFlow::globalVarRef("Number").getAMemberCall("isInteger") }
|
||||
|
||||
override predicate sanitizes(boolean outcome, Expr e) {
|
||||
override predicate blocksExpr(boolean outcome, Expr e) {
|
||||
outcome = true and
|
||||
e = this.getArgument(0).asExpr()
|
||||
}
|
||||
@@ -137,7 +142,7 @@ class IntegerCheck extends TaintTracking::SanitizerGuardNode, DataFlow::CallNode
|
||||
* validator is a library which has a variety of input-validation functions. We are interesed in
|
||||
* checking that source is a number (any type of number) or an alphanumeric value.
|
||||
*/
|
||||
class ValidatorCheck extends TaintTracking::SanitizerGuardNode, DataFlow::CallNode {
|
||||
class ValidatorCheck extends DataFlow::CallNode, BarrierGuard {
|
||||
ValidatorCheck() {
|
||||
exists(DataFlow::SourceNode mod, string method |
|
||||
mod = DataFlow::moduleImport("validator") and
|
||||
@@ -149,7 +154,7 @@ class ValidatorCheck extends TaintTracking::SanitizerGuardNode, DataFlow::CallNo
|
||||
)
|
||||
}
|
||||
|
||||
override predicate sanitizes(boolean outcome, Expr e) {
|
||||
override predicate blocksExpr(boolean outcome, Expr e) {
|
||||
outcome = true and
|
||||
e = this.getArgument(0).asExpr()
|
||||
}
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
|
||||
import javascript
|
||||
import CorsPermissiveConfigurationQuery
|
||||
import DataFlow::PathGraph
|
||||
import CorsPermissiveConfigurationFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from
|
||||
CorsPermissiveConfigurationFlow::PathNode source, CorsPermissiveConfigurationFlow::PathNode sink
|
||||
where CorsPermissiveConfigurationFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "CORS Origin misconfiguration due to a $@.", source.getNode(),
|
||||
"too permissive or user controlled value"
|
||||
|
||||
@@ -10,6 +10,45 @@ import Apollo::Apollo
|
||||
|
||||
/** Module containing sources, sinks, and sanitizers for overly permissive CORS configurations. */
|
||||
module CorsPermissiveConfiguration {
|
||||
private newtype TFlowState =
|
||||
TTaint() or
|
||||
TTrueOrNull() or
|
||||
TWildcard()
|
||||
|
||||
/** A flow state to asociate with a tracked value. */
|
||||
class FlowState extends TFlowState {
|
||||
/** Gets a string representation of this flow state. */
|
||||
string toString() {
|
||||
this = TTaint() and result = "taint"
|
||||
or
|
||||
this = TTrueOrNull() and result = "true-or-null"
|
||||
or
|
||||
this = TWildcard() and result = "wildcard"
|
||||
}
|
||||
|
||||
deprecated DataFlow::FlowLabel toFlowLabel() {
|
||||
this = TTaint() and result.isTaint()
|
||||
or
|
||||
this = TTrueOrNull() and result instanceof TrueAndNull
|
||||
or
|
||||
this = TWildcard() and result instanceof Wildcard
|
||||
}
|
||||
}
|
||||
|
||||
/** Predicates for working with flow states. */
|
||||
module FlowState {
|
||||
deprecated FlowState fromFlowLabel(DataFlow::FlowLabel label) { result.toFlowLabel() = label }
|
||||
|
||||
/** A tainted value. */
|
||||
FlowState taint() { result = TTaint() }
|
||||
|
||||
/** A `true` or `null` value. */
|
||||
FlowState trueOrNull() { result = TTrueOrNull() }
|
||||
|
||||
/** A `"*"` value. */
|
||||
FlowState wildcard() { result = TWildcard() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow source for permissive CORS configuration.
|
||||
*/
|
||||
@@ -38,18 +77,18 @@ module CorsPermissiveConfiguration {
|
||||
}
|
||||
|
||||
/** A flow label representing `true` and `null` values. */
|
||||
abstract class TrueAndNull extends DataFlow::FlowLabel {
|
||||
abstract deprecated class TrueAndNull extends DataFlow::FlowLabel {
|
||||
TrueAndNull() { this = "TrueAndNull" }
|
||||
}
|
||||
|
||||
TrueAndNull truenullLabel() { any() }
|
||||
deprecated TrueAndNull truenullLabel() { any() }
|
||||
|
||||
/** A flow label representing `*` value. */
|
||||
abstract class Wildcard extends DataFlow::FlowLabel {
|
||||
abstract deprecated class Wildcard extends DataFlow::FlowLabel {
|
||||
Wildcard() { this = "Wildcard" }
|
||||
}
|
||||
|
||||
Wildcard wildcardLabel() { any() }
|
||||
deprecated Wildcard wildcardLabel() { any() }
|
||||
|
||||
/** An overly permissive value for `origin` (Apollo) */
|
||||
class TrueNullValue extends Source {
|
||||
|
||||
@@ -10,37 +10,58 @@
|
||||
|
||||
import javascript
|
||||
import CorsPermissiveConfigurationCustomizations::CorsPermissiveConfiguration
|
||||
private import CorsPermissiveConfigurationCustomizations::CorsPermissiveConfiguration as CorsPermissiveConfiguration
|
||||
|
||||
/**
|
||||
* A data flow configuration for overly permissive CORS configuration.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
module CorsPermissiveConfigurationConfig implements DataFlow::StateConfigSig {
|
||||
class FlowState = CorsPermissiveConfiguration::FlowState;
|
||||
|
||||
predicate isSource(DataFlow::Node source, FlowState state) {
|
||||
source instanceof TrueNullValue and state = FlowState::trueOrNull()
|
||||
or
|
||||
source instanceof WildcardValue and state = FlowState::wildcard()
|
||||
or
|
||||
source instanceof RemoteFlowSource and state = FlowState::taint()
|
||||
}
|
||||
|
||||
predicate isSink(DataFlow::Node sink, FlowState state) {
|
||||
sink instanceof CorsApolloServer and state = [FlowState::taint(), FlowState::trueOrNull()]
|
||||
or
|
||||
sink instanceof ExpressCors and state = [FlowState::taint(), FlowState::wildcard()]
|
||||
}
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
module CorsPermissiveConfigurationFlow =
|
||||
TaintTracking::GlobalWithState<CorsPermissiveConfigurationConfig>;
|
||||
|
||||
/**
|
||||
* DEPRECATED. Use the `CorsPermissiveConfigurationFlow` module instead.
|
||||
*/
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "CorsPermissiveConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source, DataFlow::FlowLabel label) {
|
||||
source instanceof TrueNullValue and label = truenullLabel()
|
||||
or
|
||||
source instanceof WildcardValue and label = wildcardLabel()
|
||||
or
|
||||
source instanceof RemoteFlowSource and label = DataFlow::FlowLabel::taint()
|
||||
CorsPermissiveConfigurationConfig::isSource(source, FlowState::fromFlowLabel(label))
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel label) {
|
||||
sink instanceof CorsApolloServer and label = [DataFlow::FlowLabel::taint(), truenullLabel()]
|
||||
or
|
||||
sink instanceof ExpressCors and label = [DataFlow::FlowLabel::taint(), wildcardLabel()]
|
||||
CorsPermissiveConfigurationConfig::isSink(sink, FlowState::fromFlowLabel(label))
|
||||
}
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
super.isSanitizer(node) or
|
||||
node instanceof Sanitizer
|
||||
CorsPermissiveConfigurationConfig::isBarrier(node)
|
||||
}
|
||||
}
|
||||
|
||||
private class WildcardActivated extends DataFlow::FlowLabel, Wildcard {
|
||||
deprecated private class WildcardActivated extends DataFlow::FlowLabel, Wildcard {
|
||||
WildcardActivated() { this = this }
|
||||
}
|
||||
|
||||
private class TrueAndNullActivated extends DataFlow::FlowLabel, TrueAndNull {
|
||||
deprecated private class TrueAndNullActivated extends DataFlow::FlowLabel, TrueAndNull {
|
||||
TrueAndNullActivated() { this = this }
|
||||
}
|
||||
|
||||
@@ -12,11 +12,15 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.ExternalAPIUsedWithUntrustedDataQuery
|
||||
import DataFlow::PathGraph
|
||||
import semmle.javascript.heuristics.AdditionalSources
|
||||
import ExternalAPIUsedWithUntrustedDataFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink) and source.getNode() instanceof HeuristicSource
|
||||
from
|
||||
ExternalAPIUsedWithUntrustedDataFlow::PathNode source,
|
||||
ExternalAPIUsedWithUntrustedDataFlow::PathNode sink
|
||||
where
|
||||
ExternalAPIUsedWithUntrustedDataFlow::flowPath(source, sink) and
|
||||
source.getNode() instanceof HeuristicSource
|
||||
select sink, source, sink,
|
||||
"Call to " + sink.getNode().(Sink).getApiName() + " with untrusted data from $@.", source,
|
||||
source.toString()
|
||||
|
||||
@@ -16,17 +16,17 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.CommandInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
import semmle.javascript.heuristics.AdditionalSources
|
||||
import CommandInjectionFlow::PathGraph
|
||||
|
||||
from
|
||||
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node highlight,
|
||||
Source sourceNode
|
||||
CommandInjectionFlow::PathNode source, CommandInjectionFlow::PathNode sink,
|
||||
DataFlow::Node highlight, Source sourceNode
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
CommandInjectionFlow::flowPath(source, sink) and
|
||||
(
|
||||
if cfg.isSinkWithHighlight(sink.getNode(), _)
|
||||
then cfg.isSinkWithHighlight(sink.getNode(), highlight)
|
||||
if isSinkWithHighlight(sink.getNode(), _)
|
||||
then isSinkWithHighlight(sink.getNode(), highlight)
|
||||
else highlight = sink.getNode()
|
||||
) and
|
||||
sourceNode = source.getNode() and
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.DomBasedXssQuery
|
||||
import DataFlow::PathGraph
|
||||
import semmle.javascript.heuristics.AdditionalSources
|
||||
import DomBasedXssFlow::PathGraph
|
||||
|
||||
from DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink) and source.getNode() instanceof HeuristicSource
|
||||
from DomBasedXssFlow::PathNode source, DomBasedXssFlow::PathNode sink
|
||||
where DomBasedXssFlow::flowPath(source, sink) and source.getNode() instanceof HeuristicSource
|
||||
select sink.getNode(), source, sink,
|
||||
sink.getNode().(Sink).getVulnerabilityKind() + " vulnerability due to $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -15,18 +15,24 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.SqlInjectionQuery as SqlInjection
|
||||
import semmle.javascript.security.dataflow.NosqlInjectionQuery as NosqlInjection
|
||||
import DataFlow::PathGraph
|
||||
import semmle.javascript.security.dataflow.SqlInjectionQuery as Sql
|
||||
import semmle.javascript.security.dataflow.NosqlInjectionQuery as Nosql
|
||||
import semmle.javascript.heuristics.AdditionalSources
|
||||
|
||||
from DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, string type
|
||||
module Merged =
|
||||
DataFlow::MergePathGraph<Sql::SqlInjectionFlow::PathNode, Nosql::NosqlInjectionFlow::PathNode,
|
||||
Sql::SqlInjectionFlow::PathGraph, Nosql::NosqlInjectionFlow::PathGraph>;
|
||||
|
||||
import DataFlow::DeduplicatePathGraph<Merged::PathNode, Merged::PathGraph>
|
||||
|
||||
from PathNode source, PathNode sink, string type
|
||||
where
|
||||
(
|
||||
cfg instanceof SqlInjection::Configuration and type = "string"
|
||||
or
|
||||
cfg instanceof NosqlInjection::Configuration and type = "object"
|
||||
) and
|
||||
cfg.hasFlowPath(source, sink)
|
||||
Sql::SqlInjectionFlow::flowPath(source.getAnOriginalPathNode().asPathNode1(),
|
||||
sink.getAnOriginalPathNode().asPathNode1()) and
|
||||
type = "string"
|
||||
or
|
||||
Nosql::NosqlInjectionFlow::flowPath(source.getAnOriginalPathNode().asPathNode2(),
|
||||
sink.getAnOriginalPathNode().asPathNode2()) and
|
||||
type = "object"
|
||||
select sink.getNode(), source, sink, "This query " + type + " depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.CodeInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
import CodeInjectionFlow::PathGraph
|
||||
import semmle.javascript.heuristics.AdditionalSources
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink) and source.getNode() instanceof HeuristicSource
|
||||
from CodeInjectionFlow::PathNode source, CodeInjectionFlow::PathNode sink
|
||||
where CodeInjectionFlow::flowPath(source, sink) and source.getNode() instanceof HeuristicSource
|
||||
select sink.getNode(), source, sink, sink.getNode().(Sink).getMessagePrefix() + " depends on a $@.",
|
||||
source.getNode(), "user-provided value"
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow::PathGraph
|
||||
import semmle.javascript.security.dataflow.LogInjectionQuery
|
||||
import semmle.javascript.heuristics.AdditionalSources
|
||||
import LogInjectionFlow::PathGraph
|
||||
|
||||
from LogInjectionConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink) and source.getNode() instanceof HeuristicSource
|
||||
from LogInjectionFlow::PathNode source, LogInjectionFlow::PathNode sink
|
||||
where LogInjectionFlow::flowPath(source, sink) and source.getNode() instanceof HeuristicSource
|
||||
select sink.getNode(), source, sink, "Log entry depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -13,10 +13,11 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.TaintedFormatStringQuery
|
||||
import DataFlow::PathGraph
|
||||
import TaintedFormatStringFlow::PathGraph
|
||||
import semmle.javascript.heuristics.AdditionalSources
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink) and source.getNode() instanceof HeuristicSource
|
||||
from TaintedFormatStringFlow::PathNode source, TaintedFormatStringFlow::PathNode sink
|
||||
where
|
||||
TaintedFormatStringFlow::flowPath(source, sink) and source.getNode() instanceof HeuristicSource
|
||||
select sink.getNode(), source, sink, "Format string depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -15,11 +15,12 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.CorsMisconfigurationForCredentialsQuery
|
||||
import DataFlow::PathGraph
|
||||
import semmle.javascript.heuristics.AdditionalSources
|
||||
import CorsMisconfigurationFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink) and source.getNode() instanceof HeuristicSource
|
||||
from CorsMisconfigurationFlow::PathNode source, CorsMisconfigurationFlow::PathNode sink
|
||||
where
|
||||
CorsMisconfigurationFlow::flowPath(source, sink) and source.getNode() instanceof HeuristicSource
|
||||
select sink.getNode(), source, sink, "$@ leak vulnerability due to a $@.",
|
||||
sink.getNode().(Sink).getCredentialsHeader(), "Credential", source.getNode(),
|
||||
"misconfigured CORS header value"
|
||||
|
||||
@@ -15,10 +15,12 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.RemotePropertyInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
import RemotePropertyInjectionFlow::PathGraph
|
||||
import semmle.javascript.heuristics.AdditionalSources
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink) and source.getNode() instanceof HeuristicSource
|
||||
from RemotePropertyInjectionFlow::PathNode source, RemotePropertyInjectionFlow::PathNode sink
|
||||
where
|
||||
RemotePropertyInjectionFlow::flowPath(source, sink) and
|
||||
source.getNode() instanceof HeuristicSource
|
||||
select sink.getNode(), source, sink, sink.getNode().(Sink).getMessage() + " depends on a $@.",
|
||||
source.getNode(), "user-provided value"
|
||||
|
||||
@@ -14,10 +14,11 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.UnsafeDeserializationQuery
|
||||
import DataFlow::PathGraph
|
||||
import UnsafeDeserializationFlow::PathGraph
|
||||
import semmle.javascript.heuristics.AdditionalSources
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink) and source.getNode() instanceof HeuristicSource
|
||||
from UnsafeDeserializationFlow::PathNode source, UnsafeDeserializationFlow::PathNode sink
|
||||
where
|
||||
UnsafeDeserializationFlow::flowPath(source, sink) and source.getNode() instanceof HeuristicSource
|
||||
select sink.getNode(), source, sink, "Unsafe deserialization depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.XxeQuery
|
||||
import DataFlow::PathGraph
|
||||
import XxeFlow::PathGraph
|
||||
import semmle.javascript.heuristics.AdditionalSources
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink) and source.getNode() instanceof HeuristicSource
|
||||
from XxeFlow::PathNode source, XxeFlow::PathNode sink
|
||||
where XxeFlow::flowPath(source, sink) and source.getNode() instanceof HeuristicSource
|
||||
select sink.getNode(), source, sink,
|
||||
"XML parsing depends on a $@ without guarding against external entity expansion.",
|
||||
source.getNode(), "user-provided value"
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.XpathInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
import XpathInjectionFlow::PathGraph
|
||||
import semmle.javascript.heuristics.AdditionalSources
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink) and source.getNode() instanceof HeuristicSource
|
||||
from XpathInjectionFlow::PathNode source, XpathInjectionFlow::PathNode sink
|
||||
where XpathInjectionFlow::flowPath(source, sink) and source.getNode() instanceof HeuristicSource
|
||||
select sink.getNode(), source, sink, "XPath expression depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.RegExpInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
import RegExpInjectionFlow::PathGraph
|
||||
import semmle.javascript.heuristics.AdditionalSources
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink) and source.getNode() instanceof HeuristicSource
|
||||
from RegExpInjectionFlow::PathNode source, RegExpInjectionFlow::PathNode sink
|
||||
where RegExpInjectionFlow::flowPath(source, sink) and source.getNode() instanceof HeuristicSource
|
||||
select sink.getNode(), source, sink, "This regular expression is constructed from a $@.",
|
||||
source.getNode(), "user-provided value"
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow::PathGraph
|
||||
import semmle.javascript.security.dataflow.ResourceExhaustionQuery
|
||||
import semmle.javascript.heuristics.AdditionalSources
|
||||
import ResourceExhaustionFlow::PathGraph
|
||||
|
||||
from Configuration dataflow, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where dataflow.hasFlowPath(source, sink) and source.getNode() instanceof HeuristicSource
|
||||
from ResourceExhaustionFlow::PathNode source, ResourceExhaustionFlow::PathNode sink
|
||||
where ResourceExhaustionFlow::flowPath(source, sink) and source.getNode() instanceof HeuristicSource
|
||||
select sink, source, sink, sink.getNode().(Sink).getProblemDescription() + " from a $@.", source,
|
||||
"user-provided value"
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.XmlBombQuery
|
||||
import DataFlow::PathGraph
|
||||
import XmlBombFlow::PathGraph
|
||||
import semmle.javascript.heuristics.AdditionalSources
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink) and source.getNode() instanceof HeuristicSource
|
||||
from XmlBombFlow::PathNode source, XmlBombFlow::PathNode sink
|
||||
where XmlBombFlow::flowPath(source, sink) and source.getNode() instanceof HeuristicSource
|
||||
select sink.getNode(), source, sink,
|
||||
"XML parsing depends on a $@ without guarding against uncontrolled entity expansion.",
|
||||
source.getNode(), "user-provided value"
|
||||
|
||||
@@ -14,13 +14,15 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.ConditionalBypassQuery
|
||||
import DataFlow::PathGraph
|
||||
import semmle.javascript.heuristics.AdditionalSources
|
||||
import ConditionalBypassFlow::PathGraph
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, SensitiveAction action
|
||||
from
|
||||
ConditionalBypassFlow::PathNode source, ConditionalBypassFlow::PathNode sink,
|
||||
SensitiveAction action
|
||||
where
|
||||
isTaintedGuardForSensitiveAction(sink, source, action) and
|
||||
not isEarlyAbortGuard(sink, action) and
|
||||
isTaintedGuardNodeForSensitiveAction(sink, source, action) and
|
||||
not isEarlyAbortGuardNode(sink, action) and
|
||||
source.getNode() instanceof HeuristicSource
|
||||
select sink.getNode(), source, sink, "This condition guards a sensitive $@, but a $@ controls it.",
|
||||
action, "action", source.getNode(), "user-provided value"
|
||||
|
||||
@@ -20,11 +20,15 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.PrototypePollutingAssignmentQuery
|
||||
import DataFlow::PathGraph
|
||||
import semmle.javascript.heuristics.AdditionalSources
|
||||
import PrototypePollutingAssignmentFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink) and source.getNode() instanceof HeuristicSource
|
||||
from
|
||||
PrototypePollutingAssignmentFlow::PathNode source, PrototypePollutingAssignmentFlow::PathNode sink
|
||||
where
|
||||
PrototypePollutingAssignmentFlow::flowPath(source, sink) and
|
||||
not isIgnoredLibraryFlow(source.getNode(), sink.getNode()) and
|
||||
source.getNode() instanceof HeuristicSource
|
||||
select sink, source, sink,
|
||||
"This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@.",
|
||||
source.getNode(), source.getNode().(Source).describe()
|
||||
|
||||
@@ -133,44 +133,6 @@ private module StandardPoIs {
|
||||
override predicate is(Node l0) { l0 instanceof RemoteFlowSource }
|
||||
}
|
||||
|
||||
/**
|
||||
* A "source" for any active configuration.
|
||||
*/
|
||||
class SourcePoI extends PoI {
|
||||
SourcePoI() { this = "SourcePoI" }
|
||||
|
||||
override predicate is(Node l0) {
|
||||
exists(Configuration cfg | cfg.isSource(l0) or cfg.isSource(l0, _))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A "sink" for any active configuration.
|
||||
*/
|
||||
class SinkPoI extends PoI {
|
||||
SinkPoI() { this = "SinkPoI" }
|
||||
|
||||
override predicate is(Node l0) {
|
||||
exists(Configuration cfg | cfg.isSink(l0) or cfg.isSink(l0, _))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A "barrier" for any active configuration.
|
||||
*/
|
||||
class BarrierPoI extends PoI {
|
||||
BarrierPoI() { this = "BarrierPoI" }
|
||||
|
||||
override predicate is(Node l0) {
|
||||
exists(Configuration cfg |
|
||||
cfg.isBarrier(l0) or
|
||||
cfg.isBarrierEdge(l0, _) or
|
||||
cfg.isBarrierEdge(l0, _, _) or
|
||||
cfg.isLabeledBarrier(l0, _)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides groups of often used points of interest.
|
||||
*/
|
||||
@@ -185,16 +147,6 @@ private module StandardPoIs {
|
||||
this instanceof UnpromotedRouteHandlerWithFlowPoI
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A configuration-related point of interest.
|
||||
*/
|
||||
class DataFlowConfigurationPoI extends PoI {
|
||||
DataFlowConfigurationPoI() {
|
||||
this instanceof SourcePoI or
|
||||
this instanceof SinkPoI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
import StandardPoIGroups
|
||||
|
||||
@@ -12,20 +12,20 @@
|
||||
import javascript
|
||||
import meta.internal.TaintMetrics
|
||||
|
||||
class BasicTaintConfiguration extends TaintTracking::Configuration {
|
||||
BasicTaintConfiguration() { this = "BasicTaintConfiguration" }
|
||||
module BasicTaintConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node node) { node = relevantTaintSource() }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node = relevantTaintSource() }
|
||||
|
||||
override predicate isSink(DataFlow::Node node) {
|
||||
predicate isSink(DataFlow::Node node) {
|
||||
// To reduce noise from synthetic nodes, only count value nodes
|
||||
node instanceof DataFlow::ValueNode and
|
||||
not node.getFile() instanceof IgnoredFile
|
||||
}
|
||||
}
|
||||
|
||||
module BasicTaintFlow = TaintTracking::Global<BasicTaintConfig>;
|
||||
|
||||
// Avoid linking to the source as this would upset the statistics: nodes reachable
|
||||
// from multiple sources would be counted multilpe times, and that's not what we intend to measure.
|
||||
// from multiple sources would be counted multiple times, and that's not what we intend to measure.
|
||||
from DataFlow::Node node
|
||||
where any(BasicTaintConfiguration cfg).hasFlow(_, node)
|
||||
where BasicTaintFlow::flowTo(node)
|
||||
select node, "Tainted node"
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
import javascript
|
||||
import meta.internal.TaintMetrics
|
||||
|
||||
class BasicTaintConfiguration extends TaintTracking::Configuration {
|
||||
BasicTaintConfiguration() { this = "BasicTaintConfiguration" }
|
||||
module BasicTaintConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node node) { node = relevantTaintSource() }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node = relevantTaintSource() }
|
||||
|
||||
override predicate isSink(DataFlow::Node node) { node = relevantSanitizerInput() }
|
||||
predicate isSink(DataFlow::Node node) { node = relevantSanitizerInput() }
|
||||
}
|
||||
|
||||
select projectRoot(), count(DataFlow::Node node | any(BasicTaintConfiguration cfg).hasFlow(_, node))
|
||||
module BasicTaintFlow = TaintTracking::Global<BasicTaintConfig>;
|
||||
|
||||
select projectRoot(), count(DataFlow::Node node | BasicTaintFlow::flowTo(node))
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
import javascript
|
||||
import meta.internal.TaintMetrics
|
||||
|
||||
class BasicTaintConfiguration extends TaintTracking::Configuration {
|
||||
BasicTaintConfiguration() { this = "BasicTaintConfiguration" }
|
||||
module BasicTaintConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node node) { node = relevantSanitizerOutput() }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node = relevantSanitizerOutput() }
|
||||
|
||||
override predicate isSink(DataFlow::Node node) { node = relevantTaintSink() }
|
||||
predicate isSink(DataFlow::Node node) { node = relevantTaintSink() }
|
||||
}
|
||||
|
||||
select projectRoot(), count(DataFlow::Node node | any(BasicTaintConfiguration cfg).hasFlow(_, node))
|
||||
module BasicTaintFlow = TaintTracking::Global<BasicTaintConfig>;
|
||||
|
||||
select projectRoot(), count(DataFlow::Node node | BasicTaintFlow::flowTo(node))
|
||||
|
||||
@@ -17,8 +17,6 @@ predicate relevantStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
or
|
||||
DataFlow::SharedFlowStep::step(pred, succ)
|
||||
or
|
||||
DataFlow::SharedFlowStep::step(pred, succ, _, _)
|
||||
or
|
||||
DataFlow::SharedFlowStep::loadStep(pred, succ, _)
|
||||
or
|
||||
DataFlow::SharedFlowStep::storeStep(pred, succ, _)
|
||||
|
||||
@@ -12,16 +12,16 @@
|
||||
import javascript
|
||||
import meta.internal.TaintMetrics
|
||||
|
||||
class BasicTaintConfiguration extends TaintTracking::Configuration {
|
||||
BasicTaintConfiguration() { this = "BasicTaintConfiguration" }
|
||||
module BasicTaintConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node node) { node = relevantTaintSource() }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node = relevantTaintSource() }
|
||||
|
||||
override predicate isSink(DataFlow::Node node) {
|
||||
predicate isSink(DataFlow::Node node) {
|
||||
// To reduce noise from synthetic nodes, only count value nodes
|
||||
node instanceof DataFlow::ValueNode and
|
||||
not node.getFile() instanceof IgnoredFile
|
||||
}
|
||||
}
|
||||
|
||||
select projectRoot(), count(DataFlow::Node node | any(BasicTaintConfiguration cfg).hasFlow(_, node))
|
||||
module BasicTaintFlow = TaintTracking::Global<BasicTaintConfig>;
|
||||
|
||||
select projectRoot(), count(DataFlow::Node node | BasicTaintFlow::flowTo(node))
|
||||
|
||||
@@ -24,8 +24,6 @@ predicate unmodeled(API::Node callee, API::CallNode call, DataFlow::Node pred, D
|
||||
or
|
||||
DataFlow::SharedFlowStep::step(_, succ)
|
||||
or
|
||||
DataFlow::SharedFlowStep::step(_, succ, _, _)
|
||||
or
|
||||
DataFlow::SharedFlowStep::loadStep(_, succ, _)
|
||||
or
|
||||
DataFlow::SharedFlowStep::storeStep(_, succ, _)
|
||||
|
||||
Reference in New Issue
Block a user