JS: fix ql/field-only-used-in-charpred within JavaScript

This commit is contained in:
Erik Krogh Kristensen
2022-01-14 10:32:14 +01:00
parent 3d3c6875a6
commit b8f1fb3954
30 changed files with 147 additions and 181 deletions

View File

@@ -152,15 +152,12 @@ private module ArrayDataFlow {
/**
* A node that reads or writes an element from an array inside a for-loop.
*/
private class ArrayIndexingAccess extends DataFlow::Node {
DataFlow::PropRef read;
private class ArrayIndexingAccess extends DataFlow::Node instanceof DataFlow::PropRef {
ArrayIndexingAccess() {
read = this and
TTNumber() =
unique(InferredType type | type = read.getPropertyNameExpr().flow().analyze().getAType()) and
unique(InferredType type | type = super.getPropertyNameExpr().flow().analyze().getAType()) and
exists(VarAccess i, ExprOrVarDecl init |
i = read.getPropertyNameExpr() and init = any(ForStmt f).getInit()
i = super.getPropertyNameExpr() and init = any(ForStmt f).getInit()
|
i.getVariable().getADefinition() = init or
i.getVariable().getADefinition().(VariableDeclarator).getDeclStmt() = init

View File

@@ -445,9 +445,8 @@ module DataFlow {
*/
private class ReflectiveCallNode extends Node, TReflectiveCallNode {
MethodCallExpr call;
string kind;
ReflectiveCallNode() { this = TReflectiveCallNode(call, kind) }
ReflectiveCallNode() { this = TReflectiveCallNode(call, _) }
override BasicBlock getBasicBlock() { result = call.getBasicBlock() }

View File

@@ -379,9 +379,10 @@ private class AnalyzedExportAssign extends AnalyzedPropertyWrite, DataFlow::Valu
*/
private class AnalyzedClosureExportAssign extends AnalyzedPropertyWrite, DataFlow::ValueNode {
override AssignExpr astNode;
Closure::ClosureModule mod;
AnalyzedClosureExportAssign() { astNode.getLhs() = mod.getExportsVariable().getAReference() }
AnalyzedClosureExportAssign() {
astNode.getLhs() = any(Closure::ClosureModule mod).getExportsVariable().getAReference()
}
override predicate writes(AbstractValue baseVal, string propName, DataFlow::AnalyzedNode source) {
baseVal = TAbstractModuleObject(astNode.getTopLevel()) and

View File

@@ -302,12 +302,11 @@ private class TypeInferredMethodWithAnalyzedReturnFlow extends CallWithNonLocalA
* Propagates receivers into locally defined callbacks of partial invocations.
*/
private class AnalyzedThisInPartialInvokeCallback extends AnalyzedNode, DataFlow::ThisNode {
DataFlow::PartialInvokeNode call;
DataFlow::Node receiver;
AnalyzedThisInPartialInvokeCallback() {
exists(DataFlow::Node callbackArg |
receiver = call.getBoundReceiver(callbackArg) and
receiver = any(DataFlow::PartialInvokeNode call).getBoundReceiver(callbackArg) and
getBinder().flowsTo(callbackArg)
)
}

View File

@@ -16,9 +16,7 @@
import javascript
private class BackwardExploringConfiguration extends DataFlow::Configuration {
DataFlow::Configuration cfg;
BackwardExploringConfiguration() { this = cfg }
BackwardExploringConfiguration() { this = any(DataFlow::Configuration cfg) }
override predicate isSource(DataFlow::Node node) { any() }

View File

@@ -14,9 +14,7 @@
import javascript
private class ForwardExploringConfiguration extends DataFlow::Configuration {
DataFlow::Configuration cfg;
ForwardExploringConfiguration() { this = cfg }
ForwardExploringConfiguration() { this = any(DataFlow::Configuration cfg) }
override predicate isSink(DataFlow::Node node) { any() }

View File

@@ -149,12 +149,10 @@ DataFlow::CallNode moduleRef(AngularModule m) {
* A call to a method from the `angular.Module` API.
*/
class ModuleApiCall extends DataFlow::CallNode {
/** The module on which the method is called. */
AngularModule mod;
/** The name of the called method. */
string methodName;
ModuleApiCall() { this = moduleRef(mod).getAMethodCall(methodName) }
ModuleApiCall() { this = moduleRef(_).getAMethodCall(methodName) }
/**
* Gets the name of the invoked method.

View File

@@ -65,10 +65,9 @@ private string getInterpolatedExpressionPattern() { result = "(?<=\\{\\{).*?(?=\
*/
private class HtmlTextNodeAsNgSourceProvider extends NgSourceProvider, HTML::TextNode {
string source;
int offset;
HtmlTextNodeAsNgSourceProvider() {
source = this.getText().regexpFind(getInterpolatedExpressionPattern(), _, offset)
source = this.getText().regexpFind(getInterpolatedExpressionPattern(), _, _)
}
override predicate providesSourceAt(

View File

@@ -140,7 +140,6 @@ module Babel {
*/
private class BabelRootTransformedPathExpr extends PathExpr, Expr {
RootImportConfig plugin;
string rawPath;
string prefix;
string mappedPrefix;
string suffix;
@@ -148,9 +147,8 @@ module Babel {
BabelRootTransformedPathExpr() {
this instanceof PathExpr and
plugin.appliesTo(getTopLevel()) and
rawPath = getStringValue() and
prefix = rawPath.regexpCapture("(.)/(.*)", 1) and
suffix = rawPath.regexpCapture("(.)/(.*)", 2) and
prefix = getStringValue().regexpCapture("(.)/(.*)", 1) and
suffix = getStringValue().regexpCapture("(.)/(.*)", 2) and
mappedPrefix = plugin.getRoot(prefix)
}

View File

@@ -378,10 +378,9 @@ private module CryptoJS {
* A model of the TweetNaCl library.
*/
private module TweetNaCl {
private class Apply extends CryptographicOperation {
private class Apply extends CryptographicOperation instanceof MethodCallExpr {
Expr input;
CryptographicAlgorithm algorithm;
MethodCallExpr mce;
Apply() {
/*
@@ -395,15 +394,14 @@ private module TweetNaCl {
* Also matches the "hash" method name, and the "nacl-fast" module.
*/
this = mce and
exists(DataFlow::SourceNode mod, string name |
name = "hash" and algorithm.matchesName("SHA512")
or
name = "sign" and algorithm.matchesName("ed25519")
|
(mod = DataFlow::moduleImport("nacl") or mod = DataFlow::moduleImport("nacl-fast")) and
mce = mod.getAMemberCall(name).asExpr() and
mce.getArgument(0) = input
this = mod.getAMemberCall(name).asExpr() and
super.getArgument(0) = input
)
}
@@ -440,10 +438,9 @@ private module HashJs {
)
}
private class Apply extends CryptographicOperation {
private class Apply extends CryptographicOperation instanceof MethodCallExpr {
Expr input;
CryptographicAlgorithm algorithm; // non-functional
MethodCallExpr mce;
Apply() {
/*
@@ -459,9 +456,8 @@ private module HashJs {
* Also matches where `hash.<algorithmName>()` has been replaced by a more specific require a la `require("hash.js/lib/hash/sha/512")`
*/
this = mce and
mce = getAlgorithmExpr(algorithm).getAMemberCall("update").asExpr() and
input = mce.getArgument(0)
this = getAlgorithmExpr(algorithm).getAMemberCall("update").asExpr() and
input = super.getArgument(0)
}
override Expr getInput() { result = input }
@@ -535,16 +531,14 @@ private module Forge {
override CryptographicAlgorithm getAlgorithm() { result = algorithm }
}
private class Apply extends CryptographicOperation {
private class Apply extends CryptographicOperation instanceof MethodCallExpr {
Expr input;
CryptographicAlgorithm algorithm; // non-functional
MethodCallExpr mce;
Apply() {
this = mce and
exists(Cipher cipher |
mce = cipher.getAMemberCall("update").asExpr() and
mce.getArgument(0) = input and
this = cipher.getAMemberCall("update").asExpr() and
super.getArgument(0) = input and
algorithm = cipher.getAlgorithm()
)
}
@@ -596,19 +590,17 @@ private module Forge {
* A model of the md5 library.
*/
private module Md5 {
private class Apply extends CryptographicOperation {
private class Apply extends CryptographicOperation instanceof CallExpr {
Expr input;
CryptographicAlgorithm algorithm;
CallExpr call;
Apply() {
// `require("md5")("message");`
this = call and
exists(DataFlow::SourceNode mod |
mod = DataFlow::moduleImport("md5") and
algorithm.matchesName("MD5") and
call = mod.getACall().asExpr() and
call.getArgument(0) = input
this = mod.getACall().asExpr() and
super.getArgument(0) = input
)
}
@@ -622,14 +614,12 @@ private module Md5 {
* A model of the bcrypt, bcryptjs, bcrypt-nodejs libraries.
*/
private module Bcrypt {
private class Apply extends CryptographicOperation {
private class Apply extends CryptographicOperation instanceof MethodCallExpr {
Expr input;
CryptographicAlgorithm algorithm;
MethodCallExpr mce;
Apply() {
// `require("bcrypt").hash(password);` with minor naming variations
this = mce and
exists(DataFlow::SourceNode mod, string moduleName, string methodName |
algorithm.matchesName("BCRYPT") and
(
@@ -642,8 +632,8 @@ private module Bcrypt {
methodName = "hashSync"
) and
mod = DataFlow::moduleImport(moduleName) and
mce = mod.getAMemberCall(methodName).asExpr() and
mce.getArgument(0) = input
this = mod.getAMemberCall(methodName).asExpr() and
super.getArgument(0) = input
)
}
@@ -657,20 +647,18 @@ private module Bcrypt {
* A model of the hasha library.
*/
private module Hasha {
private class Apply extends CryptographicOperation {
private class Apply extends CryptographicOperation instanceof CallExpr {
Expr input;
CryptographicAlgorithm algorithm;
CallExpr call;
Apply() {
// `require('hasha')('unicorn', { algorithm: "md5" });`
this = call and
exists(DataFlow::SourceNode mod, string algorithmName, Expr algorithmNameNode |
mod = DataFlow::moduleImport("hasha") and
call = mod.getACall().asExpr() and
call.getArgument(0) = input and
this = mod.getACall().asExpr() and
super.getArgument(0) = input and
algorithm.matchesName(algorithmName) and
call.hasOptionArgument(1, "algorithm", algorithmNameNode) and
super.hasOptionArgument(1, "algorithm", algorithmNameNode) and
algorithmNameNode.mayHaveStringValue(algorithmName)
)
}

View File

@@ -411,10 +411,10 @@ module HTTP {
* E.g. `chunk` in: `http.createServer().on('request', (req, res) => req.on("data", (chunk) => ...))`.
*/
private class ServerRequestDataEvent extends RemoteFlowSource, DataFlow::ParameterNode {
RequestSource req;
ServerRequestDataEvent() {
exists(DataFlow::MethodCallNode mcn | mcn = req.ref().getAMethodCall(EventEmitter::on()) |
exists(DataFlow::MethodCallNode mcn, RequestSource req |
mcn = req.ref().getAMethodCall(EventEmitter::on())
|
mcn.getArgument(0).mayHaveStringValue("data") and
this = mcn.getABoundCallbackParameter(1, 0)
)

View File

@@ -74,12 +74,13 @@ private module HttpProxy {
*/
class ProxyListenerCallback extends NodeJSLib::RouteHandler, DataFlow::FunctionNode {
string event;
API::CallNode call;
ProxyListenerCallback() {
call = any(CreateServerCall server).getReturn().getMember(["on", "once"]).getACall() and
call.getParameter(0).getARhs().mayHaveStringValue(event) and
this = call.getParameter(1).getARhs().getAFunctionValue()
exists(API::CallNode call |
call = any(CreateServerCall server).getReturn().getMember(["on", "once"]).getACall() and
call.getParameter(0).getARhs().mayHaveStringValue(event) and
this = call.getParameter(1).getARhs().getAFunctionValue()
)
}
override Parameter getRequestParameter() {

View File

@@ -29,9 +29,9 @@ module LdapJS {
/** A reference to a LDAPjs client `search` options. */
class SearchOptions extends API::Node {
ClientCall call;
SearchOptions() { call.getMethodName() = "search" and this = call.getParameter(1) }
SearchOptions() {
exists(ClientCall call | call.getMethodName() = "search" and this = call.getParameter(1))
}
}
/** A creation of an LDAPjs filter, or object containing a filter, that doesn't sanitizes the input. */

View File

@@ -376,11 +376,11 @@ module NestJS {
* redirects to `https://example.com`.
*/
private class ReturnValueAsRedirection extends ServerSideUrlRedirect::Sink {
NestJSRouteHandler handler;
ReturnValueAsRedirection() {
handler.hasRedirectDecorator() and
this = handler.getAReturn().getALocalSource().getAPropertyWrite("url").getRhs()
exists(NestJSRouteHandler handler |
handler.hasRedirectDecorator() and
this = handler.getAReturn().getALocalSource().getAPropertyWrite("url").getRhs()
)
}
}

View File

@@ -155,11 +155,7 @@ module NextJS {
* A Next.js function that is exected on the server for every request, seen as a routehandler.
*/
class NextHttpRouteHandler extends HTTP::Servers::StandardRouteHandler, DataFlow::FunctionNode {
Module pageModule;
NextHttpRouteHandler() {
this = getServerSidePropsFunction(pageModule) or this = getInitialProps(pageModule)
}
NextHttpRouteHandler() { this = getServerSidePropsFunction(_) or this = getInitialProps(_) }
}
/**

View File

@@ -1133,17 +1133,17 @@ module NodeJSLib {
* A connection opened on a NodeJS net server.
*/
private class NodeJSNetServerConnection extends EventEmitter::Range {
NodeJSNetServer server;
NodeJSNetServerConnection() {
exists(DataFlow::MethodCallNode call |
call = server.ref().getAMethodCall("on") and
call.getArgument(0).mayHaveStringValue("connection")
|
this = call.getCallback(1).getParameter(0)
exists(NodeJSNetServer server |
exists(DataFlow::MethodCallNode call |
call = server.ref().getAMethodCall("on") and
call.getArgument(0).mayHaveStringValue("connection")
|
this = call.getCallback(1).getParameter(0)
)
or
this = server.getCallback([0, 1]).getParameter(0)
)
or
this = server.getCallback([0, 1]).getParameter(0)
}
DataFlow::SourceNode ref() { result = EventEmitter::trackEventEmitter(this) }
@@ -1163,9 +1163,9 @@ module NodeJSLib {
* A data flow node representing data received from a client to a NodeJS net server, viewed as remote user input.
*/
private class NodeJSNetServerItemAsRemoteFlow extends RemoteFlowSource {
NodeJSNetServerRegistration reg;
NodeJSNetServerItemAsRemoteFlow() { this = reg.getReceivedItem(_) }
NodeJSNetServerItemAsRemoteFlow() {
this = any(NodeJSNetServerRegistration reg).getReceivedItem(_)
}
override string getSourceType() { result = "NodeJS server" }
}

View File

@@ -284,10 +284,8 @@ private class JQueryAttr3Call extends JQueryAttributeDefinition, @call_expr {
* the DOM element constructed by `$("<script/>")`.
*/
private class JQueryChainedElement extends DOM::Element, InvokeExpr {
DOM::Element inner;
JQueryChainedElement() {
exists(JQuery::MethodCall call | this = call.asExpr() |
exists(JQuery::MethodCall call, DOM::Element inner | this = call.asExpr() |
call.getReceiver().asExpr() = inner and
defn = inner.getDefinition()
)

View File

@@ -121,39 +121,37 @@ module HtmlSanitization {
/**
* An incomplete sanitizer for HTML-relevant characters.
*/
class IncompleteSanitizer extends IncompleteBlacklistSanitizer {
StringReplaceCallSequence chain;
class IncompleteSanitizer extends IncompleteBlacklistSanitizer instanceof StringReplaceCallSequence {
string unsanitized;
IncompleteSanitizer() {
this = chain and
fixedGlobalReplacement(chain) and
not getALikelyReplacedCharacter(chain) = unsanitized and
fixedGlobalReplacement(this) and
not getALikelyReplacedCharacter(this) = unsanitized and
(
// replaces `<` and `>`
getALikelyReplacedCharacter(chain) = "<" and
getALikelyReplacedCharacter(chain) = ">" and
getALikelyReplacedCharacter(this) = "<" and
getALikelyReplacedCharacter(this) = ">" and
unsanitized = ["\"", "'", "&"]
or
// replaces '&' and either `<` or `>`
getALikelyReplacedCharacter(chain) = "&" and
getALikelyReplacedCharacter(this) = "&" and
(
getALikelyReplacedCharacter(chain) = ">" and
getALikelyReplacedCharacter(this) = ">" and
unsanitized = ">"
or
getALikelyReplacedCharacter(chain) = "<" and
getALikelyReplacedCharacter(this) = "<" and
unsanitized = "<"
)
) and
// does not replace special characters that the browser doesn't care for
not chain.getAReplacedString() = ["!", "#", "*", "?", "@", "|", "~"] and
not super.getAReplacedString() = ["!", "#", "*", "?", "@", "|", "~"] and
/// only replaces explicit characters: exclude character ranges and negated character classes
not exists(RegExpTerm t | t = chain.getAMember().getRegExp().getRoot().getAChild*() |
not exists(RegExpTerm t | t = super.getAMember().getRegExp().getRoot().getAChild*() |
t.(RegExpCharacterClass).isInverted() or
t instanceof RegExpCharacterRange
) and
// the replacements are either empty or HTML entities
chain.getAReplacementString().regexpMatch("(?i)(|(&[#a-z0-9]+;))")
super.getAReplacementString().regexpMatch("(?i)(|(&[#a-z0-9]+;))")
}
override string getKind() { result = "HTML" }

View File

@@ -136,12 +136,13 @@ module InsecureDownload {
*/
class FileWriteSink extends Sink {
ClientRequest request;
FileSystemWriteAccess write;
FileWriteSink() {
this = request.getUrl() and
clientRequestResponse(DataFlow::TypeTracker::end(), request).flowsTo(write.getADataNode()) and
hasUnsafeExtension(write.getAPathArgument().getStringValue())
exists(FileSystemWriteAccess write |
this = request.getUrl() and
clientRequestResponse(DataFlow::TypeTracker::end(), request).flowsTo(write.getADataNode()) and
hasUnsafeExtension(write.getAPathArgument().getStringValue())
)
}
override DataFlow::FlowLabel getALabel() { result instanceof Label::InsecureURL }

View File

@@ -134,10 +134,8 @@ module LoopBoundInjection {
* such as `_.filter(sink, ...)`.
*/
private class LodashIterationSink extends Sink {
DataFlow::CallNode call;
LodashIterationSink() {
exists(string name |
exists(string name, DataFlow::CallNode call |
loopableLodashMethod(name) and
call = LodashUnderscore::member(name).getACall() and
call.getArgument(0) = this and

View File

@@ -89,21 +89,22 @@ module PrototypePollution {
}
class DeepExtendSink extends Sink {
ExtendCall call;
string moduleName;
Locatable location;
DeepExtendSink() {
this = call.getASourceOperand() and
(
exists(Dependency dep |
isVulnerableVersionOfDeepExtendCall(call, dep) and
dep = location and
dep.info(moduleName, _)
exists(ExtendCall call |
this = call.getASourceOperand() and
(
exists(Dependency dep |
isVulnerableVersionOfDeepExtendCall(call, dep) and
dep = location and
dep.info(moduleName, _)
)
or
isVulnerableDeepExtendCallAllVersions(call, moduleName) and
location = call.asExpr()
)
or
isVulnerableDeepExtendCallAllVersions(call, moduleName) and
location = call.asExpr()
)
}

View File

@@ -149,11 +149,11 @@ module UnsafeHtmlConstruction {
* A string parsed as XML, which is later used in an XSS sink.
*/
class XMLParsedSink extends XssSink {
XML::ParserInvocation parser;
XMLParsedSink() {
this.asExpr() = parser.getSourceArgument() and
isUsedInXssSink(xssSink) = parser.getAResult()
exists(XML::ParserInvocation parser |
this.asExpr() = parser.getSourceArgument() and
isUsedInXssSink(xssSink) = parser.getAResult()
)
}
override string describe() { result = "XML parsing" }

View File

@@ -109,19 +109,20 @@ module UnsafeShellCommandConstruction {
* An element pushed to an array, where the array is later used to execute a shell command.
*/
class ArrayAppendEndingInCommandExecutinSink extends Sink {
DataFlow::SourceNode array;
SystemCommandExecution sys;
ArrayAppendEndingInCommandExecutinSink() {
this =
[
array.(DataFlow::ArrayCreationNode).getAnElement(),
array.getAMethodCall(["push", "unshift"]).getAnArgument()
] and
exists(DataFlow::MethodCallNode joinCall | array.getAMethodCall("join") = joinCall |
joinCall = isExecutedAsShellCommand(DataFlow::TypeBackTracker::end(), sys) and
joinCall.getNumArgument() = 1 and
joinCall.getArgument(0).getStringValue() = " "
exists(DataFlow::SourceNode array |
this =
[
array.(DataFlow::ArrayCreationNode).getAnElement(),
array.getAMethodCall(["push", "unshift"]).getAnArgument()
] and
exists(DataFlow::MethodCallNode joinCall | array.getAMethodCall("join") = joinCall |
joinCall = isExecutedAsShellCommand(DataFlow::TypeBackTracker::end(), sys) and
joinCall.getNumArgument() = 1 and
joinCall.getArgument(0).getStringValue() = " "
)
)
}
@@ -136,14 +137,15 @@ module UnsafeShellCommandConstruction {
* A formatted string that is later executed as a shell command.
*/
class FormatedStringInCommandExecutionSink extends Sink {
PrintfStyleCall call;
SystemCommandExecution sys;
FormatedStringInCommandExecutionSink() {
this = call.getFormatArgument(_) and
call = isExecutedAsShellCommand(DataFlow::TypeBackTracker::end(), sys) and
exists(string formatString | call.getFormatString().mayHaveStringValue(formatString) |
formatString.regexpMatch(".* ('|\")?[0-9a-zA-Z/:_-]*%.*")
exists(PrintfStyleCall call |
this = call.getFormatArgument(_) and
call = isExecutedAsShellCommand(DataFlow::TypeBackTracker::end(), sys) and
exists(string formatString | call.getFormatString().mayHaveStringValue(formatString) |
formatString.regexpMatch(".* ('|\")?[0-9a-zA-Z/:_-]*%.*")
)
)
}
@@ -206,13 +208,14 @@ module UnsafeShellCommandConstruction {
* Joining a path is similar to string concatenation that automatically inserts slashes.
*/
class JoinedPathEndingInCommandExecutionSink extends Sink {
DataFlow::MethodCallNode joinCall;
SystemCommandExecution sys;
JoinedPathEndingInCommandExecutionSink() {
this = joinCall.getAnArgument() and
joinCall = DataFlow::moduleMember("path", ["resolve", "join"]).getACall() and
joinCall = isExecutedAsShellCommand(DataFlow::TypeBackTracker::end(), sys)
exists(DataFlow::MethodCallNode joinCall |
this = joinCall.getAnArgument() and
joinCall = DataFlow::moduleMember("path", ["resolve", "join"]).getACall() and
joinCall = isExecutedAsShellCommand(DataFlow::TypeBackTracker::end(), sys)
)
}
override string getSinkType() { result = "Path concatenation" }

View File

@@ -73,12 +73,12 @@ module UnvalidatedDynamicMethodCall {
* A function invocation of an unsafe function, as a sink for remote unvalidated dynamic method calls.
*/
class CalleeAsSink extends Sink {
InvokeExpr invk;
CalleeAsSink() {
this = invk.getCallee().flow() and
// don't flag invocations inside a try-catch
not invk.getASuccessor() instanceof CatchClause
exists(InvokeExpr invk |
this = invk.getCallee().flow() and
// don't flag invocations inside a try-catch
not invk.getASuccessor() instanceof CatchClause
)
}
override DataFlow::FlowLabel getFlowLabel() {

View File

@@ -17,11 +17,10 @@ class CandidateTopLevel extends TopLevel {
/** A string literal in a toplevel that contains at least one template literal. */
class CandidateStringLiteral extends StringLiteral {
CandidateTopLevel tl;
string v;
CandidateStringLiteral() {
tl = this.getTopLevel() and
this.getTopLevel() instanceof CandidateTopLevel and
v = this.getStringValue()
}

View File

@@ -123,12 +123,13 @@ predicate isDerivedFromLength(DataFlow::Node length, DataFlow::Node operand) {
*/
class UnsafeIndexOfComparison extends EqualityTest {
IndexOfCall indexOf;
DataFlow::Node testedValue;
UnsafeIndexOfComparison() {
this.hasOperands(indexOf.getAUse(), testedValue.asExpr()) and
isDerivedFromLength(testedValue, indexOf.getReceiver()) and
isDerivedFromLength(testedValue, indexOf.getArgument(0)) and
exists(DataFlow::Node testedValue |
this.hasOperands(indexOf.getAUse(), testedValue.asExpr()) and
isDerivedFromLength(testedValue, indexOf.getReceiver()) and
isDerivedFromLength(testedValue, indexOf.getArgument(0))
) and
// Ignore cases like `x.indexOf("/") === x.length - 1` that can only be bypassed if `x` is the empty string.
// Sometimes strings are just known to be non-empty from the context, and it is unlikely to be a security issue,
// since it's obviously not a domain name check.

View File

@@ -35,13 +35,14 @@ external predicate additionalSteps(
* An additional source specified through the `additionalSources` predicate.
*/
private class AdditionalSourceFromSpec extends DataFlow::AdditionalSource {
Portal portal;
string flowLabel;
string config;
AdditionalSourceFromSpec() {
additionalSources(portal.toString(), flowLabel, config) and
this = portal.getAnExitNode(_)
exists(Portal portal |
additionalSources(portal.toString(), flowLabel, config) and
this = portal.getAnExitNode(_)
)
}
override predicate isSourceFor(DataFlow::Configuration cfg, DataFlow::FlowLabel lbl) {
@@ -53,13 +54,14 @@ private class AdditionalSourceFromSpec extends DataFlow::AdditionalSource {
* An additional sink specified through the `additionalSinks` predicate.
*/
private class AdditionalSinkFromSpec extends DataFlow::AdditionalSink {
Portal portal;
string flowLabel;
string config;
AdditionalSinkFromSpec() {
additionalSinks(portal.toString(), flowLabel, config) and
this = portal.getAnEntryNode(_)
exists(Portal portal |
additionalSinks(portal.toString(), flowLabel, config) and
this = portal.getAnEntryNode(_)
)
}
override predicate isSinkFor(DataFlow::Configuration cfg, DataFlow::FlowLabel lbl) {

View File

@@ -2,14 +2,11 @@ import javascript
import semmle.javascript.dataflow.InferredTypes
import semmle.javascript.dataflow.CustomAbstractValueDefinitions
class MyCustomAbstractValueDefinition extends CustomAbstractValueDefinition {
DataFlow::ValueNode node;
class MyCustomAbstractValueDefinition extends CustomAbstractValueDefinition, AST::ValueNode {
MyCustomAbstractValueDefinition() {
DataFlow::valueNode(this) = node and
node instanceof DataFlow::ObjectLiteralNode and
this.flow() instanceof DataFlow::ObjectLiteralNode and
exists(DataFlow::PropWrite pwn |
pwn.writes(node, "custom", any(BooleanLiteral l | l.getValue() = "true").flow())
pwn.writes(this.flow(), "custom", any(BooleanLiteral l | l.getValue() = "true").flow())
)
}

View File

@@ -11,17 +11,17 @@ abstract class Violation extends ASTNode {
* The assertion holds if `name1 = name2`, indicating that `X` resolved to the right interface.
*/
class TypeResolutionAssertion extends TupleTypeExpr, Violation {
InterfaceDeclaration interface;
LocalTypeAccess typeAccess;
string expected;
string actual;
TypeResolutionAssertion() {
typeAccess = getElementType(0) and
expected = getElementType(1).(StringLiteralTypeExpr).getValue() and
typeAccess.getLocalTypeName() = interface.getIdentifier().(TypeDecl).getLocalTypeName() and
actual = interface.getField("where").getTypeAnnotation().(StringLiteralTypeExpr).getValue() and
actual != expected
exists(InterfaceDeclaration interface, LocalTypeAccess typeAccess |
typeAccess = getElementType(0) and
expected = getElementType(1).(StringLiteralTypeExpr).getValue() and
typeAccess.getLocalTypeName() = interface.getIdentifier().(TypeDecl).getLocalTypeName() and
actual = interface.getField("where").getTypeAnnotation().(StringLiteralTypeExpr).getValue() and
actual != expected
)
}
override string reason() {

View File

@@ -23,9 +23,7 @@ class ApiObject extends DataFlow::NewNode {
}
class Connection extends DataFlow::SourceNode {
ApiObject api;
Connection() { this = api.ref().getAMethodCall("createConnection") }
Connection() { this = any(ApiObject api).ref().getAMethodCall("createConnection") }
DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
t.start() and
@@ -49,9 +47,7 @@ class Connection extends DataFlow::SourceNode {
}
class DataValue extends DataFlow::SourceNode {
Connection connection;
DataValue() { this = connection.getACallback().getParameter(0) }
DataValue() { this = any(Connection connection).getACallback().getParameter(0) }
DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
t.start() and