Skip to content

Commit 5ead424

Browse files
authored
Merge pull request #4450 from asgerf/js/angular
Approved by erik-krogh
2 parents d644a30 + f003413 commit 5ead424

File tree

88 files changed

+765
-66
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+765
-66
lines changed

change-notes/1.26/analysis-javascript.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
## General improvements
44

5+
* Angular-specific taint sources and sinks are now recognized by the security queries.
6+
57
* Support for the following frameworks and libraries has been improved:
8+
- [@angular/*](https://www.npmjs.com/package/@angular/core)
69
- [AWS Serverless](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html)
710
- [Alibaba Serverless](https://www.alibabacloud.com/help/doc-detail/156876.htm)
811
- [bluebird](https://www.npmjs.com/package/bluebird)

javascript/ql/src/javascript.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import semmle.javascript.YAML
6565
import semmle.javascript.dataflow.DataFlow
6666
import semmle.javascript.dataflow.TaintTracking
6767
import semmle.javascript.dataflow.TypeInference
68+
import semmle.javascript.frameworks.Angular2
6869
import semmle.javascript.frameworks.AngularJS
6970
import semmle.javascript.frameworks.AsyncPackage
7071
import semmle.javascript.frameworks.AWS
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
/**
2+
* Provides classes for working with Angular (also known as Angular 2.x) applications.
3+
*/
4+
5+
private import javascript
6+
private import semmle.javascript.security.dataflow.Xss
7+
private import semmle.javascript.security.dataflow.CodeInjectionCustomizations
8+
private import semmle.javascript.security.dataflow.ClientSideUrlRedirectCustomizations
9+
private import semmle.javascript.DynamicPropertyAccess
10+
11+
/**
12+
* Provides classes for working with Angular (also known as Angular 2.x) applications.
13+
*/
14+
module Angular2 {
15+
/** Gets a reference to a `Router` object. */
16+
DataFlow::SourceNode router() { result.hasUnderlyingType("@angular/router", "Router") }
17+
18+
/** Gets a reference to a `RouterState` object. */
19+
DataFlow::SourceNode routerState() {
20+
result.hasUnderlyingType("@angular/router", "RouterState")
21+
or
22+
result = router().getAPropertyRead("routerState")
23+
}
24+
25+
/** Gets a reference to a `RouterStateSnapshot` object. */
26+
DataFlow::SourceNode routerStateSnapshot() {
27+
result.hasUnderlyingType("@angular/router", "RouterStateSnapshot")
28+
or
29+
result = routerState().getAPropertyRead("snapshot")
30+
}
31+
32+
/** Gets a reference to an `ActivatedRoute` object. */
33+
DataFlow::SourceNode activatedRoute() {
34+
result.hasUnderlyingType("@angular/router", "ActivatedRoute")
35+
}
36+
37+
/** Gets a reference to an `ActivatedRouteSnapshot` object. */
38+
DataFlow::SourceNode activatedRouteSnapshot() {
39+
result.hasUnderlyingType("@angular/router", "ActivatedRouteSnapshot")
40+
or
41+
result = activatedRoute().getAPropertyRead("snapshot")
42+
}
43+
44+
/**
45+
* Gets a data flow node referring to the value of the route property `name`, accessed
46+
* via one of the following patterns:
47+
* ```js
48+
* route.snapshot.name
49+
* route.snapshot.data.name
50+
* route.name.subscribe(x => ...)
51+
* ```
52+
*/
53+
DataFlow::SourceNode activatedRouteProp(string name) {
54+
// this.route.snapshot.foo
55+
result = activatedRouteSnapshot().getAPropertyRead(name)
56+
or
57+
// this.route.snapshot.data.foo
58+
result = activatedRouteSnapshot().getAPropertyRead("data").getAPropertyRead(name)
59+
or
60+
// this.route.foo.subscribe(foo => { ... })
61+
result =
62+
activatedRoute()
63+
.getAPropertyRead(name)
64+
.getAMethodCall("subscribe")
65+
.getABoundCallbackParameter(0, 0)
66+
}
67+
68+
/** Gets an array of URL segments matched by some route. */
69+
private DataFlow::SourceNode urlSegmentArray() { result = activatedRouteProp("url") }
70+
71+
/** Gets a data flow node referring to a `UrlSegment` object matched by some route. */
72+
DataFlow::SourceNode urlSegment() {
73+
result = getAnEnumeratedArrayElement(urlSegmentArray())
74+
or
75+
result = urlSegmentArray().getAPropertyRead(any(string s | exists(s.toInt())))
76+
}
77+
78+
/** Gets a reference to a `ParamMap` object, usually containing values from the URL. */
79+
DataFlow::SourceNode paramMap() {
80+
result.hasUnderlyingType("@angular/router", "ParamMap")
81+
or
82+
result = activatedRouteProp(["paramMap", "queryParamMap"])
83+
or
84+
result = urlSegment().getAPropertyRead("parameterMap")
85+
}
86+
87+
/** Gets a reference to a `Params` object, usually containing values from the URL. */
88+
DataFlow::SourceNode paramDictionaryObject() {
89+
result.hasUnderlyingType("@angular/router", "Params") and
90+
not result instanceof DataFlow::ObjectLiteralNode // ignore object literals found by contextual typing
91+
or
92+
result = activatedRouteProp(["params", "queryParams"])
93+
or
94+
result = paramMap().getAPropertyRead("params")
95+
or
96+
result = urlSegment().getAPropertyRead("parameters")
97+
}
98+
99+
/**
100+
* A value from `@angular/router` derived from the URL.
101+
*/
102+
class AngularSource extends RemoteFlowSource {
103+
AngularSource() {
104+
this = paramMap().getAMethodCall(["get", "getAll"])
105+
or
106+
this = paramDictionaryObject()
107+
or
108+
this = activatedRouteProp("fragment")
109+
or
110+
this = urlSegment().getAPropertyRead("path")
111+
or
112+
// Note that Router.url and RouterStateSnapshot.url are strings, not UrlSegment[]
113+
this = router().getAPropertyRead("url")
114+
or
115+
this = routerStateSnapshot().getAPropertyRead("url")
116+
}
117+
118+
override string getSourceType() { result = "Angular route parameter" }
119+
}
120+
121+
/** Gets a reference to a `DomSanitizer` object. */
122+
DataFlow::SourceNode domSanitizer() {
123+
result.hasUnderlyingType("@angular/platform-browser", "DomSanitizer")
124+
}
125+
126+
/** A value that is about to be promoted to a trusted HTML or CSS value. */
127+
private class AngularXssSink extends DomBasedXss::Sink {
128+
AngularXssSink() {
129+
this =
130+
domSanitizer()
131+
.getAMethodCall(["bypassSecurityTrustHtml", "bypassSecurityTrustStyle"])
132+
.getArgument(0)
133+
}
134+
}
135+
136+
/** A value that is about to be promoted to a trusted script value. */
137+
private class AngularCodeInjectionSink extends CodeInjection::Sink {
138+
AngularCodeInjectionSink() {
139+
this = domSanitizer().getAMethodCall(["bypassSecurityTrustScript"]).getArgument(0)
140+
}
141+
}
142+
143+
/**
144+
* A value that is about to be promoted to a trusted URL or resource URL value.
145+
*/
146+
private class AngularUrlSink extends ClientSideUrlRedirect::Sink {
147+
// We mark this as a client URL redirect sink for precision reasons, though its description can be a bit confusing.
148+
AngularUrlSink() {
149+
this =
150+
domSanitizer()
151+
.getAMethodCall(["bypassSecurityTrustUrl", "bypassSecurityTrustResourceUrl"])
152+
.getArgument(0)
153+
}
154+
}
155+
156+
private predicate taintStep(DataFlow::Node pred, DataFlow::Node succ) {
157+
exists(DataFlow::CallNode call |
158+
call = DataFlow::moduleMember("@angular/router", "convertToParamMap").getACall()
159+
or
160+
call = router().getAMemberCall(["parseUrl", "serializeUrl"])
161+
|
162+
pred = call.getArgument(0) and
163+
succ = call
164+
)
165+
}
166+
167+
private class AngularTaintStep extends TaintTracking::AdditionalTaintStep {
168+
AngularTaintStep() { taintStep(_, this) }
169+
170+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) { taintStep(pred, succ) }
171+
}
172+
173+
/** Gets a reference to an `HttpClient` object. */
174+
DataFlow::SourceNode httpClient() {
175+
result.hasUnderlyingType("@angular/common/http", "HttpClient")
176+
}
177+
178+
private class AngularClientRequest extends ClientRequest::Range, DataFlow::MethodCallNode {
179+
int argumentOffset;
180+
181+
AngularClientRequest() {
182+
this = httpClient().getAMethodCall("request") and argumentOffset = 1
183+
or
184+
this = httpClient().getAMethodCall() and
185+
not getMethodName() = "request" and
186+
argumentOffset = 0
187+
}
188+
189+
override DataFlow::Node getUrl() { result = getArgument(argumentOffset) }
190+
191+
override DataFlow::Node getHost() { none() }
192+
193+
override DataFlow::Node getADataNode() {
194+
getMethodName() = ["patch", "post", "put"] and
195+
result = getArgument(argumentOffset + 1)
196+
or
197+
result = getOptionArgument(argumentOffset + 1, "body")
198+
}
199+
}
200+
201+
private string getInternalName(string name) {
202+
exists(Identifier id |
203+
result = id.getName() and
204+
name = result.regexpCapture("\\u0275(DomAdapter|getDOM)", 1)
205+
)
206+
}
207+
208+
/** Gets a reference to a `DomAdapter`, which provides acess to raw DOM elements. */
209+
private DataFlow::SourceNode domAdapter() {
210+
// Note: these are internal properties, prefixed with the "latin small letter barred O (U+0275)" character.
211+
// Despite being internal, some codebases do access them.
212+
result.hasUnderlyingType("@angular/common", getInternalName("DomAdapter"))
213+
or
214+
result = DataFlow::moduleImport("@angular/common").getAMemberCall(getInternalName("getDOM"))
215+
}
216+
217+
/** A reference to the DOM location obtained through `DomAdapter.getLocation()`. */
218+
private class DomAdapterLocation extends DOM::LocationSource::Range {
219+
DomAdapterLocation() { this = domAdapter().getAMethodCall("getLocation") }
220+
}
221+
}

