Skip to content

Commit 1b7977b

Browse files
authored
Merge pull request github#18466 from asgerf/js/view-component-inputs
JS: Add view-component-input threat model
2 parents 60f9160 + 051fa66 commit 1b7977b

34 files changed

+407
-66
lines changed

docs/codeql/reusables/threat-model-description.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ The less commonly used categories are:
1515
- ``database-access-result`` which represents a database access. Currently only used by JavaScript.
1616
- ``file-write`` which represents opening a file in write mode. Currently only used in C#.
1717
- ``reverse-dns`` which represents reverse DNS lookups. Currently only used in Java.
18+
- ``view-component-input`` which represents inputs to a React, Vue, or Angular component (also known as "props"). Currently only used by JavaScript/TypeScript.
1819

1920
When running a CodeQL analysis, the ``remote`` threat model is included by default. You can optionally include other threat models as appropriate when using the CodeQL CLI and in GitHub code scanning. For more information, see `Analyzing your code with CodeQL queries <https://docs.github.com/code-security/codeql-cli/getting-started-with-the-codeql-cli/analyzing-your-code-with-codeql-queries#including-model-packs-to-add-potential-sources-of-tainted-data>`__ and `Customizing your advanced setup for code scanning <https://docs.github.com/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning#extending-codeql-coverage-with-threat-models>`__.

javascript/ql/lib/semmle/javascript/Concepts.qll

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ class ThreatModelSource extends DataFlow::Node instanceof ThreatModelSource::Ran
2626

2727
/** Gets a string that describes the type of this threat-model source. */
2828
string getSourceType() { result = super.getSourceType() }
29+
30+
/**
31+
* Holds if this is a source of data that is specific to the web browser environment.
32+
*/
33+
predicate isClientSideSource() { super.isClientSideSource() }
2934
}
3035

3136
/** Provides a class for modeling new sources for specific threat-models. */
@@ -48,6 +53,11 @@ module ThreatModelSource {
4853

4954
/** Gets a string that describes the type of this threat-model source. */
5055
abstract string getSourceType();
56+
57+
/**
58+
* Holds if this is a source of data that is specific to the web browser environment.
59+
*/
60+
predicate isClientSideSource() { this.getThreatModel() = "view-component-input" }
5161
}
5262
}
5363

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* Provides a classes and predicates for contributing to the `view-component-input` threat model.
3+
*/
4+
5+
private import javascript
6+
7+
/**
8+
* An input to a view component, such as React props.
9+
*/
10+
abstract class ViewComponentInput extends DataFlow::Node {
11+
/** Gets a string that describes the type of this threat-model source. */
12+
abstract string getSourceType();
13+
}
14+
15+
private class ViewComponentInputAsThreatModelSource extends ThreatModelSource::Range instanceof ViewComponentInput
16+
{
17+
ViewComponentInputAsThreatModelSource() { not isSafeType(this.asExpr().getType()) }
18+
19+
final override string getThreatModel() { result = "view-component-input" }
20+
21+
final override string getSourceType() { result = ViewComponentInput.super.getSourceType() }
22+
}
23+
24+
private predicate isSafeType(Type t) {
25+
t instanceof NumberLikeType
26+
or
27+
t instanceof BooleanLikeType
28+
or
29+
t instanceof UndefinedType
30+
or
31+
t instanceof NullType
32+
or
33+
t instanceof VoidType
34+
or
35+
hasSafeTypes(t, t.(UnionType).getNumElementType())
36+
or
37+
isSafeType(t.(IntersectionType).getAnElementType())
38+
}
39+
40+
/** Hold if the first `n` components of `t` are safe types. */
41+
private predicate hasSafeTypes(UnionType t, int n) {
42+
isSafeType(t.getElementType(0)) and
43+
n = 1
44+
or
45+
isSafeType(t.getElementType(n - 1)) and
46+
hasSafeTypes(t, n - 1)
47+
}

javascript/ql/lib/semmle/javascript/frameworks/Angular2.qll

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ private import semmle.javascript.security.dataflow.CodeInjectionCustomizations
88
private import semmle.javascript.security.dataflow.ClientSideUrlRedirectCustomizations
99
private import semmle.javascript.DynamicPropertyAccess
1010
private import semmle.javascript.dataflow.internal.PreCallGraphStep
11+
private import semmle.javascript.ViewComponentInput
1112

