mirror of
https://github.com/github/codeql.git
synced 2026-04-27 09:45:15 +02:00
Merge branch 'main' into angular-sources-sinks
This commit is contained in:
@@ -26,6 +26,11 @@ class ThreatModelSource extends DataFlow::Node instanceof ThreatModelSource::Ran
|
||||
|
||||
/** Gets a string that describes the type of this threat-model source. */
|
||||
string getSourceType() { result = super.getSourceType() }
|
||||
|
||||
/**
|
||||
* Holds if this is a source of data that is specific to the web browser environment.
|
||||
*/
|
||||
predicate isClientSideSource() { super.isClientSideSource() }
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new sources for specific threat-models. */
|
||||
@@ -48,6 +53,11 @@ module ThreatModelSource {
|
||||
|
||||
/** Gets a string that describes the type of this threat-model source. */
|
||||
abstract string getSourceType();
|
||||
|
||||
/**
|
||||
* Holds if this is a source of data that is specific to the web browser environment.
|
||||
*/
|
||||
predicate isClientSideSource() { this.getThreatModel() = "view-component-input" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
47
javascript/ql/lib/semmle/javascript/ViewComponentInput.qll
Normal file
47
javascript/ql/lib/semmle/javascript/ViewComponentInput.qll
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Provides a classes and predicates for contributing to the `view-component-input` threat model.
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
|
||||
/**
|
||||
* An input to a view component, such as React props.
|
||||
*/
|
||||
abstract class ViewComponentInput extends DataFlow::Node {
|
||||
/** Gets a string that describes the type of this threat-model source. */
|
||||
abstract string getSourceType();
|
||||
}
|
||||
|
||||
private class ViewComponentInputAsThreatModelSource extends ThreatModelSource::Range instanceof ViewComponentInput
|
||||
{
|
||||
ViewComponentInputAsThreatModelSource() { not isSafeType(this.asExpr().getType()) }
|
||||
|
||||
final override string getThreatModel() { result = "view-component-input" }
|
||||
|
||||
final override string getSourceType() { result = ViewComponentInput.super.getSourceType() }
|
||||
}
|
||||
|
||||
private predicate isSafeType(Type t) {
|
||||
t instanceof NumberLikeType
|
||||
or
|
||||
t instanceof BooleanLikeType
|
||||
or
|
||||
t instanceof UndefinedType
|
||||
or
|
||||
t instanceof NullType
|
||||
or
|
||||
t instanceof VoidType
|
||||
or
|
||||
hasSafeTypes(t, t.(UnionType).getNumElementType())
|
||||
or
|
||||
isSafeType(t.(IntersectionType).getAnElementType())
|
||||
}
|
||||
|
||||
/** Hold if the first `n` components of `t` are safe types. */
|
||||
private predicate hasSafeTypes(UnionType t, int n) {
|
||||
isSafeType(t.getElementType(0)) and
|
||||
n = 1
|
||||
or
|
||||
isSafeType(t.getElementType(n - 1)) and
|
||||
hasSafeTypes(t, n - 1)
|
||||
}
|
||||
@@ -8,6 +8,7 @@ private import semmle.javascript.security.dataflow.CodeInjectionCustomizations
|
||||
private import semmle.javascript.security.dataflow.ClientSideUrlRedirectCustomizations
|
||||
private import semmle.javascript.DynamicPropertyAccess
|
||||
private import semmle.javascript.dataflow.internal.PreCallGraphStep
|
||||
private import semmle.javascript.ViewComponentInput
|
||||
|
||||
/**
|
||||
* Provides classes for working with Angular (also known as Angular 2.x) applications.
|
||||
@@ -610,4 +611,17 @@ module Angular2 {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class InputFieldAsViewComponentInput extends ViewComponentInput {
|
||||
InputFieldAsViewComponentInput() {
|
||||
this =
|
||||
API::moduleImport("@angular/core")
|
||||
.getMember("Input")
|
||||
.getReturn()
|
||||
.getADecoratedMember()
|
||||
.asSource()
|
||||
}
|
||||
|
||||
override string getSourceType() { result = "Angular component input field" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import javascript
|
||||
private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
|
||||
private import semmle.javascript.dataflow.internal.PreCallGraphStep
|
||||
private import semmle.javascript.ViewComponentInput
|
||||
|
||||
/**
|
||||
* Gets a reference to the 'React' object.
|
||||
@@ -868,3 +869,9 @@ private class PropsFlowStep extends PreCallGraphStep {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class ReactPropAsViewComponentInput extends ViewComponentInput {
|
||||
ReactPropAsViewComponentInput() { this = any(ReactComponent c).getADirectPropsAccess() }
|
||||
|
||||
override string getSourceType() { result = "React props" }
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.ViewComponentInput
|
||||
|
||||
module Vue {
|
||||
/** The global variable `Vue`, as an API graph entry point. */
|
||||
@@ -85,17 +86,16 @@ module Vue {
|
||||
* A class with a `@Component` decorator, making it usable as an "options" object in Vue.
|
||||
*/
|
||||
class ClassComponent extends DataFlow::ClassNode {
|
||||
private ClassDefinition cls;
|
||||
DataFlow::Node decorator;
|
||||
|
||||
ClassComponent() {
|
||||
exists(ClassDefinition cls |
|
||||
this = cls.flow() and
|
||||
cls.getADecorator().getExpression() = decorator.asExpr() and
|
||||
(
|
||||
componentDecorator().flowsTo(decorator)
|
||||
or
|
||||
componentDecorator().getACall() = decorator
|
||||
)
|
||||
this = cls.flow() and
|
||||
cls.getADecorator().getExpression() = decorator.asExpr() and
|
||||
(
|
||||
componentDecorator().flowsTo(decorator)
|
||||
or
|
||||
componentDecorator().getACall() = decorator
|
||||
)
|
||||
}
|
||||
|
||||
@@ -105,6 +105,9 @@ module Vue {
|
||||
* These options correspond to the options one would pass to `new Vue({...})` or similar.
|
||||
*/
|
||||
API::Node getDecoratorOptions() { result = decorator.(API::CallNode).getParameter(0) }
|
||||
|
||||
/** Gets the AST node for the class definition. */
|
||||
ClassDefinition getClassDefinition() { result = cls }
|
||||
}
|
||||
|
||||
private string memberKindVerb(DataFlow::MemberKind kind) {
|
||||
@@ -460,6 +463,12 @@ module Vue {
|
||||
|
||||
SingleFileComponent() { this = MkSingleFileComponent(file) }
|
||||
|
||||
/** Gets a call to `defineProps` in this component. */
|
||||
DataFlow::CallNode getDefinePropsCall() {
|
||||
result = DataFlow::globalVarRef("defineProps").getACall() and
|
||||
result.getFile() = file
|
||||
}
|
||||
|
||||
override Template::Element getTemplateElement() {
|
||||
exists(HTML::Element e | result.(Template::HtmlElement).getElement() = e |
|
||||
e.getFile() = file and
|
||||
@@ -697,4 +706,68 @@ module Vue {
|
||||
|
||||
override ClientSideRemoteFlowKind getKind() { result = kind }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the given type annotation indicates a value that is not typically considered taintable.
|
||||
*/
|
||||
private predicate isSafeType(TypeAnnotation type) {
|
||||
type.isBooleany() or
|
||||
type.isNumbery() or
|
||||
type.isRawFunction() or
|
||||
type instanceof FunctionTypeExpr
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the given field has a type that indicates that is can not contain a taintable value.
|
||||
*/
|
||||
private predicate isSafeField(FieldDeclaration field) { isSafeType(field.getTypeAnnotation()) }
|
||||
|
||||
private DataFlow::Node getPropSpec(Component component) {
|
||||
result = component.getOption("props")
|
||||
or
|
||||
result = component.(SingleFileComponent).getDefinePropsCall().getArgument(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `component` has an input prop with the given name, that is of a taintable type.
|
||||
*/
|
||||
private predicate hasTaintableProp(Component component, string name) {
|
||||
exists(DataFlow::SourceNode spec | spec = getPropSpec(component).getALocalSource() |
|
||||
spec.(DataFlow::ArrayCreationNode).getAnElement().getStringValue() = name
|
||||
or
|
||||
exists(DataFlow::PropWrite write |
|
||||
write = spec.getAPropertyWrite(name) and
|
||||
not DataFlow::globalVarRef(["Number", "Boolean"]).flowsTo(write.getRhs())
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(FieldDeclaration field |
|
||||
field = component.getAsClassComponent().getClassDefinition().getField(name) and
|
||||
DataFlow::moduleMember("vue-property-decorator", "Prop")
|
||||
.getACall()
|
||||
.flowsToExpr(field.getADecorator().getExpression()) and
|
||||
not isSafeField(field)
|
||||
)
|
||||
or
|
||||
// defineProps() can be called with only type arguments and then the Vue compiler will
|
||||
// infer the prop types.
|
||||
exists(CallExpr call, FieldDeclaration field |
|
||||
call = component.(SingleFileComponent).getDefinePropsCall().asExpr() and
|
||||
field = call.getTypeArgument(0).(InterfaceTypeExpr).getMember(name) and
|
||||
not isSafeField(field)
|
||||
)
|
||||
}
|
||||
|
||||
private class PropAsViewComponentInput extends ViewComponentInput {
|
||||
PropAsViewComponentInput() {
|
||||
exists(Component component, string name | hasTaintableProp(component, name) |
|
||||
this = component.getAnInstanceRef().getAPropertyRead(name)
|
||||
or
|
||||
// defineProps() returns the props
|
||||
this = component.(SingleFileComponent).getDefinePropsCall().getAPropertyRead(name)
|
||||
)
|
||||
}
|
||||
|
||||
override string getSourceType() { result = "Vue prop" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,12 @@ module BrokenCryptoAlgorithmConfig implements DataFlow::ConfigSig {
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
|
||||
Location getASelectedSinkLocation(DataFlow::Node sink) {
|
||||
result = sink.(Sink).getLocation()
|
||||
or
|
||||
result = sink.(Sink).getInitialization().getLocation()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,6 +33,12 @@ module ClientSideRequestForgeryConfig implements DataFlow::ConfigSig {
|
||||
}
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
|
||||
Location getASelectedSinkLocation(DataFlow::Node sink) {
|
||||
result = sink.(Sink).getLocation()
|
||||
or
|
||||
result = sink.(Sink).getARequest().getLocation()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,7 +34,7 @@ module CommandInjection {
|
||||
* An active threat-model source, considered as a flow source.
|
||||
*/
|
||||
private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource {
|
||||
ActiveThreatModelSourceAsSource() { not this instanceof ClientSideRemoteFlowSource }
|
||||
ActiveThreatModelSourceAsSource() { not this.isClientSideSource() }
|
||||
|
||||
override string getSourceType() { result = "a user-provided value" }
|
||||
}
|
||||
|
||||
@@ -32,6 +32,13 @@ module CommandInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
|
||||
Location getASelectedSinkLocation(DataFlow::Node sink) {
|
||||
exists(DataFlow::Node node |
|
||||
isSinkWithHighlight(sink, node) and
|
||||
result = node.getLocation()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,7 +36,7 @@ module CorsMisconfigurationForCredentials {
|
||||
* An active threat-model source, considered as a flow source.
|
||||
*/
|
||||
private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource {
|
||||
ActiveThreatModelSourceAsSource() { not this instanceof ClientSideRemoteFlowSource }
|
||||
ActiveThreatModelSourceAsSource() { not this.isClientSideSource() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,6 +25,12 @@ module CorsMisconfigurationConfig implements DataFlow::ConfigSig {
|
||||
}
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
|
||||
Location getASelectedSinkLocation(DataFlow::Node sink) {
|
||||
result = sink.(Sink).getLocation()
|
||||
or
|
||||
result = sink.(Sink).getCredentialsHeader().getLocation()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,6 +35,15 @@ module DeepObjectResourceExhaustionConfig implements DataFlow::StateConfigSig {
|
||||
}
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
|
||||
Location getASelectedSinkLocation(DataFlow::Node sink) {
|
||||
result = sink.(Sink).getLocation()
|
||||
or
|
||||
exists(DataFlow::Node link |
|
||||
sink.(Sink).hasReason(link, _) and
|
||||
result = link.getLocation()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,6 +28,13 @@ module IndirectCommandInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
|
||||
Location getASelectedSinkLocation(DataFlow::Node sink) {
|
||||
exists(DataFlow::Node node |
|
||||
isSinkWithHighlight(sink, node) and
|
||||
result = node.getLocation()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,6 +25,12 @@ module InsecureDownloadConfig implements DataFlow::StateConfigSig {
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
|
||||
Location getASelectedSinkLocation(DataFlow::Node sink) {
|
||||
result = sink.(Sink).getLocation()
|
||||
or
|
||||
result = sink.(Sink).getDownloadCall().getLocation()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -54,7 +54,7 @@ deprecated class LogInjectionConfiguration extends TaintTracking::Configuration
|
||||
* A source of remote user controlled input.
|
||||
*/
|
||||
class RemoteSource extends Source instanceof RemoteFlowSource {
|
||||
RemoteSource() { not this instanceof ClientSideRemoteFlowSource }
|
||||
RemoteSource() { not this.isClientSideSource() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,6 +49,15 @@ module PrototypePollutionConfig implements DataFlow::StateConfigSig {
|
||||
}
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
|
||||
Location getASelectedSinkLocation(DataFlow::Node sink) {
|
||||
result = sink.(Sink).getLocation()
|
||||
or
|
||||
exists(Locatable loc |
|
||||
sink.(Sink).dependencyInfo(_, loc) and
|
||||
result = loc.getLocation()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,7 +34,7 @@ module RegExpInjection {
|
||||
* An active threat-model source, considered as a flow source.
|
||||
*/
|
||||
private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource {
|
||||
ActiveThreatModelSourceAsSource() { not this instanceof ClientSideRemoteFlowSource }
|
||||
ActiveThreatModelSourceAsSource() { not this.isClientSideSource() }
|
||||
}
|
||||
|
||||
private import IndirectCommandInjectionCustomizations
|
||||
|
||||
@@ -24,12 +24,18 @@ private module Cached {
|
||||
|
||||
/**
|
||||
* A source of remote input in a web browser environment.
|
||||
*
|
||||
* Note that this does not include `view-component-input` sources even if that threat model has been enabled by the user.
|
||||
* Consider using the predicate `ThreatModelSource#isClientSideSource()` to check for a broader class of client-side sources.
|
||||
*/
|
||||
cached
|
||||
abstract class ClientSideRemoteFlowSource extends RemoteFlowSource {
|
||||
/** Gets a string indicating what part of the browser environment this was derived from. */
|
||||
cached
|
||||
abstract ClientSideRemoteFlowKind getKind();
|
||||
|
||||
cached
|
||||
final override predicate isClientSideSource() { any() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ module RequestForgery {
|
||||
not this.(ClientSideRemoteFlowSource).getKind().isPathOrUrl()
|
||||
}
|
||||
|
||||
override predicate isServerSide() { not this instanceof ClientSideRemoteFlowSource }
|
||||
override predicate isServerSide() { not super.isClientSideSource() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,6 +28,12 @@ module RequestForgeryConfig implements DataFlow::ConfigSig {
|
||||
}
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
|
||||
Location getASelectedSinkLocation(DataFlow::Node sink) {
|
||||
result = sink.(Sink).getLocation()
|
||||
or
|
||||
result = sink.(Sink).getARequest().getLocation()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -63,7 +63,7 @@ module ResourceExhaustion {
|
||||
private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource {
|
||||
ActiveThreatModelSourceAsSource() {
|
||||
// exclude source that only happen client-side
|
||||
not this instanceof ClientSideRemoteFlowSource and
|
||||
not this.isClientSideSource() and
|
||||
not this = DataFlow::parameterNode(any(PostMessageEventHandler pmeh).getEventParameter())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,13 @@ module ShellCommandInjectionFromEnvironmentConfig implements DataFlow::ConfigSig
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
|
||||
Location getASelectedSinkLocation(DataFlow::Node sink) {
|
||||
exists(DataFlow::Node node |
|
||||
isSinkWithHighlight(sink, node) and
|
||||
result = node.getLocation()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -719,7 +719,7 @@ module TaintedPath {
|
||||
* An active threat-model source, considered as a flow source.
|
||||
*/
|
||||
private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource {
|
||||
ActiveThreatModelSourceAsSource() { not this instanceof ClientSideRemoteFlowSource }
|
||||
ActiveThreatModelSourceAsSource() { not this.isClientSideSource() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,6 +34,12 @@ module UnsafeCodeConstruction {
|
||||
DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSourceCallContext }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
|
||||
Location getASelectedSinkLocation(DataFlow::Node sink) {
|
||||
result = sink.(Sink).getLocation()
|
||||
or
|
||||
result = sink.(Sink).getCodeSink().getLocation()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -62,6 +62,12 @@ module UnsafeHtmlConstructionConfig implements DataFlow::StateConfigSig {
|
||||
DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSourceCallContext }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
|
||||
Location getASelectedSinkLocation(DataFlow::Node sink) {
|
||||
result = sink.(Sink).getLocation()
|
||||
or
|
||||
result = sink.(Sink).getSink().getLocation()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,6 +38,12 @@ module UnsafeJQueryPluginConfig implements DataFlow::ConfigSig {
|
||||
}
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
|
||||
Location getASelectedSourceLocation(DataFlow::Node source) {
|
||||
result = source.(Source).getLocation()
|
||||
or
|
||||
result = source.(Source).getPlugin().getLocation()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,6 +27,14 @@ module UnsafeShellCommandConstructionConfig implements DataFlow::ConfigSig {
|
||||
DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSourceCallContext }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
|
||||
Location getASelectedSinkLocation(DataFlow::Node sink) {
|
||||
result = sink.(Sink).getLocation()
|
||||
or
|
||||
result = sink.(Sink).getAlertLocation().getLocation()
|
||||
or
|
||||
result = sink.(Sink).getCommandExecution().getLocation()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,6 +27,14 @@ module PolynomialReDoSConfig implements DataFlow::ConfigSig {
|
||||
int fieldFlowBranchLimit() { result = 1 } // library inputs are too expensive on some projects
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
|
||||
Location getASelectedSinkLocation(DataFlow::Node sink) {
|
||||
result = sink.(Sink).getLocation()
|
||||
or
|
||||
result = sink.(Sink).getHighlight().getLocation()
|
||||
or
|
||||
result = sink.(Sink).getRegExp().getLocation()
|
||||
}
|
||||
}
|
||||
|
||||
/** Taint-tracking for reasoning about polynomial regular expression denial-of-service attacks. */
|
||||
|
||||
Reference in New Issue
Block a user