diff --git a/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/ATMConfig.qll b/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/ATMConfig.qll index bce5a3172d6c..469945550bc7 100644 --- a/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/ATMConfig.qll +++ b/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/ATMConfig.qll @@ -7,6 +7,7 @@ private import javascript as JS import EndpointTypes import EndpointCharacteristics as EndpointCharacteristics +import AdaptiveThreatModeling::ATM::ResultsInfo as AtmResultsInfo /** * EXPERIMENTAL. This API may change in the future. @@ -29,10 +30,23 @@ import EndpointCharacteristics as EndpointCharacteristics * `isAdditionalFlowStep` with a more generalised definition of additional edges. See * `NosqlInjectionATM.qll` for an example of doing this. */ -abstract class AtmConfig extends string { +abstract class AtmConfig extends JS::TaintTracking::Configuration { bindingset[this] AtmConfig() { any() } + /** + * Holds if `source` is a relevant taint source. When sources are not boosted, `isSource` is equivalent to + * `isKnownSource` (i.e there are no "effective" sources to be classified by an ML model). + */ + override predicate isSource(JS::DataFlow::Node source) { this.isKnownSource(source) } + + /** + * Holds if `sink` is a known taint sink or an "effective" sink (a candidate to be classified by an ML model). + */ + override predicate isSink(JS::DataFlow::Node sink) { + this.isKnownSink(sink) or this.isEffectiveSink(sink) + } + /** * EXPERIMENTAL. This API may change in the future. * @@ -117,6 +131,17 @@ abstract class AtmConfig extends string { * A cut-off value of 1 produces all alerts including those that are likely false-positives. */ float getScoreCutoff() { result = 0.0 } + + /** + * Holds if there's an ATM alert (a flow path from `source` to `sink` with ML-determined likelihood `score`) according + * to this ML-boosted configuration, whereas the unboosted base query is unlikely to report an alert for this source + * and sink. + */ + predicate getAlerts(JS::DataFlow::PathNode source, JS::DataFlow::PathNode sink, float score) { + this.hasFlowPath(source, sink) and + not AtmResultsInfo::isFlowLikelyInBaseQuery(source.getNode(), sink.getNode()) and + score = AtmResultsInfo::getScoreForFlow(source.getNode(), sink.getNode()) + } } /** DEPRECATED: Alias for AtmConfig */ diff --git a/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/CoreKnowledge.qll b/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/CoreKnowledge.qll deleted file mode 100644 index e569fb3dc960..000000000000 --- a/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/CoreKnowledge.qll +++ /dev/null @@ -1,225 +0,0 @@ -/* - * For internal use only. - * - * Provides predicates that expose the knowledge of models - * in the core CodeQL JavaScript libraries. - */ - -private import javascript -private import semmle.javascript.security.dataflow.XxeCustomizations -private import semmle.javascript.security.dataflow.RemotePropertyInjectionCustomizations -private import semmle.javascript.security.dataflow.TypeConfusionThroughParameterTamperingCustomizations -private import semmle.javascript.security.dataflow.ZipSlipCustomizations -private import semmle.javascript.security.dataflow.TaintedPathCustomizations -private import semmle.javascript.security.dataflow.CleartextLoggingCustomizations -private import semmle.javascript.security.dataflow.XpathInjectionCustomizations -private import semmle.javascript.security.dataflow.Xss::Shared as Xss -private import semmle.javascript.security.dataflow.StackTraceExposureCustomizations -private import semmle.javascript.security.dataflow.ClientSideUrlRedirectCustomizations -private import semmle.javascript.security.dataflow.CodeInjectionCustomizations -private import semmle.javascript.security.dataflow.RequestForgeryCustomizations -private import semmle.javascript.security.dataflow.CorsMisconfigurationForCredentialsCustomizations -private import semmle.javascript.security.dataflow.ShellCommandInjectionFromEnvironmentCustomizations -private import semmle.javascript.security.dataflow.DifferentKindsComparisonBypassCustomizations -private import semmle.javascript.security.dataflow.CommandInjectionCustomizations -private import semmle.javascript.security.dataflow.PrototypePollutionCustomizations -private import semmle.javascript.security.dataflow.UnvalidatedDynamicMethodCallCustomizations -private import semmle.javascript.security.dataflow.TaintedFormatStringCustomizations -private import semmle.javascript.security.dataflow.NosqlInjectionCustomizations -private import semmle.javascript.security.dataflow.PostMessageStarCustomizations -private import semmle.javascript.security.dataflow.RegExpInjectionCustomizations -private import semmle.javascript.security.dataflow.SqlInjectionCustomizations -private import semmle.javascript.security.dataflow.InsecureRandomnessCustomizations -private import semmle.javascript.security.dataflow.XmlBombCustomizations -private import semmle.javascript.security.dataflow.InsufficientPasswordHashCustomizations -private import semmle.javascript.security.dataflow.HardcodedCredentialsCustomizations -private import semmle.javascript.security.dataflow.FileAccessToHttpCustomizations -private import semmle.javascript.security.dataflow.UnsafeDynamicMethodAccessCustomizations -private import semmle.javascript.security.dataflow.UnsafeDeserializationCustomizations -private import semmle.javascript.security.dataflow.HardcodedDataInterpretedAsCodeCustomizations -private import semmle.javascript.security.dataflow.ServerSideUrlRedirectCustomizations -private import semmle.javascript.security.dataflow.IndirectCommandInjectionCustomizations -private import semmle.javascript.security.dataflow.ConditionalBypassCustomizations -private import semmle.javascript.security.dataflow.HttpToFileAccessCustomizations -private import semmle.javascript.security.dataflow.BrokenCryptoAlgorithmCustomizations -private import semmle.javascript.security.dataflow.LoopBoundInjectionCustomizations -private import semmle.javascript.security.dataflow.CleartextStorageCustomizations -import FilteringReasons - -/** - * Holds if the node `n` is a known sink in a modeled library, or a sibling-argument of such a sink. - */ -predicate isArgumentToKnownLibrarySinkFunction(DataFlow::Node n) { - exists(DataFlow::InvokeNode invk, DataFlow::Node known | - invk.getAnArgument() = n and invk.getAnArgument() = known and isKnownLibrarySink(known) - ) -} - -/** - * Holds if the node `n` is a known sink for the external API security query. - * - * This corresponds to known sinks from security queries whose sources include remote flow and - * DOM-based sources. - */ -predicate isKnownExternalApiQuerySink(DataFlow::Node n) { - n instanceof Xxe::Sink or - n instanceof TaintedPath::Sink or - n instanceof XpathInjection::Sink or - n instanceof Xss::Sink or - n instanceof ClientSideUrlRedirect::Sink or - n instanceof CodeInjection::Sink or - n instanceof RequestForgery::Sink or - n instanceof CorsMisconfigurationForCredentials::Sink or - n instanceof CommandInjection::Sink or - n instanceof PrototypePollution::Sink or - n instanceof UnvalidatedDynamicMethodCall::Sink or - n instanceof TaintedFormatString::Sink or - n instanceof NosqlInjection::Sink or - n instanceof PostMessageStar::Sink or - n instanceof RegExpInjection::Sink or - n instanceof SqlInjection::Sink or - n instanceof XmlBomb::Sink or - n instanceof ZipSlip::Sink or - n instanceof UnsafeDeserialization::Sink or - n instanceof ServerSideUrlRedirect::Sink or - n instanceof CleartextStorage::Sink or - n instanceof HttpToFileAccess::Sink -} - -/** DEPRECATED: Alias for isKnownExternalApiQuerySink */ -deprecated predicate isKnownExternalAPIQuerySink = isKnownExternalApiQuerySink/1; - -/** - * Holds if the node `n` is a known sink in a modeled library. - */ -predicate isKnownLibrarySink(DataFlow::Node n) { - isKnownExternalApiQuerySink(n) or - n instanceof CleartextLogging::Sink or - n instanceof StackTraceExposure::Sink or - n instanceof ShellCommandInjectionFromEnvironment::Sink or - n instanceof InsecureRandomness::Sink or - n instanceof FileAccessToHttp::Sink or - n instanceof IndirectCommandInjection::Sink -} - -/** - * Holds if the node `n` is known as the predecessor in a modeled flow step. - */ -predicate isKnownStepSrc(DataFlow::Node n) { - TaintTracking::sharedTaintStep(n, _) or - DataFlow::SharedFlowStep::step(n, _) or - DataFlow::SharedFlowStep::step(n, _, _, _) -} - -/** - * Holds if `n` is an argument to a function of a builtin object. - */ -private predicate isArgumentToBuiltinFunction(DataFlow::Node n, FilteringReason reason) { - exists(DataFlow::SourceNode builtin, DataFlow::SourceNode receiver, DataFlow::InvokeNode invk | - ( - builtin instanceof DataFlow::ArrayCreationNode and - reason instanceof ArgumentToArrayReason - or - builtin = - DataFlow::globalVarRef([ - "Map", "Set", "WeakMap", "WeakSet", "Number", "Object", "String", "Array", "Error", - "Math", "Boolean" - ]) and - reason instanceof ArgumentToBuiltinGlobalVarRefReason - ) - | - receiver = [builtin.getAnInvocation(), builtin] and - invk = [receiver, receiver.getAPropertyRead()].getAnInvocation() and - invk.getAnArgument() = n - ) - or - exists(Expr primitive, MethodCallExpr c | - primitive instanceof ConstantString or - primitive instanceof NumberLiteral or - primitive instanceof BooleanLiteral - | - c.calls(primitive, _) and - c.getAnArgument() = n.asExpr() and - reason instanceof ConstantReceiverReason - ) - or - exists(DataFlow::CallNode call | - call.getAnArgument() = n and - call.getCalleeName() = - [ - "indexOf", "hasOwnProperty", "substring", "isDecimal", "decode", "encode", "keys", "shift", - "values", "forEach", "toString", "slice", "splice", "push", "isArray", "sort" - ] and - reason instanceof BuiltinCallNameReason - ) -} - -predicate isOtherModeledArgument(DataFlow::Node n, FilteringReason reason) { - isArgumentToBuiltinFunction(n, reason) - or - any(LodashUnderscore::Member m).getACall().getAnArgument() = n and - reason instanceof LodashUnderscoreArgumentReason - or - any(JQuery::MethodCall m).getAnArgument() = n and - reason instanceof JQueryArgumentReason - or - exists(ClientRequest r | - r.getAnArgument() = n or n = r.getUrl() or n = r.getHost() or n = r.getADataNode() - ) and - reason instanceof ClientRequestReason - or - exists(PromiseDefinition p | - n = [p.getResolveParameter(), p.getRejectParameter()].getACall().getAnArgument() - ) and - reason instanceof PromiseDefinitionReason - or - n instanceof CryptographicKey and reason instanceof CryptographicKeyReason - or - any(CryptographicOperation op).getInput() = n and - reason instanceof CryptographicOperationFlowReason - or - exists(DataFlow::CallNode call | n = call.getAnArgument() | - call.getCalleeName() = getAStandardLoggerMethodName() and - reason instanceof LoggerMethodReason - or - call.getCalleeName() = ["setTimeout", "clearTimeout"] and - reason instanceof TimeoutReason - or - call.getReceiver() = DataFlow::globalVarRef(["localStorage", "sessionStorage"]) and - reason instanceof ReceiverStorageReason - or - call instanceof StringOps::StartsWith and reason instanceof StringStartsWithReason - or - call instanceof StringOps::EndsWith and reason instanceof StringEndsWithReason - or - call instanceof StringOps::RegExpTest and reason instanceof StringRegExpTestReason - or - call instanceof EventRegistration and reason instanceof EventRegistrationReason - or - call instanceof EventDispatch and reason instanceof EventDispatchReason - or - call = any(MembershipCandidate c).getTest() and - reason instanceof MembershipCandidateTestReason - or - call instanceof FileSystemAccess and reason instanceof FileSystemAccessReason - or - // TODO database accesses are less well defined than database query sinks, so this may cover unmodeled sinks on existing database models - [ - call, call.getAMethodCall() - /* command pattern where the query is built, and then exec'ed later */ ] instanceof - DatabaseAccess and - reason instanceof DatabaseAccessReason - or - call = DOM::domValueRef() and reason instanceof DomReason - or - call.getCalleeName() = "next" and - exists(DataFlow::FunctionNode f | call = f.getLastParameter().getACall()) and - reason instanceof NextFunctionCallReason - or - call = DataFlow::globalVarRef("dojo").getAPropertyRead("require").getACall() and - reason instanceof DojoRequireReason - ) - or - (exists(Base64::Decode d | n = d.getInput()) or exists(Base64::Encode d | n = d.getInput())) and - reason instanceof Base64ManipulationReason -} diff --git a/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/EndpointCharacteristics.qll b/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/EndpointCharacteristics.qll index 0f899d795d9c..e40db8ecbfb7 100644 --- a/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/EndpointCharacteristics.qll +++ b/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/EndpointCharacteristics.qll @@ -7,10 +7,46 @@ private import semmle.javascript.security.dataflow.SqlInjectionCustomizations private import semmle.javascript.security.dataflow.DomBasedXssCustomizations private import semmle.javascript.security.dataflow.NosqlInjectionCustomizations private import semmle.javascript.security.dataflow.TaintedPathCustomizations -private import CoreKnowledge as CoreKnowledge private import semmle.javascript.heuristics.SyntacticHeuristics private import semmle.javascript.filters.ClassifyFiles as ClassifyFiles -private import StandardEndpointFilters as StandardEndpointFilters +private import semmle.javascript.security.dataflow.XxeCustomizations +private import semmle.javascript.security.dataflow.RemotePropertyInjectionCustomizations +private import semmle.javascript.security.dataflow.TypeConfusionThroughParameterTamperingCustomizations +private import semmle.javascript.security.dataflow.ZipSlipCustomizations +private import semmle.javascript.security.dataflow.TaintedPathCustomizations +private import semmle.javascript.security.dataflow.CleartextLoggingCustomizations +private import semmle.javascript.security.dataflow.XpathInjectionCustomizations +private import semmle.javascript.security.dataflow.Xss::Shared as Xss +private import semmle.javascript.security.dataflow.StackTraceExposureCustomizations +private import semmle.javascript.security.dataflow.ClientSideUrlRedirectCustomizations +private import semmle.javascript.security.dataflow.CodeInjectionCustomizations +private import semmle.javascript.security.dataflow.RequestForgeryCustomizations +private import semmle.javascript.security.dataflow.CorsMisconfigurationForCredentialsCustomizations +private import semmle.javascript.security.dataflow.ShellCommandInjectionFromEnvironmentCustomizations +private import semmle.javascript.security.dataflow.DifferentKindsComparisonBypassCustomizations +private import semmle.javascript.security.dataflow.CommandInjectionCustomizations +private import semmle.javascript.security.dataflow.PrototypePollutionCustomizations +private import semmle.javascript.security.dataflow.UnvalidatedDynamicMethodCallCustomizations +private import semmle.javascript.security.dataflow.TaintedFormatStringCustomizations +private import semmle.javascript.security.dataflow.NosqlInjectionCustomizations +private import semmle.javascript.security.dataflow.PostMessageStarCustomizations +private import semmle.javascript.security.dataflow.RegExpInjectionCustomizations +private import semmle.javascript.security.dataflow.SqlInjectionCustomizations +private import semmle.javascript.security.dataflow.InsecureRandomnessCustomizations +private import semmle.javascript.security.dataflow.XmlBombCustomizations +private import semmle.javascript.security.dataflow.InsufficientPasswordHashCustomizations +private import semmle.javascript.security.dataflow.HardcodedCredentialsCustomizations +private import semmle.javascript.security.dataflow.FileAccessToHttpCustomizations +private import semmle.javascript.security.dataflow.UnsafeDynamicMethodAccessCustomizations +private import semmle.javascript.security.dataflow.UnsafeDeserializationCustomizations +private import semmle.javascript.security.dataflow.HardcodedDataInterpretedAsCodeCustomizations +private import semmle.javascript.security.dataflow.ServerSideUrlRedirectCustomizations +private import semmle.javascript.security.dataflow.IndirectCommandInjectionCustomizations +private import semmle.javascript.security.dataflow.ConditionalBypassCustomizations +private import semmle.javascript.security.dataflow.HttpToFileAccessCustomizations +private import semmle.javascript.security.dataflow.BrokenCryptoAlgorithmCustomizations +private import semmle.javascript.security.dataflow.LoopBoundInjectionCustomizations +private import semmle.javascript.security.dataflow.CleartextStorageCustomizations /** * A set of characteristics that a particular endpoint might have. This set of characteristics is used to make decisions @@ -61,6 +97,110 @@ abstract class EndpointCharacteristic extends string { final float mediumConfidence() { result = 0.6 } } +/* + * Helper predicates. + */ + +/** + * Holds if the node `n` is a known sink for the external API security query. + * + * This corresponds to known sinks from security queries whose sources include remote flow and + * DOM-based sources. + */ +private predicate isKnownExternalApiQuerySink(DataFlow::Node n) { + n instanceof Xxe::Sink or + n instanceof TaintedPath::Sink or + n instanceof XpathInjection::Sink or + n instanceof Xss::Sink or + n instanceof ClientSideUrlRedirect::Sink or + n instanceof CodeInjection::Sink or + n instanceof RequestForgery::Sink or + n instanceof CorsMisconfigurationForCredentials::Sink or + n instanceof CommandInjection::Sink or + n instanceof PrototypePollution::Sink or + n instanceof UnvalidatedDynamicMethodCall::Sink or + n instanceof TaintedFormatString::Sink or + n instanceof NosqlInjection::Sink or + n instanceof PostMessageStar::Sink or + n instanceof RegExpInjection::Sink or + n instanceof SqlInjection::Sink or + n instanceof XmlBomb::Sink or + n instanceof ZipSlip::Sink or + n instanceof UnsafeDeserialization::Sink or + n instanceof ServerSideUrlRedirect::Sink or + n instanceof CleartextStorage::Sink or + n instanceof HttpToFileAccess::Sink +} + +/** + * Holds if the node `n` is a known sink in a modeled library. + */ +private predicate isKnownLibrarySink(DataFlow::Node n) { + isKnownExternalApiQuerySink(n) or + n instanceof CleartextLogging::Sink or + n instanceof StackTraceExposure::Sink or + n instanceof ShellCommandInjectionFromEnvironment::Sink or + n instanceof InsecureRandomness::Sink or + n instanceof FileAccessToHttp::Sink or + n instanceof IndirectCommandInjection::Sink +} + +/** + * Holds if the node `n` is known as the predecessor in a modeled flow step. + */ +private predicate isKnownStepSrc(DataFlow::Node n) { + TaintTracking::sharedTaintStep(n, _) or + DataFlow::SharedFlowStep::step(n, _) or + DataFlow::SharedFlowStep::step(n, _, _, _) +} + +/** + * Holds if the data flow node is a (possibly indirect) argument of a likely external library call. + * + * This includes direct arguments of likely external library calls as well as nested object + * literals within those calls. + */ +private predicate flowsToArgumentOfLikelyExternalLibraryCall(DataFlow::Node n) { + n = getACallWithoutCallee().getAnArgument() + or + exists(DataFlow::SourceNode src | flowsToArgumentOfLikelyExternalLibraryCall(src) | + n = src.getAPropertyWrite().getRhs() + ) + or + exists(DataFlow::ArrayCreationNode arr | flowsToArgumentOfLikelyExternalLibraryCall(arr) | + n = arr.getAnElement() + ) +} + +/** + * Get calls for which we do not have the callee (i.e. the definition of the called function). This + * acts as a heuristic for identifying calls to external library functions. + */ +private DataFlow::CallNode getACallWithoutCallee() { + forall(Function callee | callee = result.getACallee() | callee.getTopLevel().isExterns()) and + not exists(DataFlow::ParameterNode param, DataFlow::FunctionNode callback | + param.flowsTo(result.getCalleeNode()) and + callback = getACallback(param, DataFlow::TypeBackTracker::end()) + ) +} + +/** + * Gets a node that flows to callback-parameter `p`. + */ +private DataFlow::SourceNode getACallback(DataFlow::ParameterNode p, DataFlow::TypeBackTracker t) { + t.start() and + result = p and + any(DataFlow::FunctionNode f).getLastParameter() = p and + exists(p.getACall()) + or + exists(DataFlow::TypeBackTracker t2 | result = getACallback(p, t2).backtrack(t2, t)) +} + +/** + * Get calls which are likely to be to external non-built-in libraries. + */ +DataFlow::CallNode getALikelyExternalLibraryCall() { result = getACallWithoutCallee() } + /* * Characteristics that are indicative of a sink. * NOTE: Initially each sink type has only one characteristic, which is that it's a sink of this type in the standard @@ -143,11 +283,19 @@ private class NosqlInjectionSinkCharacteristic extends EndpointCharacteristic { * negative samples for training. */ +/** + * A characteristic that is an indicator of not being a sink of any type, because it's a modeled argument. + */ +abstract class OtherModeledArgumentCharacteristic extends EndpointCharacteristic { + bindingset[this] + OtherModeledArgumentCharacteristic() { any() } +} + /** * A characteristic that is an indicator of not being a sink of any type, because it's an argument to a function of a * builtin object. */ -abstract private class ArgumentToBuiltinFunctionCharacteristic extends EndpointCharacteristic { +abstract private class ArgumentToBuiltinFunctionCharacteristic extends OtherModeledArgumentCharacteristic { bindingset[this] ArgumentToBuiltinFunctionCharacteristic() { any() } } @@ -187,15 +335,17 @@ abstract class LikelyNotASinkCharacteristic extends EndpointCharacteristic { } } -private class LodashUnderscore extends NotASinkCharacteristic { - LodashUnderscore() { this = "LodashUnderscoreArgument" } +private class LodashUnderscoreCharacteristic extends NotASinkCharacteristic, + OtherModeledArgumentCharacteristic { + LodashUnderscoreCharacteristic() { this = "LodashUnderscoreArgument" } override predicate getEndpoints(DataFlow::Node n) { any(LodashUnderscore::Member m).getACall().getAnArgument() = n } } -private class JQueryArgumentCharacteristic extends NotASinkCharacteristic { +private class JQueryArgumentCharacteristic extends NotASinkCharacteristic, + OtherModeledArgumentCharacteristic { JQueryArgumentCharacteristic() { this = "JQueryArgument" } override predicate getEndpoints(DataFlow::Node n) { @@ -203,7 +353,8 @@ private class JQueryArgumentCharacteristic extends NotASinkCharacteristic { } } -private class ClientRequestCharacteristic extends NotASinkCharacteristic { +private class ClientRequestCharacteristic extends NotASinkCharacteristic, + OtherModeledArgumentCharacteristic { ClientRequestCharacteristic() { this = "ClientRequest" } override predicate getEndpoints(DataFlow::Node n) { @@ -213,7 +364,8 @@ private class ClientRequestCharacteristic extends NotASinkCharacteristic { } } -private class PromiseDefinitionCharacteristic extends NotASinkCharacteristic { +private class PromiseDefinitionCharacteristic extends NotASinkCharacteristic, + OtherModeledArgumentCharacteristic { PromiseDefinitionCharacteristic() { this = "PromiseDefinition" } override predicate getEndpoints(DataFlow::Node n) { @@ -223,13 +375,15 @@ private class PromiseDefinitionCharacteristic extends NotASinkCharacteristic { } } -private class CryptographicKeyCharacteristic extends NotASinkCharacteristic { +private class CryptographicKeyCharacteristic extends NotASinkCharacteristic, + OtherModeledArgumentCharacteristic { CryptographicKeyCharacteristic() { this = "CryptographicKey" } override predicate getEndpoints(DataFlow::Node n) { n instanceof CryptographicKey } } -private class CryptographicOperationFlowCharacteristic extends NotASinkCharacteristic { +private class CryptographicOperationFlowCharacteristic extends NotASinkCharacteristic, + OtherModeledArgumentCharacteristic { CryptographicOperationFlowCharacteristic() { this = "CryptographicOperationFlow" } override predicate getEndpoints(DataFlow::Node n) { @@ -237,7 +391,8 @@ private class CryptographicOperationFlowCharacteristic extends NotASinkCharacter } } -private class LoggerMethodCharacteristic extends NotASinkCharacteristic { +private class LoggerMethodCharacteristic extends NotASinkCharacteristic, + OtherModeledArgumentCharacteristic { LoggerMethodCharacteristic() { this = "LoggerMethod" } override predicate getEndpoints(DataFlow::Node n) { @@ -247,7 +402,8 @@ private class LoggerMethodCharacteristic extends NotASinkCharacteristic { } } -private class TimeoutCharacteristic extends NotASinkCharacteristic { +private class TimeoutCharacteristic extends NotASinkCharacteristic, + OtherModeledArgumentCharacteristic { TimeoutCharacteristic() { this = "Timeout" } override predicate getEndpoints(DataFlow::Node n) { @@ -257,7 +413,8 @@ private class TimeoutCharacteristic extends NotASinkCharacteristic { } } -private class ReceiverStorageCharacteristic extends NotASinkCharacteristic { +private class ReceiverStorageCharacteristic extends NotASinkCharacteristic, + OtherModeledArgumentCharacteristic { ReceiverStorageCharacteristic() { this = "ReceiverStorage" } override predicate getEndpoints(DataFlow::Node n) { @@ -267,7 +424,8 @@ private class ReceiverStorageCharacteristic extends NotASinkCharacteristic { } } -private class StringStartsWithCharacteristic extends NotASinkCharacteristic { +private class StringStartsWithCharacteristic extends NotASinkCharacteristic, + OtherModeledArgumentCharacteristic { StringStartsWithCharacteristic() { this = "StringStartsWith" } override predicate getEndpoints(DataFlow::Node n) { @@ -277,7 +435,8 @@ private class StringStartsWithCharacteristic extends NotASinkCharacteristic { } } -private class StringEndsWithCharacteristic extends NotASinkCharacteristic { +private class StringEndsWithCharacteristic extends NotASinkCharacteristic, + OtherModeledArgumentCharacteristic { StringEndsWithCharacteristic() { this = "StringEndsWith" } override predicate getEndpoints(DataFlow::Node n) { @@ -285,7 +444,8 @@ private class StringEndsWithCharacteristic extends NotASinkCharacteristic { } } -private class StringRegExpTestCharacteristic extends NotASinkCharacteristic { +private class StringRegExpTestCharacteristic extends NotASinkCharacteristic, + OtherModeledArgumentCharacteristic { StringRegExpTestCharacteristic() { this = "StringRegExpTest" } override predicate getEndpoints(DataFlow::Node n) { @@ -295,7 +455,8 @@ private class StringRegExpTestCharacteristic extends NotASinkCharacteristic { } } -private class EventRegistrationCharacteristic extends NotASinkCharacteristic { +private class EventRegistrationCharacteristic extends NotASinkCharacteristic, + OtherModeledArgumentCharacteristic { EventRegistrationCharacteristic() { this = "EventRegistration" } override predicate getEndpoints(DataFlow::Node n) { @@ -303,7 +464,8 @@ private class EventRegistrationCharacteristic extends NotASinkCharacteristic { } } -private class EventDispatchCharacteristic extends NotASinkCharacteristic { +private class EventDispatchCharacteristic extends NotASinkCharacteristic, + OtherModeledArgumentCharacteristic { EventDispatchCharacteristic() { this = "EventDispatch" } override predicate getEndpoints(DataFlow::Node n) { @@ -311,7 +473,8 @@ private class EventDispatchCharacteristic extends NotASinkCharacteristic { } } -private class MembershipCandidateTestCharacteristic extends NotASinkCharacteristic { +private class MembershipCandidateTestCharacteristic extends NotASinkCharacteristic, + OtherModeledArgumentCharacteristic { MembershipCandidateTestCharacteristic() { this = "MembershipCandidateTest" } override predicate getEndpoints(DataFlow::Node n) { @@ -321,7 +484,8 @@ private class MembershipCandidateTestCharacteristic extends NotASinkCharacterist } } -private class FileSystemAccessCharacteristic extends NotASinkCharacteristic { +private class FileSystemAccessCharacteristic extends NotASinkCharacteristic, + OtherModeledArgumentCharacteristic { FileSystemAccessCharacteristic() { this = "FileSystemAccess" } override predicate getEndpoints(DataFlow::Node n) { @@ -329,7 +493,8 @@ private class FileSystemAccessCharacteristic extends NotASinkCharacteristic { } } -private class DatabaseAccessCharacteristic extends NotASinkCharacteristic { +private class DatabaseAccessCharacteristic extends NotASinkCharacteristic, + OtherModeledArgumentCharacteristic { DatabaseAccessCharacteristic() { this = "DatabaseAccess" } override predicate getEndpoints(DataFlow::Node n) { @@ -344,7 +509,7 @@ private class DatabaseAccessCharacteristic extends NotASinkCharacteristic { } } -private class DomCharacteristic extends NotASinkCharacteristic { +private class DomCharacteristic extends NotASinkCharacteristic, OtherModeledArgumentCharacteristic { DomCharacteristic() { this = "DOM" } override predicate getEndpoints(DataFlow::Node n) { @@ -352,7 +517,8 @@ private class DomCharacteristic extends NotASinkCharacteristic { } } -private class NextFunctionCallCharacteristic extends NotASinkCharacteristic { +private class NextFunctionCallCharacteristic extends NotASinkCharacteristic, + OtherModeledArgumentCharacteristic { NextFunctionCallCharacteristic() { this = "NextFunctionCall" } override predicate getEndpoints(DataFlow::Node n) { @@ -363,7 +529,8 @@ private class NextFunctionCallCharacteristic extends NotASinkCharacteristic { } } -private class DojoRequireCharacteristic extends NotASinkCharacteristic { +private class DojoRequireCharacteristic extends NotASinkCharacteristic, + OtherModeledArgumentCharacteristic { DojoRequireCharacteristic() { this = "DojoRequire" } override predicate getEndpoints(DataFlow::Node n) { @@ -373,7 +540,8 @@ private class DojoRequireCharacteristic extends NotASinkCharacteristic { } } -private class Base64ManipulationCharacteristic extends NotASinkCharacteristic { +private class Base64ManipulationCharacteristic extends NotASinkCharacteristic, + OtherModeledArgumentCharacteristic { Base64ManipulationCharacteristic() { this = "Base64Manipulation" } override predicate getEndpoints(DataFlow::Node n) { @@ -460,7 +628,6 @@ abstract class EndpointFilterCharacteristic extends EndpointCharacteristic { /** * An EndpointFilterCharacteristic that indicates that an endpoint is unlikely to be a sink of any type. - * Replaces https://github.com/github/codeql/blob/387e57546bf7352f7c1cfe781daa1a3799b7063e/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/StandardEndpointFilters.qll#LL15C24-L15C24 */ abstract class StandardEndpointFilterCharacteristic extends EndpointFilterCharacteristic { bindingset[this] @@ -475,7 +642,7 @@ abstract class StandardEndpointFilterCharacteristic extends EndpointFilterCharac } } -private class IsArgumentToModeledFunctionCharacteristic extends StandardEndpointFilterCharacteristic { +class IsArgumentToModeledFunctionCharacteristic extends StandardEndpointFilterCharacteristic { IsArgumentToModeledFunctionCharacteristic() { this = "argument to modeled function" } override predicate getEndpoints(DataFlow::Node n) { @@ -483,11 +650,13 @@ private class IsArgumentToModeledFunctionCharacteristic extends StandardEndpoint invk.getAnArgument() = n and invk.getAnArgument() = known and ( - CoreKnowledge::isKnownLibrarySink(known) + isKnownLibrarySink(known) or - CoreKnowledge::isKnownStepSrc(known) + isKnownStepSrc(known) or - CoreKnowledge::isOtherModeledArgument(known, _) + exists(OtherModeledArgumentCharacteristic characteristic | + characteristic.getEndpoints(known) + ) ) ) } @@ -584,10 +753,19 @@ private class DatabaseAccessCallHeuristicCharacteristic extends NosqlInjectionSi private class ModeledSinkCharacteristic extends NosqlInjectionSinkEndpointFilterCharacteristic { ModeledSinkCharacteristic() { this = "modeled sink" } + /** + * Holds if the node `n` is a known sink in a modeled library, or a sibling-argument of such a sink. + */ + predicate isArgumentToKnownLibrarySinkFunction(DataFlow::Node n) { + exists(DataFlow::InvokeNode invk, DataFlow::Node known | + invk.getAnArgument() = n and invk.getAnArgument() = known and isKnownLibrarySink(known) + ) + } + override predicate getEndpoints(DataFlow::Node n) { exists(DataFlow::CallNode call | n = call.getAnArgument() | // Remove modeled sinks - CoreKnowledge::isArgumentToKnownLibrarySinkFunction(n) + isArgumentToKnownLibrarySinkFunction(n) ) } } @@ -598,7 +776,7 @@ private class PredecessorInModeledFlowStepCharacteristic extends NosqlInjectionS override predicate getEndpoints(DataFlow::Node n) { exists(DataFlow::CallNode call | n = call.getAnArgument() | // Remove common kinds of unlikely sinks - CoreKnowledge::isKnownStepSrc(n) + isKnownStepSrc(n) ) } } @@ -651,7 +829,7 @@ private class NotDirectArgumentToLikelyExternalLibraryCallOrHeuristicSinkNosqlCh // // ## Direct arguments to external library calls // - // The `StandardEndpointFilters::flowsToArgumentOfLikelyExternalLibraryCall` endpoint filter + // The `flowsToArgumentOfLikelyExternalLibraryCall` endpoint filter // allows sink candidates which are within object literals or array literals, for example // `req.sendFile(_, { path: ENDPOINT })`. // @@ -674,7 +852,7 @@ private class NotDirectArgumentToLikelyExternalLibraryCallOrHeuristicSinkNosqlCh // `codeql/javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll`. // We can't reuse the class because importing that file would cause us to treat these // heuristic sinks as known sinks. - not n = StandardEndpointFilters::getALikelyExternalLibraryCall().getAnArgument() and + not n = getALikelyExternalLibraryCall().getAnArgument() and not ( isAssignedToOrConcatenatedWith(n, "(?i)(nosql|query)") or isArgTo(n, "(?i)(query)") @@ -743,7 +921,7 @@ private class NotAnArgumentToLikelyExternalLibraryCallOrHeuristicSinkCharacteris // `codeql/javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll`. // We can't reuse the class because importing that file would cause us to treat these // heuristic sinks as known sinks. - not StandardEndpointFilters::flowsToArgumentOfLikelyExternalLibraryCall(n) and + not flowsToArgumentOfLikelyExternalLibraryCall(n) and not ( isAssignedToOrConcatenatedWith(n, "(?i)(sql|query)") or isArgTo(n, "(?i)(query)") or @@ -781,7 +959,7 @@ private class NotDirectArgumentToLikelyExternalLibraryCallOrHeuristicSinkTainted // `codeql/javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll`. // We can't reuse the class because importing that file would cause us to treat these // heuristic sinks as known sinks. - not StandardEndpointFilters::flowsToArgumentOfLikelyExternalLibraryCall(n) and + not flowsToArgumentOfLikelyExternalLibraryCall(n) and not ( isAssignedToOrConcatenatedWith(n, "(?i)(file|folder|dir|absolute)") or @@ -842,7 +1020,7 @@ private class NotDirectArgumentToLikelyExternalLibraryCallOrHeuristicSinkXssChar // `codeql/javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll`. // We can't reuse the class because importing that file would cause us to treat these // heuristic sinks as known sinks. - not StandardEndpointFilters::flowsToArgumentOfLikelyExternalLibraryCall(n) and + not flowsToArgumentOfLikelyExternalLibraryCall(n) and not ( isAssignedToOrConcatenatedWith(n, "(?i)(html|innerhtml)") or diff --git a/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/FilteringReasons.qll b/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/FilteringReasons.qll deleted file mode 100644 index 4b0cdb986e88..000000000000 --- a/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/FilteringReasons.qll +++ /dev/null @@ -1,220 +0,0 @@ -/** - * For internal use only. - * - * Defines a set of reasons why a particular endpoint was filtered out. This set of reasons - * contains both reasons why an endpoint could be `NotASink` and reasons why an endpoint could be - * `LikelyNotASink`. The `NotASinkReason`s defined here are exhaustive, but the - * `LikelyNotASinkReason`s are not exhaustive. - */ -newtype TFilteringReason = - TIsArgumentToBuiltinFunctionReason() or - TLodashUnderscoreArgumentReason() or - TClientRequestReason() or - TPromiseDefinitionReason() or - TCryptographicKeyReason() or - TCryptographicOperationFlowReason() or - TLoggerMethodReason() or - TTimeoutReason() or - TReceiverStorageReason() or - TStringStartsWithReason() or - TStringEndsWithReason() or - TStringRegExpTestReason() or - TEventRegistrationReason() or - TEventDispatchReason() or - TMembershipCandidateTestReason() or - TFileSystemAccessReason() or - TDatabaseAccessReason() or - TDomReason() or - TNextFunctionCallReason() or - TArgumentToArrayReason() or - TArgumentToBuiltinGlobalVarRefReason() or - TConstantReceiverReason() or - TBuiltinCallNameReason() or - TBase64ManipulationReason() or - TJQueryArgumentReason() or - TDojoRequireReason() - -/** A reason why a particular endpoint was filtered out by the endpoint filters. */ -abstract class FilteringReason extends TFilteringReason { - abstract string getDescription(); - - abstract int getEncoding(); - - string toString() { result = getDescription() } -} - -/** - * A reason why a particular endpoint might be considered to be `NotASink`. - * - * An endpoint is `NotASink` if it has at least one `NotASinkReason`, it does not have any - * `LikelyNotASinkReason`s, and it is not a known sink. - */ -abstract class NotASinkReason extends FilteringReason { } - -/** - * A reason why a particular endpoint might be considered to be `LikelyNotASink`. - * - * An endpoint is `LikelyNotASink` if it has at least one `LikelyNotASinkReason` and it is not a - * known sink. - */ -abstract class LikelyNotASinkReason extends FilteringReason { } - -class IsArgumentToBuiltinFunctionReason extends NotASinkReason, TIsArgumentToBuiltinFunctionReason { - override string getDescription() { result = "IsArgumentToBuiltinFunction" } - - override int getEncoding() { result = 5 } -} - -class LodashUnderscoreArgumentReason extends NotASinkReason, TLodashUnderscoreArgumentReason { - override string getDescription() { result = "LodashUnderscoreArgument" } - - override int getEncoding() { result = 6 } -} - -class ClientRequestReason extends NotASinkReason, TClientRequestReason { - override string getDescription() { result = "ClientRequest" } - - override int getEncoding() { result = 7 } -} - -class PromiseDefinitionReason extends NotASinkReason, TPromiseDefinitionReason { - override string getDescription() { result = "PromiseDefinition" } - - override int getEncoding() { result = 8 } -} - -class CryptographicKeyReason extends NotASinkReason, TCryptographicKeyReason { - override string getDescription() { result = "CryptographicKey" } - - override int getEncoding() { result = 9 } -} - -class CryptographicOperationFlowReason extends NotASinkReason, TCryptographicOperationFlowReason { - override string getDescription() { result = "CryptographicOperationFlow" } - - override int getEncoding() { result = 10 } -} - -class LoggerMethodReason extends NotASinkReason, TLoggerMethodReason { - override string getDescription() { result = "LoggerMethod" } - - override int getEncoding() { result = 11 } -} - -class TimeoutReason extends NotASinkReason, TTimeoutReason { - override string getDescription() { result = "Timeout" } - - override int getEncoding() { result = 12 } -} - -class ReceiverStorageReason extends NotASinkReason, TReceiverStorageReason { - override string getDescription() { result = "ReceiverStorage" } - - override int getEncoding() { result = 13 } -} - -class StringStartsWithReason extends NotASinkReason, TStringStartsWithReason { - override string getDescription() { result = "StringStartsWith" } - - override int getEncoding() { result = 14 } -} - -class StringEndsWithReason extends NotASinkReason, TStringEndsWithReason { - override string getDescription() { result = "StringEndsWith" } - - override int getEncoding() { result = 15 } -} - -class StringRegExpTestReason extends NotASinkReason, TStringRegExpTestReason { - override string getDescription() { result = "StringRegExpTest" } - - override int getEncoding() { result = 16 } -} - -class EventRegistrationReason extends NotASinkReason, TEventRegistrationReason { - override string getDescription() { result = "EventRegistration" } - - override int getEncoding() { result = 17 } -} - -class EventDispatchReason extends NotASinkReason, TEventDispatchReason { - override string getDescription() { result = "EventDispatch" } - - override int getEncoding() { result = 18 } -} - -class MembershipCandidateTestReason extends NotASinkReason, TMembershipCandidateTestReason { - override string getDescription() { result = "MembershipCandidateTest" } - - override int getEncoding() { result = 19 } -} - -class FileSystemAccessReason extends NotASinkReason, TFileSystemAccessReason { - override string getDescription() { result = "FileSystemAccess" } - - override int getEncoding() { result = 20 } -} - -class DatabaseAccessReason extends NotASinkReason, TDatabaseAccessReason { - override string getDescription() { result = "DatabaseAccess" } - - override int getEncoding() { result = 21 } -} - -class DomReason extends NotASinkReason, TDomReason { - override string getDescription() { result = "DOM" } - - override int getEncoding() { result = 22 } -} - -/** DEPRECATED: Alias for DomReason */ -deprecated class DOMReason = DomReason; - -class NextFunctionCallReason extends NotASinkReason, TNextFunctionCallReason { - override string getDescription() { result = "NextFunctionCall" } - - override int getEncoding() { result = 23 } -} - -class ArgumentToArrayReason extends LikelyNotASinkReason, TArgumentToArrayReason { - override string getDescription() { result = "ArgumentToArray" } - - override int getEncoding() { result = 24 } -} - -class ArgumentToBuiltinGlobalVarRefReason extends LikelyNotASinkReason, - TArgumentToBuiltinGlobalVarRefReason { - override string getDescription() { result = "ArgumentToBuiltinGlobalVarRef" } - - override int getEncoding() { result = 25 } -} - -class ConstantReceiverReason extends NotASinkReason, TConstantReceiverReason { - override string getDescription() { result = "ConstantReceiver" } - - override int getEncoding() { result = 26 } -} - -class BuiltinCallNameReason extends NotASinkReason, TBuiltinCallNameReason { - override string getDescription() { result = "BuiltinCallName" } - - override int getEncoding() { result = 27 } -} - -class Base64ManipulationReason extends NotASinkReason, TBase64ManipulationReason { - override string getDescription() { result = "Base64Manipulation" } - - override int getEncoding() { result = 28 } -} - -class JQueryArgumentReason extends NotASinkReason, TJQueryArgumentReason { - override string getDescription() { result = "JQueryArgument" } - - override int getEncoding() { result = 29 } -} - -class DojoRequireReason extends NotASinkReason, TDojoRequireReason { - override string getDescription() { result = "DojoRequire" } - - override int getEncoding() { result = 30 } -} diff --git a/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/NosqlInjectionATM.qll b/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/NosqlInjectionATM.qll index 85b3d14d7e93..6724b5794b8b 100644 --- a/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/NosqlInjectionATM.qll +++ b/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/NosqlInjectionATM.qll @@ -1,6 +1,7 @@ /** * For internal use only. * + * A taint-tracking configuration for reasoning about NoSQL injection vulnerabilities. * Defines shared code used by the NoSQL injection boosted query. */ @@ -8,65 +9,21 @@ import javascript private import semmle.javascript.heuristics.SyntacticHeuristics private import semmle.javascript.security.dataflow.NosqlInjectionCustomizations import AdaptiveThreatModeling -private import CoreKnowledge as CoreKnowledge -class NosqlInjectionAtmConfig extends AtmConfig { - NosqlInjectionAtmConfig() { this = "NosqlInjectionATMConfig" } +class Configuration extends AtmConfig { + Configuration() { this = "NosqlInjectionATMConfig" } override predicate isKnownSource(DataFlow::Node source) { source instanceof NosqlInjection::Source or TaintedObject::isSource(source, _) } override EndpointType getASinkEndpointType() { result instanceof NosqlInjectionSinkType } -} - -/** DEPRECATED: Alias for NosqlInjectionAtmConfig */ -deprecated class NosqlInjectionATMConfig = NosqlInjectionAtmConfig; - -/** Holds if src -> trg is an additional flow step in the non-boosted NoSql injection security query. */ -predicate isBaseAdditionalFlowStep( - DataFlow::Node src, DataFlow::Node trg, DataFlow::FlowLabel inlbl, DataFlow::FlowLabel outlbl -) { - TaintedObject::step(src, trg, inlbl, outlbl) - or - // additional flow step to track taint through NoSQL query objects - inlbl = TaintedObject::label() and - outlbl = TaintedObject::label() and - exists(NoSql::Query query, DataFlow::SourceNode queryObj | - queryObj.flowsTo(query) and - queryObj.flowsTo(trg) and - src = queryObj.getAPropertyWrite().getRhs() - ) -} - -/** - * Gets a value that is (transitively) written to `query`, where `query` is a NoSQL sink. - * - * This predicate allows us to propagate data flow through property writes and array constructors - * within a query object, enabling the security query to pick up NoSQL injection vulnerabilities - * involving more complex queries. - */ -DataFlow::Node getASubexpressionWithinQuery(DataFlow::Node query) { - any(NosqlInjectionAtmConfig cfg).isEffectiveSink(query) and - exists(DataFlow::SourceNode receiver | - receiver = [getASubexpressionWithinQuery(query), query].getALocalSource() - | - result = - [receiver.getAPropertyWrite().getRhs(), receiver.(DataFlow::ArrayCreationNode).getAnElement()] - ) -} - -/** - * A taint-tracking configuration for reasoning about NoSQL injection vulnerabilities. - * - * This is largely a copy of the taint tracking configuration for the standard NoSQL injection - * query, except additional ATM sinks have been added and the additional flow step has been - * generalised to cover the sinks predicted by ATM. - */ -class Configuration extends TaintTracking::Configuration { - Configuration() { this = "NosqlInjectionATM" } - override predicate isSource(DataFlow::Node source) { source instanceof NosqlInjection::Source } + /* + * This is largely a copy of the taint tracking configuration for the standard NoSQL injection + * query, except additional ATM sinks have been added and the additional flow step has been + * generalised to cover the sinks predicted by ATM. + */ override predicate isSource(DataFlow::Node source, DataFlow::FlowLabel label) { TaintedObject::isSource(source, label) @@ -76,7 +33,7 @@ class Configuration extends TaintTracking::Configuration { sink.(NosqlInjection::Sink).getAFlowLabel() = label or // Allow effective sinks to have any taint label - any(NosqlInjectionAtmConfig cfg).isEffectiveSink(sink) + isEffectiveSink(sink) } override predicate isSanitizer(DataFlow::Node node) { @@ -95,7 +52,43 @@ class Configuration extends TaintTracking::Configuration { isBaseAdditionalFlowStep(src, trg, inlbl, outlbl) or // relaxed version of previous step to track taint through unmodeled NoSQL query objects - any(NosqlInjectionAtmConfig cfg).isEffectiveSink(trg) and + isEffectiveSink(trg) and src = getASubexpressionWithinQuery(trg) } + + /** Holds if src -> trg is an additional flow step in the non-boosted NoSql injection security query. */ + private predicate isBaseAdditionalFlowStep( + DataFlow::Node src, DataFlow::Node trg, DataFlow::FlowLabel inlbl, DataFlow::FlowLabel outlbl + ) { + TaintedObject::step(src, trg, inlbl, outlbl) + or + // additional flow step to track taint through NoSQL query objects + inlbl = TaintedObject::label() and + outlbl = TaintedObject::label() and + exists(NoSql::Query query, DataFlow::SourceNode queryObj | + queryObj.flowsTo(query) and + queryObj.flowsTo(trg) and + src = queryObj.getAPropertyWrite().getRhs() + ) + } + + /** + * Gets a value that is (transitively) written to `query`, where `query` is a NoSQL sink. + * + * This predicate allows us to propagate data flow through property writes and array constructors + * within a query object, enabling the security query to pick up NoSQL injection vulnerabilities + * involving more complex queries. + */ + private DataFlow::Node getASubexpressionWithinQuery(DataFlow::Node query) { + isEffectiveSink(query) and + exists(DataFlow::SourceNode receiver | + receiver = [getASubexpressionWithinQuery(query), query].getALocalSource() + | + result = + [ + receiver.getAPropertyWrite().getRhs(), + receiver.(DataFlow::ArrayCreationNode).getAnElement() + ] + ) + } } diff --git a/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/SqlInjectionATM.qll b/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/SqlInjectionATM.qll index f52e1898667c..3dd9b595327e 100644 --- a/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/SqlInjectionATM.qll +++ b/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/SqlInjectionATM.qll @@ -1,39 +1,25 @@ /** * For internal use only. * + * A taint-tracking configuration for reasoning about SQL injection vulnerabilities. * Defines shared code used by the SQL injection boosted query. */ import semmle.javascript.heuristics.SyntacticHeuristics import semmle.javascript.security.dataflow.SqlInjectionCustomizations import AdaptiveThreatModeling -import CoreKnowledge as CoreKnowledge -class SqlInjectionAtmConfig extends AtmConfig { - SqlInjectionAtmConfig() { this = "SqlInjectionATMConfig" } +class Configuration extends AtmConfig { + Configuration() { this = "SqlInjectionATMConfig" } override predicate isKnownSource(DataFlow::Node source) { source instanceof SqlInjection::Source } override EndpointType getASinkEndpointType() { result instanceof SqlInjectionSinkType } -} - -/** DEPRECATED: Alias for SqlInjectionAtmConfig */ -deprecated class SqlInjectionATMConfig = SqlInjectionAtmConfig; - -/** - * A taint-tracking configuration for reasoning about SQL injection vulnerabilities. - * - * This is largely a copy of the taint tracking configuration for the standard SQL injection - * query, except additional sinks have been added using the sink endpoint filter. - */ -class Configuration extends TaintTracking::Configuration { - Configuration() { this = "SqlInjectionATM" } - override predicate isSource(DataFlow::Node source) { source instanceof SqlInjection::Source } - - override predicate isSink(DataFlow::Node sink) { - sink instanceof SqlInjection::Sink or any(SqlInjectionAtmConfig cfg).isEffectiveSink(sink) - } + /* + * This is largely a copy of the taint tracking configuration for the standard SQL injection + * query, except additional sinks have been added using the sink endpoint filter. + */ override predicate isSanitizer(DataFlow::Node node) { super.isSanitizer(node) or diff --git a/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/StandardEndpointFilters.qll b/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/StandardEndpointFilters.qll deleted file mode 100644 index 11041937a3a2..000000000000 --- a/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/StandardEndpointFilters.qll +++ /dev/null @@ -1,78 +0,0 @@ -/** - * For internal use only. - * - * Provides classes and predicates that are useful for endpoint filters. - * - * The standard use of this library is to make use of `isPotentialEffectiveSink/1` - */ - -private import javascript -private import semmle.javascript.filters.ClassifyFiles as ClassifyFiles -private import semmle.javascript.heuristics.SyntacticHeuristics -private import CoreKnowledge as CoreKnowledge -import EndpointCharacteristics as EndpointCharacteristics - -/** - * Holds if the node `n` is an argument to a function that has a manual model. - */ -predicate isArgumentToModeledFunction(DataFlow::Node n) { - exists(DataFlow::InvokeNode invk, DataFlow::Node known | - invk.getAnArgument() = n and invk.getAnArgument() = known and isSomeModeledArgument(known) - ) -} - -/** - * Holds if the node `n` is an argument that has a manual model. - */ -predicate isSomeModeledArgument(DataFlow::Node n) { - CoreKnowledge::isKnownLibrarySink(n) or - CoreKnowledge::isKnownStepSrc(n) or - CoreKnowledge::isOtherModeledArgument(n, _) -} - -/** - * Holds if the data flow node is a (possibly indirect) argument of a likely external library call. - * - * This includes direct arguments of likely external library calls as well as nested object - * literals within those calls. - */ -predicate flowsToArgumentOfLikelyExternalLibraryCall(DataFlow::Node n) { - n = getACallWithoutCallee().getAnArgument() - or - exists(DataFlow::SourceNode src | flowsToArgumentOfLikelyExternalLibraryCall(src) | - n = src.getAPropertyWrite().getRhs() - ) - or - exists(DataFlow::ArrayCreationNode arr | flowsToArgumentOfLikelyExternalLibraryCall(arr) | - n = arr.getAnElement() - ) -} - -/** - * Get calls which are likely to be to external non-built-in libraries. - */ -DataFlow::CallNode getALikelyExternalLibraryCall() { result = getACallWithoutCallee() } - -/** - * Gets a node that flows to callback-parameter `p`. - */ -private DataFlow::SourceNode getACallback(DataFlow::ParameterNode p, DataFlow::TypeBackTracker t) { - t.start() and - result = p and - any(DataFlow::FunctionNode f).getLastParameter() = p and - exists(p.getACall()) - or - exists(DataFlow::TypeBackTracker t2 | result = getACallback(p, t2).backtrack(t2, t)) -} - -/** - * Get calls for which we do not have the callee (i.e. the definition of the called function). This - * acts as a heuristic for identifying calls to external library functions. - */ -DataFlow::CallNode getACallWithoutCallee() { - forall(Function callee | callee = result.getACallee() | callee.getTopLevel().isExterns()) and - not exists(DataFlow::ParameterNode param, DataFlow::FunctionNode callback | - param.flowsTo(result.getCalleeNode()) and - callback = getACallback(param, DataFlow::TypeBackTracker::end()) - ) -} diff --git a/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/TaintedPathATM.qll b/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/TaintedPathATM.qll index e83938071df8..a48eadc2532b 100644 --- a/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/TaintedPathATM.qll +++ b/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/TaintedPathATM.qll @@ -1,41 +1,31 @@ /** * For internal use only. * + * A taint-tracking configuration for reasoning about path injection vulnerabilities. * Defines shared code used by the path injection boosted query. */ import semmle.javascript.heuristics.SyntacticHeuristics import semmle.javascript.security.dataflow.TaintedPathCustomizations import AdaptiveThreatModeling -import CoreKnowledge as CoreKnowledge -class TaintedPathAtmConfig extends AtmConfig { - TaintedPathAtmConfig() { this = "TaintedPathATMConfig" } +class Configuration extends AtmConfig { + Configuration() { this = "TaintedPathATMConfig" } override predicate isKnownSource(DataFlow::Node source) { source instanceof TaintedPath::Source } override EndpointType getASinkEndpointType() { result instanceof TaintedPathSinkType } -} - -/** DEPRECATED: Alias for TaintedPathAtmConfig */ -deprecated class TaintedPathATMConfig = TaintedPathAtmConfig; - -/** - * A taint-tracking configuration for reasoning about path injection vulnerabilities. - * - * This is largely a copy of the taint tracking configuration for the standard path injection - * query, except additional ATM sinks have been added to the `isSink` predicate. - */ -class Configuration extends TaintTracking::Configuration { - Configuration() { this = "TaintedPathATM" } - override predicate isSource(DataFlow::Node source) { source instanceof TaintedPath::Source } + /* + * This is largely a copy of the taint tracking configuration for the standard path injection + * query, except additional ATM sinks have been added to the `isSink` predicate. + */ override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel label) { label = sink.(TaintedPath::Sink).getAFlowLabel() or // Allow effective sinks to have any taint label - any(TaintedPathAtmConfig cfg).isEffectiveSink(sink) + isEffectiveSink(sink) } override predicate isSanitizer(DataFlow::Node node) { node instanceof TaintedPath::Sanitizer } @@ -61,7 +51,7 @@ class Configuration extends TaintTracking::Configuration { * of barrier guards, we port the barrier guards for the boosted query from the standard library to * sanitizer guards here. */ -class BarrierGuardNodeAsSanitizerGuardNode extends TaintTracking::LabeledSanitizerGuardNode { +private class BarrierGuardNodeAsSanitizerGuardNode extends TaintTracking::LabeledSanitizerGuardNode { BarrierGuardNodeAsSanitizerGuardNode() { this instanceof TaintedPath::BarrierGuardNode } override predicate sanitizes(boolean outcome, Expr e) { diff --git a/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/XssATM.qll b/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/XssATM.qll index 508cac4544fa..43d8375b8a51 100644 --- a/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/XssATM.qll +++ b/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/XssATM.qll @@ -1,40 +1,25 @@ /** * For internal use only. * + * A taint-tracking configuration for reasoning about XSS vulnerabilities. * Defines shared code used by the XSS boosted query. */ private import semmle.javascript.heuristics.SyntacticHeuristics private import semmle.javascript.security.dataflow.DomBasedXssCustomizations import AdaptiveThreatModeling -import CoreKnowledge as CoreKnowledge -class DomBasedXssAtmConfig extends AtmConfig { - DomBasedXssAtmConfig() { this = "DomBasedXssATMConfig" } +class Configuration extends AtmConfig { + Configuration() { this = "DomBasedXssATMConfig" } override predicate isKnownSource(DataFlow::Node source) { source instanceof DomBasedXss::Source } override EndpointType getASinkEndpointType() { result instanceof XssSinkType } -} - -/** DEPRECATED: Alias for DomBasedXssAtmConfig */ -deprecated class DomBasedXssATMConfig = DomBasedXssAtmConfig; - -/** - * A taint-tracking configuration for reasoning about XSS vulnerabilities. - * - * This is largely a copy of the taint tracking configuration for the standard XSSThroughDom query, - * except additional ATM sinks have been added to the `isSink` predicate. - */ -class Configuration extends TaintTracking::Configuration { - Configuration() { this = "DomBasedXssATMConfiguration" } - override predicate isSource(DataFlow::Node source) { source instanceof DomBasedXss::Source } - - override predicate isSink(DataFlow::Node sink) { - sink instanceof DomBasedXss::Sink or - any(DomBasedXssAtmConfig cfg).isEffectiveSink(sink) - } + /* + * This is largely a copy of the taint tracking configuration for the standard XSSThroughDom query, + * except additional ATM sinks have been added to the `isSink` predicate. + */ override predicate isSanitizer(DataFlow::Node node) { super.isSanitizer(node) or diff --git a/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/XssThroughDomATM.qll b/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/XssThroughDomATM.qll new file mode 100644 index 000000000000..119e28f58108 --- /dev/null +++ b/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/XssThroughDomATM.qll @@ -0,0 +1,88 @@ +/** + * For internal use only. + * + * A taint-tracking configuration for reasoning about XSS through the DOM. + * Defines shared code used by the XSS Through DOM boosted query. + */ + +private import semmle.javascript.heuristics.SyntacticHeuristics +private import semmle.javascript.security.dataflow.DomBasedXssCustomizations +private import semmle.javascript.dataflow.InferredTypes +private import semmle.javascript.security.dataflow.XssThroughDomCustomizations::XssThroughDom as XssThroughDom +private import semmle.javascript.security.dataflow.UnsafeJQueryPluginCustomizations::UnsafeJQueryPlugin as UnsafeJQuery +import AdaptiveThreatModeling + +class Configuration extends AtmConfig { + Configuration() { this = "XssThroughDomAtmConfig" } + + override predicate isKnownSource(DataFlow::Node source) { + source instanceof XssThroughDom::Source + } + + override EndpointType getASinkEndpointType() { result instanceof XssSinkType } + + override predicate isSanitizer(DataFlow::Node node) { + super.isSanitizer(node) or + node instanceof DomBasedXss::Sanitizer + } + + override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) { + guard instanceof TypeTestGuard or + guard instanceof UnsafeJQuery::PropertyPresenceSanitizer or + guard instanceof UnsafeJQuery::NumberGuard or + guard instanceof PrefixStringSanitizer or + guard instanceof QuoteGuard or + guard instanceof ContainsHtmlGuard + } + + override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) { + DomBasedXss::isOptionallySanitizedEdge(pred, succ) + } +} + +/** + * A test of form `typeof x === "something"`, preventing `x` from being a string in some cases. + * + * This sanitizer helps prune infeasible paths in type-overloaded functions. + */ +class TypeTestGuard extends TaintTracking::SanitizerGuardNode, DataFlow::ValueNode { + override EqualityTest astNode; + Expr operand; + boolean polarity; + + TypeTestGuard() { + exists(TypeofTag tag | TaintTracking::isTypeofGuard(astNode, operand, tag) | + // typeof x === "string" sanitizes `x` when it evaluates to false + tag = "string" and + polarity = astNode.getPolarity().booleanNot() + or + // typeof x === "object" sanitizes `x` when it evaluates to true + tag != "string" and + polarity = astNode.getPolarity() + ) + } + + override predicate sanitizes(boolean outcome, Expr e) { + polarity = outcome and + e = operand + } +} + +private import semmle.javascript.security.dataflow.Xss::Shared as Shared + +private class PrefixStringSanitizer extends TaintTracking::SanitizerGuardNode, + DomBasedXss::PrefixStringSanitizer { + PrefixStringSanitizer() { this = this } +} + +private class PrefixString extends DataFlow::FlowLabel, DomBasedXss::PrefixString { + PrefixString() { this = this } +} + +private class QuoteGuard extends TaintTracking::SanitizerGuardNode, Shared::QuoteGuard { + QuoteGuard() { this = this } +} + +private class ContainsHtmlGuard extends TaintTracking::SanitizerGuardNode, Shared::ContainsHtmlGuard { + ContainsHtmlGuard() { this = this } +} diff --git a/javascript/ql/experimental/adaptivethreatmodeling/modelbuilding/DebugResultInclusion.ql b/javascript/ql/experimental/adaptivethreatmodeling/modelbuilding/DebugResultInclusion.ql index 444f682304da..1b5c235107e9 100644 --- a/javascript/ql/experimental/adaptivethreatmodeling/modelbuilding/DebugResultInclusion.ql +++ b/javascript/ql/experimental/adaptivethreatmodeling/modelbuilding/DebugResultInclusion.ql @@ -19,16 +19,16 @@ private import experimental.adaptivethreatmodeling.XssATM as XssAtm string getAReasonSinkExcluded(DataFlow::Node sinkCandidate, Query query) { query instanceof NosqlInjectionQuery and - result = any(NosqlInjectionAtm::NosqlInjectionAtmConfig cfg).getAReasonSinkExcluded(sinkCandidate) + result = any(NosqlInjectionAtm::Configuration cfg).getAReasonSinkExcluded(sinkCandidate) or query instanceof SqlInjectionQuery and - result = any(SqlInjectionAtm::SqlInjectionAtmConfig cfg).getAReasonSinkExcluded(sinkCandidate) + result = any(SqlInjectionAtm::Configuration cfg).getAReasonSinkExcluded(sinkCandidate) or query instanceof TaintedPathQuery and - result = any(TaintedPathAtm::TaintedPathAtmConfig cfg).getAReasonSinkExcluded(sinkCandidate) + result = any(TaintedPathAtm::Configuration cfg).getAReasonSinkExcluded(sinkCandidate) or query instanceof XssQuery and - result = any(XssAtm::DomBasedXssAtmConfig cfg).getAReasonSinkExcluded(sinkCandidate) + result = any(XssAtm::Configuration cfg).getAReasonSinkExcluded(sinkCandidate) } pragma[inline] diff --git a/javascript/ql/experimental/adaptivethreatmodeling/modelbuilding/extraction/ExtractEndpointMapping.ql b/javascript/ql/experimental/adaptivethreatmodeling/modelbuilding/extraction/ExtractEndpointMapping.ql index 697928d74b03..2fb6b56d4c09 100644 --- a/javascript/ql/experimental/adaptivethreatmodeling/modelbuilding/extraction/ExtractEndpointMapping.ql +++ b/javascript/ql/experimental/adaptivethreatmodeling/modelbuilding/extraction/ExtractEndpointMapping.ql @@ -8,21 +8,24 @@ import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionAtm import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionAtm import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathAtm import experimental.adaptivethreatmodeling.XssATM as XssAtm +import experimental.adaptivethreatmodeling.XssThroughDomATM as XssThroughDomAtm import experimental.adaptivethreatmodeling.AdaptiveThreatModeling from string queryName, AtmConfig c, EndpointType e where ( queryName = "SqlInjection" and - c instanceof SqlInjectionAtm::SqlInjectionAtmConfig + c instanceof SqlInjectionAtm::Configuration or queryName = "NosqlInjection" and - c instanceof NosqlInjectionAtm::NosqlInjectionAtmConfig + c instanceof NosqlInjectionAtm::Configuration or queryName = "TaintedPath" and - c instanceof TaintedPathAtm::TaintedPathAtmConfig + c instanceof TaintedPathAtm::Configuration or - queryName = "Xss" and c instanceof XssAtm::DomBasedXssAtmConfig + queryName = "Xss" and c instanceof XssAtm::Configuration + or + queryName = "XssThroughDOM" and c instanceof XssThroughDomAtm::Configuration ) and e = c.getASinkEndpointType() select queryName, e.getEncoding() as label diff --git a/javascript/ql/experimental/adaptivethreatmodeling/src/NosqlInjectionATM.ql b/javascript/ql/experimental/adaptivethreatmodeling/src/NosqlInjectionATM.ql index e35653fb96ac..8ec315c3d9c4 100644 --- a/javascript/ql/experimental/adaptivethreatmodeling/src/NosqlInjectionATM.ql +++ b/javascript/ql/experimental/adaptivethreatmodeling/src/NosqlInjectionATM.ql @@ -17,11 +17,8 @@ import ATM::ResultsInfo import DataFlow::PathGraph import experimental.adaptivethreatmodeling.NosqlInjectionATM -from DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, float score -where - cfg.hasFlowPath(source, sink) and - not isFlowLikelyInBaseQuery(source.getNode(), sink.getNode()) and - score = getScoreForFlow(source.getNode(), sink.getNode()) +from AtmConfig cfg, DataFlow::PathNode source, DataFlow::PathNode sink, float score +where cfg.getAlerts(source, sink, score) select sink.getNode(), source, sink, "(Experimental) This may be a database query that depends on $@. Identified using machine learning.", source.getNode(), "a user-provided value", score diff --git a/javascript/ql/experimental/adaptivethreatmodeling/src/SqlInjectionATM.ql b/javascript/ql/experimental/adaptivethreatmodeling/src/SqlInjectionATM.ql index b58dd9f46094..169215657070 100644 --- a/javascript/ql/experimental/adaptivethreatmodeling/src/SqlInjectionATM.ql +++ b/javascript/ql/experimental/adaptivethreatmodeling/src/SqlInjectionATM.ql @@ -17,11 +17,8 @@ import experimental.adaptivethreatmodeling.SqlInjectionATM import ATM::ResultsInfo import DataFlow::PathGraph -from DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, float score -where - cfg.hasFlowPath(source, sink) and - not isFlowLikelyInBaseQuery(source.getNode(), sink.getNode()) and - score = getScoreForFlow(source.getNode(), sink.getNode()) +from AtmConfig cfg, DataFlow::PathNode source, DataFlow::PathNode sink, float score +where cfg.getAlerts(source, sink, score) select sink.getNode(), source, sink, "(Experimental) This may be a database query that depends on $@. Identified using machine learning.", source.getNode(), "a user-provided value", score diff --git a/javascript/ql/experimental/adaptivethreatmodeling/src/TaintedPathATM.ql b/javascript/ql/experimental/adaptivethreatmodeling/src/TaintedPathATM.ql index 7e637687d75d..e4e4db0bf321 100644 --- a/javascript/ql/experimental/adaptivethreatmodeling/src/TaintedPathATM.ql +++ b/javascript/ql/experimental/adaptivethreatmodeling/src/TaintedPathATM.ql @@ -21,11 +21,8 @@ import ATM::ResultsInfo import DataFlow::PathGraph import experimental.adaptivethreatmodeling.TaintedPathATM -from DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, float score -where - cfg.hasFlowPath(source, sink) and - not isFlowLikelyInBaseQuery(source.getNode(), sink.getNode()) and - score = getScoreForFlow(source.getNode(), sink.getNode()) +from AtmConfig cfg, DataFlow::PathNode source, DataFlow::PathNode sink, float score +where cfg.getAlerts(source, sink, score) select sink.getNode(), source, sink, "(Experimental) This may be a path that depends on $@. Identified using machine learning.", source.getNode(), "a user-provided value", score diff --git a/javascript/ql/experimental/adaptivethreatmodeling/src/XssATM.ql b/javascript/ql/experimental/adaptivethreatmodeling/src/XssATM.ql index d0e98c1cd54e..deac86922ab6 100644 --- a/javascript/ql/experimental/adaptivethreatmodeling/src/XssATM.ql +++ b/javascript/ql/experimental/adaptivethreatmodeling/src/XssATM.ql @@ -18,11 +18,8 @@ import ATM::ResultsInfo import DataFlow::PathGraph import experimental.adaptivethreatmodeling.XssATM -from DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, float score -where - cfg.hasFlowPath(source, sink) and - not isFlowLikelyInBaseQuery(source.getNode(), sink.getNode()) and - score = getScoreForFlow(source.getNode(), sink.getNode()) +from AtmConfig cfg, DataFlow::PathNode source, DataFlow::PathNode sink, float score +where cfg.getAlerts(source, sink, score) select sink.getNode(), source, sink, "(Experimental) This may be a cross-site scripting vulnerability due to $@. Identified using machine learning.", source.getNode(), "a user-provided value", score diff --git a/javascript/ql/experimental/adaptivethreatmodeling/src/XssThroughDomATM.ql b/javascript/ql/experimental/adaptivethreatmodeling/src/XssThroughDomATM.ql new file mode 100644 index 000000000000..60df69414006 --- /dev/null +++ b/javascript/ql/experimental/adaptivethreatmodeling/src/XssThroughDomATM.ql @@ -0,0 +1,25 @@ +/** + * For internal use only. + * + * @name DOM text reinterpreted as HTML (experimental) + * @description Reinterpreting text from the DOM as HTML can lead + * to a cross-site scripting vulnerability. + * @kind path-problem + * @scored + * @problem.severity error + * @security-severity 6.1 + * @id js/ml-powered/xss-through-dom + * @tags experimental security + * external/cwe/cwe-079 external/cwe/cwe-116 + */ + +import javascript +import ATM::ResultsInfo +import DataFlow::PathGraph +import experimental.adaptivethreatmodeling.XssThroughDomATM + +from AtmConfig cfg, DataFlow::PathNode source, DataFlow::PathNode sink, float score +where cfg.getAlerts(source, sink, score) +select sink.getNode(), source, sink, + "(Experimental) $@ may be reinterpreted as HTML without escaping meta-characters. Identified using machine learning.", + source.getNode(), "DOM text", score diff --git a/javascript/ql/experimental/adaptivethreatmodeling/test/endpoint_large_scale/EndpointFeatures.ql b/javascript/ql/experimental/adaptivethreatmodeling/test/endpoint_large_scale/EndpointFeatures.ql index 6c1d88443d0d..047245578433 100644 --- a/javascript/ql/experimental/adaptivethreatmodeling/test/endpoint_large_scale/EndpointFeatures.ql +++ b/javascript/ql/experimental/adaptivethreatmodeling/test/endpoint_large_scale/EndpointFeatures.ql @@ -12,16 +12,17 @@ import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionAtm import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathAtm import experimental.adaptivethreatmodeling.XssATM as XssAtm import experimental.adaptivethreatmodeling.EndpointFeatures as EndpointFeatures -import experimental.adaptivethreatmodeling.StandardEndpointFilters as StandardEndpointFilters import extraction.NoFeaturizationRestrictionsConfig +private import experimental.adaptivethreatmodeling.EndpointCharacteristics as EndpointCharacteristics query predicate tokenFeatures(DataFlow::Node endpoint, string featureName, string featureValue) { ( - not exists(any(NosqlInjectionAtm::NosqlInjectionAtmConfig cfg).getAReasonSinkExcluded(endpoint)) or - not exists(any(SqlInjectionAtm::SqlInjectionAtmConfig cfg).getAReasonSinkExcluded(endpoint)) or - not exists(any(TaintedPathAtm::TaintedPathAtmConfig cfg).getAReasonSinkExcluded(endpoint)) or - not exists(any(XssAtm::DomBasedXssAtmConfig cfg).getAReasonSinkExcluded(endpoint)) or - StandardEndpointFilters::isArgumentToModeledFunction(endpoint) + not exists(any(NosqlInjectionAtm::Configuration cfg).getAReasonSinkExcluded(endpoint)) or + not exists(any(SqlInjectionAtm::Configuration cfg).getAReasonSinkExcluded(endpoint)) or + not exists(any(TaintedPathAtm::Configuration cfg).getAReasonSinkExcluded(endpoint)) or + not exists(any(XssAtm::Configuration cfg).getAReasonSinkExcluded(endpoint)) or + any(EndpointCharacteristics::IsArgumentToModeledFunctionCharacteristic characteristic) + .getEndpoints(endpoint) ) and EndpointFeatures::tokenFeatures(endpoint, featureName, featureValue) } diff --git a/javascript/ql/experimental/adaptivethreatmodeling/test/endpoint_large_scale/FilteredTruePositives.ql b/javascript/ql/experimental/adaptivethreatmodeling/test/endpoint_large_scale/FilteredTruePositives.ql index d8de88e3454c..deae494c2265 100644 --- a/javascript/ql/experimental/adaptivethreatmodeling/test/endpoint_large_scale/FilteredTruePositives.ql +++ b/javascript/ql/experimental/adaptivethreatmodeling/test/endpoint_large_scale/FilteredTruePositives.ql @@ -23,24 +23,24 @@ import experimental.adaptivethreatmodeling.XssATM as XssAtm query predicate nosqlFilteredTruePositives(DataFlow::Node endpoint, string reason) { endpoint instanceof NosqlInjection::Sink and - reason = any(NosqlInjectionAtm::NosqlInjectionAtmConfig cfg).getAReasonSinkExcluded(endpoint) and + reason = any(NosqlInjectionAtm::Configuration cfg).getAReasonSinkExcluded(endpoint) and not reason = ["argument to modeled function", "modeled sink", "modeled database access"] } query predicate sqlFilteredTruePositives(DataFlow::Node endpoint, string reason) { endpoint instanceof SqlInjection::Sink and - reason = any(SqlInjectionAtm::SqlInjectionAtmConfig cfg).getAReasonSinkExcluded(endpoint) and + reason = any(SqlInjectionAtm::Configuration cfg).getAReasonSinkExcluded(endpoint) and reason != "argument to modeled function" } query predicate taintedPathFilteredTruePositives(DataFlow::Node endpoint, string reason) { endpoint instanceof TaintedPath::Sink and - reason = any(TaintedPathAtm::TaintedPathAtmConfig cfg).getAReasonSinkExcluded(endpoint) and + reason = any(TaintedPathAtm::Configuration cfg).getAReasonSinkExcluded(endpoint) and reason != "argument to modeled function" } query predicate xssFilteredTruePositives(DataFlow::Node endpoint, string reason) { endpoint instanceof DomBasedXss::Sink and - reason = any(XssAtm::DomBasedXssAtmConfig cfg).getAReasonSinkExcluded(endpoint) and + reason = any(XssAtm::Configuration cfg).getAReasonSinkExcluded(endpoint) and reason != "argument to modeled function" } diff --git a/javascript/ql/experimental/adaptivethreatmodeling/test/endpoint_large_scale/getALikelyExternalLibraryCall.ql b/javascript/ql/experimental/adaptivethreatmodeling/test/endpoint_large_scale/getALikelyExternalLibraryCall.ql index 6c2928c74e00..da91ecf1b438 100644 --- a/javascript/ql/experimental/adaptivethreatmodeling/test/endpoint_large_scale/getALikelyExternalLibraryCall.ql +++ b/javascript/ql/experimental/adaptivethreatmodeling/test/endpoint_large_scale/getALikelyExternalLibraryCall.ql @@ -1,3 +1,3 @@ -import experimental.adaptivethreatmodeling.StandardEndpointFilters +import experimental.adaptivethreatmodeling.EndpointCharacteristics as EndpointCharacteristics -select getALikelyExternalLibraryCall() +select EndpointCharacteristics::getALikelyExternalLibraryCall() diff --git a/javascript/ql/experimental/adaptivethreatmodeling/test/modeled_apis/nosql_endpoint_filter_ignores_modeled_apis.ql b/javascript/ql/experimental/adaptivethreatmodeling/test/modeled_apis/nosql_endpoint_filter_ignores_modeled_apis.ql index a1d06d2881d0..b77685e966c6 100644 --- a/javascript/ql/experimental/adaptivethreatmodeling/test/modeled_apis/nosql_endpoint_filter_ignores_modeled_apis.ql +++ b/javascript/ql/experimental/adaptivethreatmodeling/test/modeled_apis/nosql_endpoint_filter_ignores_modeled_apis.ql @@ -2,5 +2,5 @@ import javascript import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionAtm query predicate effectiveSinks(DataFlow::Node node) { - not exists(any(NosqlInjectionAtm::NosqlInjectionAtmConfig cfg).getAReasonSinkExcluded(node)) + not exists(any(NosqlInjectionAtm::Configuration cfg).getAReasonSinkExcluded(node)) }