1213
/**
1314
* Provides classes for working with Angular (also known as Angular 2.x) applications.
@@ -575,4 +576,17 @@ module Angular2 {
575576
)
576577
}
577578
}
579+
580+
private class InputFieldAsViewComponentInput extends ViewComponentInput {
581+
InputFieldAsViewComponentInput() {
582+
this =
583+
API::moduleImport("@angular/core")
584+
.getMember("Input")
585+
.getReturn()
586+
.getADecoratedMember()
587+
.asSource()
588+
}
589+
590+
override string getSourceType() { result = "Angular component input field" }
591+
}
578592
}

javascript/ql/lib/semmle/javascript/frameworks/React.qll

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import javascript
66
private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
77
private import semmle.javascript.dataflow.internal.PreCallGraphStep
8+
private import semmle.javascript.ViewComponentInput
89

910
/**
1011
* Gets a reference to the 'React' object.
@@ -868,3 +869,9 @@ private class PropsFlowStep extends PreCallGraphStep {
868869
)
869870
}
870871
}
872+
873+
private class ReactPropAsViewComponentInput extends ViewComponentInput {
874+
ReactPropAsViewComponentInput() { this = any(ReactComponent c).getADirectPropsAccess() }
875+
876+
override string getSourceType() { result = "React props" }
877+
}

javascript/ql/lib/semmle/javascript/frameworks/Vue.qll

Lines changed: 81 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44

55
import javascript
6+
import semmle.javascript.ViewComponentInput
67

78
module Vue {
89
/** The global variable `Vue`, as an API graph entry point. */
@@ -85,17 +86,16 @@ module Vue {
8586
* A class with a `@Component` decorator, making it usable as an "options" object in Vue.
8687
*/
8788
class ClassComponent extends DataFlow::ClassNode {
89+
private ClassDefinition cls;
8890
DataFlow::Node decorator;
8991

9092
ClassComponent() {
91-
exists(ClassDefinition cls |
92-
this = cls.flow() and
93-
cls.getADecorator().getExpression() = decorator.asExpr() and
94-
(
95-
componentDecorator().flowsTo(decorator)
96-
or
97-
componentDecorator().getACall() = decorator
98-
)
93+
this = cls.flow() and
94+
cls.getADecorator().getExpression() = decorator.asExpr() and
95+
(
96+
componentDecorator().flowsTo(decorator)
97+
or
98+
componentDecorator().getACall() = decorator
9999
)
100100
}
101101

@@ -105,6 +105,9 @@ module Vue {
105105
* These options correspond to the options one would pass to `new Vue({...})` or similar.
106106
*/
107107
API::Node getDecoratorOptions() { result = decorator.(API::CallNode).getParameter(0) }
108+
109+
/** Gets the AST node for the class definition. */
110+
ClassDefinition getClassDefinition() { result = cls }
108111
}
109112

110113
private string memberKindVerb(DataFlow::MemberKind kind) {
@@ -460,6 +463,12 @@ module Vue {
460463

461464
SingleFileComponent() { this = MkSingleFileComponent(file) }
462465

466+
/** Gets a call to `defineProps` in this component. */
467+
DataFlow::CallNode getDefinePropsCall() {
468+
result = DataFlow::globalVarRef("defineProps").getACall() and
469+
result.getFile() = file
470+
}
471+
463472
override Template::Element getTemplateElement() {
464473
exists(HTML::Element e | result.(Template::HtmlElement).getElement() = e |
465474
e.getFile() = file and
@@ -697,4 +706,68 @@ module Vue {
697706

698707
override ClientSideRemoteFlowKind getKind() { result = kind }
699708
}
709+
710+
/**
711+
* Holds if the given type annotation indicates a value that is not typically considered taintable.
712+
*/
713+
private predicate isSafeType(TypeAnnotation type) {
714+
type.isBooleany() or
715+
type.isNumbery() or
716+
type.isRawFunction() or
717+
type instanceof FunctionTypeExpr
718+
}
719+
720+
/**
721+
* Holds if the given field has a type that indicates that is can not contain a taintable value.
722+
*/
723+
private predicate isSafeField(FieldDeclaration field) { isSafeType(field.getTypeAnnotation()) }
724+
725+
private DataFlow::Node getPropSpec(Component component) {
726+
result = component.getOption("props")
727+
or
728+
result = component.(SingleFileComponent).getDefinePropsCall().getArgument(0)
729+
}
730+
731+
/**
732+
* Holds if `component` has an input prop with the given name, that is of a taintable type.
733+
*/
734+
private predicate hasTaintableProp(Component component, string name) {
735+
exists(DataFlow::SourceNode spec | spec = getPropSpec(component).getALocalSource() |
736+
spec.(DataFlow::ArrayCreationNode).getAnElement().getStringValue() = name
737+
or
738+
exists(DataFlow::PropWrite write |
739+
write = spec.getAPropertyWrite(name) and
740+
not DataFlow::globalVarRef(["Number", "Boolean"]).flowsTo(write.getRhs())
741+
)
742+
)
743+
or
744+
exists(FieldDeclaration field |
745+
field = component.getAsClassComponent().getClassDefinition().getField(name) and
746+
DataFlow::moduleMember("vue-property-decorator", "Prop")
747+
.getACall()
748+
.flowsToExpr(field.getADecorator().getExpression()) and
749+
not isSafeField(field)
750+
)
751+
or
752+
// defineProps() can be called with only type arguments and then the Vue compiler will
753+
// infer the prop types.
754+
exists(CallExpr call, FieldDeclaration field |
755+
call = component.(SingleFileComponent).getDefinePropsCall().asExpr() and
756+
field = call.getTypeArgument(0).(InterfaceTypeExpr).getMember(name) and
757+
not isSafeField(field)
758+
)
759+
}
760+
761+
private class PropAsViewComponentInput extends ViewComponentInput {
762+
PropAsViewComponentInput() {
763+
exists(Component component, string name | hasTaintableProp(component, name) |
764+
this = component.getAnInstanceRef().getAPropertyRead(name)
765+
or
766+
// defineProps() returns the props
767+
this = component.(SingleFileComponent).getDefinePropsCall().getAPropertyRead(name)
768+
)
769+
}
770+
771+
override string getSourceType() { result = "Vue prop" }
772+
}
700773
}

javascript/ql/lib/semmle/javascript/security/dataflow/CommandInjectionCustomizations.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ module CommandInjection {
3434
* An active threat-model source, considered as a flow source.
3535
*/
3636
private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource {
37-
ActiveThreatModelSourceAsSource() { not this instanceof ClientSideRemoteFlowSource }
37+
ActiveThreatModelSourceAsSource() { not this.isClientSideSource() }
3838

3939
override string getSourceType() { result = "a user-provided value" }
4040
}

javascript/ql/lib/semmle/javascript/security/dataflow/CorsMisconfigurationForCredentialsCustomizations.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ module CorsMisconfigurationForCredentials {
3636
* An active threat-model source, considered as a flow source.
3737
*/
3838
private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource {
39-
ActiveThreatModelSourceAsSource() { not this instanceof ClientSideRemoteFlowSource }
39+
ActiveThreatModelSourceAsSource() { not this.isClientSideSource() }
4040
}
4141

4242
/**

javascript/ql/lib/semmle/javascript/security/dataflow/LogInjectionQuery.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ deprecated class LogInjectionConfiguration extends TaintTracking::Configuration
5454
* A source of remote user controlled input.
5555
*/
5656
class RemoteSource extends Source instanceof RemoteFlowSource {
57-
RemoteSource() { not this instanceof ClientSideRemoteFlowSource }
57+
RemoteSource() { not this.isClientSideSource() }
5858
}
5959

6060
/**

javascript/ql/lib/semmle/javascript/security/dataflow/RegExpInjectionCustomizations.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ module RegExpInjection {
3434
* An active threat-model source, considered as a flow source.
3535
*/
3636
private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource {
37-
ActiveThreatModelSourceAsSource() { not this instanceof ClientSideRemoteFlowSource }
37+
ActiveThreatModelSourceAsSource() { not this.isClientSideSource() }
3838
}
3939

4040
private import IndirectCommandInjectionCustomizations

0 commit comments

Comments
 (0)