javascript/ql/src/semmle/javascript/security/dataflow/ClientSideUrlRedirect.qll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ import UrlConcatenation
1414
module ClientSideUrlRedirect {
1515
import ClientSideUrlRedirectCustomizations::ClientSideUrlRedirect
1616

17+
// Materialize flow labels
18+
private class ConcreteDocumentUrl extends DocumentUrl {
19+
ConcreteDocumentUrl() { this = this }
20+
}
21+
1722
/**
1823
* A taint-tracking configuration for reasoning about unvalidated URL redirections.
1924
*/

javascript/ql/src/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
import javascript
88
import semmle.javascript.security.dataflow.RemoteFlowSources
9-
private import UrlConcatenation
109

1110
module ClientSideUrlRedirect {
1211
private import Xss::DomBasedXss as DomBasedXss
@@ -30,7 +29,7 @@ module ClientSideUrlRedirect {
3029
* A flow label for values that represent the URL of the current document, and
3130
* hence are only partially user-controlled.
3231
*/
33-
class DocumentUrl extends DataFlow::FlowLabel {
32+
abstract class DocumentUrl extends DataFlow::FlowLabel {
3433
DocumentUrl() { this = "document.url" }
3534
}
3635

javascript/ql/src/semmle/javascript/security/dataflow/InsecureDownload.qll

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ import javascript
1414
module InsecureDownload {
1515
import InsecureDownloadCustomizations::InsecureDownload
1616

17+
// Materialize flow labels
18+
private class ConcreteSensitiveInsecureURL extends Label::SensitiveInsecureURL {
19+
ConcreteSensitiveInsecureURL() { this = this }
20+
}
21+
22+
private class ConcreteInsecureURL extends Label::InsecureURL {
23+
ConcreteInsecureURL() { this = this }
24+
}
25+
1726
/**
1827
* A taint tracking configuration for download of sensitive file through insecure connection.
1928
*/

javascript/ql/src/semmle/javascript/security/dataflow/PostMessageStar.qll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ import javascript
1212
module PostMessageStar {
1313
import PostMessageStarCustomizations::PostMessageStar
1414

15+
// Materialize flow labels
16+
private class ConcretePartiallyTaintedObject extends PartiallyTaintedObject {
17+
ConcretePartiallyTaintedObject() { this = this }
18+
}
19+
1520
/**
1621
* A taint tracking configuration for cross-window communication with unrestricted origin.
1722
*

javascript/ql/src/semmle/javascript/security/dataflow/PostMessageStarCustomizations.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ module PostMessageStar {
2626
/**
2727
* A flow label representing an object with at least one tainted property.
2828
*/
29-
class PartiallyTaintedObject extends DataFlow::FlowLabel {
29+
abstract class PartiallyTaintedObject extends DataFlow::FlowLabel {
3030
PartiallyTaintedObject() { this = "partially tainted object" }
3131
}
3232

javascript/ql/src/semmle/javascript/security/dataflow/PrototypePollution.qll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ import semmle.javascript.dependencies.SemVer
1515
module PrototypePollution {
1616
import PrototypePollutionCustomizations::PrototypePollution
1717

18+
// Materialize flow labels
19+
private class ConcreteTaintedObjectWrapper extends TaintedObjectWrapper {
20+
ConcreteTaintedObjectWrapper() { this = this }
21+
}
22+
1823
/**
1924
* A taint tracking configuration for user-controlled objects flowing into deep `extend` calls,
2025
* leading to prototype pollution.

javascript/ql/src/semmle/javascript/security/dataflow/PrototypePollutionCustomizations.qll

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ module PrototypePollution {
2424
* }
2525
* ```
2626
*/
27-
module TaintedObjectWrapper {
28-
private class TaintedObjectWrapper extends DataFlow::FlowLabel {
29-
TaintedObjectWrapper() { this = "tainted-object-wrapper" }
30-
}
27+
abstract class TaintedObjectWrapper extends DataFlow::FlowLabel {
28+
TaintedObjectWrapper() { this = "tainted-object-wrapper" }
29+
}
3130

31+
/** Companion module to the `TaintedObjectWrapper` class. */
32+
module TaintedObjectWrapper {
33+
/** Gets the instance of the `TaintedObjectWrapper` label. */
3234
TaintedObjectWrapper label() { any() }
3335
}
3436

0 commit comments

Comments
 (0)