mirror of
https://github.com/github/codeql.git
synced 2026-04-30 11:15:13 +02:00
JS: fix ql/field-only-used-in-charpred within JavaScript
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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() }
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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() }
|
||||
|
||||
|
||||
@@ -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() }
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(_) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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" }
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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())
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user