JS: Add Angular2 model

This commit is contained in:
Asger Feldthaus
2020-09-29 12:01:47 +01:00
parent a92a701c35
commit afd82e202d
4 changed files with 343 additions and 0 deletions

View File

@@ -65,6 +65,7 @@ import semmle.javascript.YAML
import semmle.javascript.dataflow.DataFlow
import semmle.javascript.dataflow.TaintTracking
import semmle.javascript.dataflow.TypeInference
import semmle.javascript.frameworks.Angular2
import semmle.javascript.frameworks.AngularJS
import semmle.javascript.frameworks.AsyncPackage
import semmle.javascript.frameworks.AWS

View File

@@ -0,0 +1,214 @@
/**
* Provides classes for working with Angular (also known as Angular 2.x) applications.
*/
private import javascript
private import semmle.javascript.security.dataflow.Xss
private import semmle.javascript.security.dataflow.CodeInjectionCustomizations
private import semmle.javascript.security.dataflow.ClientSideUrlRedirectCustomizations
private import semmle.javascript.DynamicPropertyAccess
/**
* Provides classes for working with Angular (also known as Angular 2.x) applications.
*/
module Angular2 {
/** Gets a reference to a `Router` object. */
DataFlow::SourceNode router() {
result.hasUnderlyingType("@angular/router", "Router")
}
/** Gets a reference to a `RouterState` object. */
DataFlow::SourceNode routerState() {
result.hasUnderlyingType("@angular/router", "RouterState")
or
result = router().getAPropertyRead("routerState")
}
/** Gets a reference to a `RouterStateSnapshot` object. */
DataFlow::SourceNode routerStateSnapshot() {
result.hasUnderlyingType("@angular/router", "RouterStateSnapshot")
or
result = routerState().getAPropertyRead("snapshot")
}
/** Gets a reference to an `ActivatedRoute` object. */
DataFlow::SourceNode activatedRoute() {
result.hasUnderlyingType("@angular/router", "ActivatedRoute")
}
/** Gets a reference to an `ActivatedRouteSnapshot` object. */
DataFlow::SourceNode activatedRouteSnapshot() {
result.hasUnderlyingType("@angular/router", "ActivatedRouteSnapshot")
or
result = activatedRoute().getAPropertyRead("snapshot")
}
/**
* Gets a data flow node referring to the value of the route property `name`, accessed
* via one of the following patterns:
* ```js
* route.snapshot.name
* route.snapshot.data.name
* route.name.subscribe(x => ...)
* ```
*/
DataFlow::SourceNode activatedRouteProp(string name) {
// this.route.snapshot.foo
result = activatedRouteSnapshot().getAPropertyRead(name)
or
// this.route.snapshot.data.foo
result = activatedRouteSnapshot().getAPropertyRead("data").getAPropertyRead(name)
or
// this.route.foo.subscribe(foo => { ... })
result = activatedRoute().getAPropertyRead(name).getAMethodCall("subscribe").getABoundCallbackParameter(0, 0)
}
/** Gets an array of URL segments matched by some route. */
private DataFlow::SourceNode urlSegmentArray() {
result = activatedRouteProp("url")
}
/** Gets a data flow node referring to a `UrlSegment` object matched by some route. */
DataFlow::SourceNode urlSegment() {
result = getAnEnumeratedArrayElement(urlSegmentArray())
or
result = urlSegmentArray().getAPropertyRead(any(string s | exists(s.toInt())))
}
/** Gets a reference to a `ParamMap` object, usually containing values from the URL. */
DataFlow::SourceNode paramMap() {
result.hasUnderlyingType("@angular/router", "ParamMap")
or
result = activatedRouteProp(["paramMap", "queryParamMap"])
or
result = urlSegment().getAPropertyRead("parameterMap")
}
/** Gets a reference to a `Params` object, usually containing values from the URL. */
DataFlow::SourceNode paramDictionaryObject() {
result.hasUnderlyingType("@angular/router", "Params") and
not result instanceof DataFlow::ObjectLiteralNode // ignore object literals found by contextual typing
or
result = activatedRouteProp(["params", "queryParams"])
or
result = paramMap().getAPropertyRead("params")
or
result = urlSegment().getAPropertyRead("parameters")
}
/**
* A value from `@angular/router` derived from the URL.
*/
class AngularSource extends RemoteFlowSource {
AngularSource() {
this = paramMap().getAMethodCall(["get", "getAll"])
or
this = paramDictionaryObject()
or
this = activatedRouteProp("fragment")
or
this = urlSegment().getAPropertyRead("path")
or
// Note that Router.url and RouterStateSnapshot.url are strings, not UrlSegment[]
this = router().getAPropertyRead("url")
or
this = routerStateSnapshot().getAPropertyRead("url")
}
override string getSourceType() {
result = "Angular route parameter"
}
}
/** Gets a reference to a `DomSanitizer` object. */
DataFlow::SourceNode domSanitizer() {
result.hasUnderlyingType("@angular/platform-browser", "DomSanitizer")
}
/** A value that is about to be promoted to a trusted HTML or CSS value. */
private class AngularXssSink extends DomBasedXss::Sink {
AngularXssSink() { this = domSanitizer().getAMethodCall(["bypassSecurityTrustHtml", "bypassSecurityTrustStyle"]).getArgument(0) }
}
/** A value that is about to be promoted to a trusted script value. */
private class AngularCodeInjectionSink extends CodeInjection::Sink {
AngularCodeInjectionSink() { this = domSanitizer().getAMethodCall(["bypassSecurityTrustScript"]).getArgument(0) }
}
/**
* A value that is about to be promoted to a trusted URL or resource URL value.
*/
private class AngularUrlSink extends ClientSideUrlRedirect::Sink {
// We mark this as a client URL redirect sink for precision reasons, though its description can be a bit confusing.
AngularUrlSink() { this = domSanitizer().getAMethodCall(["bypassSecurityTrustUrl", "bypassSecurityTrustResourceUrl"]).getArgument(0) }
}
private predicate taintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::CallNode call |
call = DataFlow::moduleMember("@angular/router", "convertToParamMap").getACall()
or
call = router().getAMemberCall(["parseUrl", "serializeUrl"])
|
pred = call.getArgument(0) and
succ = call
)
}
private class AngularTaintStep extends TaintTracking::AdditionalTaintStep {
AngularTaintStep() {
taintStep(_, this)
}
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
taintStep(pred, succ)
}
}
/** Gets a reference to an `HttpClient` object. */
DataFlow::SourceNode httpClient() {
result.hasUnderlyingType("@angular/common/http", "HttpClient")
}
private class AngularClientRequest extends ClientRequest::Range, DataFlow::MethodCallNode {
int argumentOffset;
AngularClientRequest() {
this = httpClient().getAMethodCall("request") and argumentOffset = 1
or
this = httpClient().getAMethodCall() and
not getMethodName() = "request" and
argumentOffset = 0
}
override DataFlow::Node getUrl() {
result = getArgument(argumentOffset)
}
override DataFlow::Node getHost() {
none()
}
override DataFlow::Node getADataNode() {
getMethodName() = ["patch", "post", "put"] and
result = getArgument(argumentOffset + 1)
or
result = getOptionArgument(argumentOffset + 1, "body")
}
}
/** Gets a reference to a `DomAdapter`, which provides acess to raw DOM elements. */
private DataFlow::SourceNode domAdapter() {
// Note: these are internal properties, prefixed with the theta character "ɵ".
// Despite being internal, some codebases do access them.
result.hasUnderlyingType("@angular/common", "ɵDomAdapter")
or
result = DataFlow::moduleImport("@angular/common").getAMemberCall("ɵgetDOM")
}
/** A reference to the DOM location obtained through `DomAdapter.getLocation()`. */
private class DomAdapterLocation extends DOM::LocationSource::Range {
DomAdapterLocation() {
this = domAdapter().getAMethodCall("getLocation")
}
}
}

View File

@@ -15,6 +15,50 @@ nodes
| addEventListener.js:12:24:12:28 | event |
| addEventListener.js:12:24:12:33 | event.data |
| addEventListener.js:12:24:12:33 | event.data |
| angular2-client.ts:21:44:21:66 | \\u0275getDOM ... ation() |
| angular2-client.ts:21:44:21:66 | \\u0275getDOM ... ation() |
| angular2-client.ts:21:44:21:71 | \\u0275getDOM ... ().href |
| angular2-client.ts:21:44:21:71 | \\u0275getDOM ... ().href |
| angular2-client.ts:23:44:23:69 | this.ro ... .params |
| angular2-client.ts:23:44:23:69 | this.ro ... .params |
| angular2-client.ts:23:44:23:73 | this.ro ... ams.foo |
| angular2-client.ts:23:44:23:73 | this.ro ... ams.foo |
| angular2-client.ts:24:44:24:74 | this.ro ... yParams |
| angular2-client.ts:24:44:24:74 | this.ro ... yParams |
| angular2-client.ts:24:44:24:78 | this.ro ... ams.foo |
| angular2-client.ts:24:44:24:78 | this.ro ... ams.foo |
| angular2-client.ts:25:44:25:71 | this.ro ... ragment |
| angular2-client.ts:25:44:25:71 | this.ro ... ragment |
| angular2-client.ts:25:44:25:71 | this.ro ... ragment |
| angular2-client.ts:26:44:26:82 | this.ro ... ('foo') |
| angular2-client.ts:26:44:26:82 | this.ro ... ('foo') |
| angular2-client.ts:26:44:26:82 | this.ro ... ('foo') |
| angular2-client.ts:27:44:27:87 | this.ro ... ('foo') |
| angular2-client.ts:27:44:27:87 | this.ro ... ('foo') |
| angular2-client.ts:27:44:27:87 | this.ro ... ('foo') |
| angular2-client.ts:29:46:29:59 | map.get('foo') |
| angular2-client.ts:29:46:29:59 | map.get('foo') |
| angular2-client.ts:29:46:29:59 | map.get('foo') |
| angular2-client.ts:32:44:32:74 | this.ro ... 1].path |
| angular2-client.ts:32:44:32:74 | this.ro ... 1].path |
| angular2-client.ts:32:44:32:74 | this.ro ... 1].path |
| angular2-client.ts:33:44:33:80 | this.ro ... ameters |
| angular2-client.ts:33:44:33:80 | this.ro ... ameters |
| angular2-client.ts:33:44:33:82 | this.ro ... eters.x |
| angular2-client.ts:33:44:33:82 | this.ro ... eters.x |
| angular2-client.ts:34:44:34:91 | this.ro ... et('x') |
| angular2-client.ts:34:44:34:91 | this.ro ... et('x') |
| angular2-client.ts:34:44:34:91 | this.ro ... et('x') |
| angular2-client.ts:35:44:35:89 | this.ro ... .params |
| angular2-client.ts:35:44:35:89 | this.ro ... .params |
| angular2-client.ts:35:44:35:91 | this.ro ... arams.x |
| angular2-client.ts:35:44:35:91 | this.ro ... arams.x |
| angular2-client.ts:37:44:37:58 | this.router.url |
| angular2-client.ts:37:44:37:58 | this.router.url |
| angular2-client.ts:37:44:37:58 | this.router.url |
| angular2-client.ts:41:44:41:76 | routeSn ... ('foo') |
| angular2-client.ts:41:44:41:76 | routeSn ... ('foo') |
| angular2-client.ts:41:44:41:76 | routeSn ... ('foo') |
| exception-xss.js:2:6:2:28 | foo |
| exception-xss.js:2:12:2:28 | document.location |
| exception-xss.js:2:12:2:28 | document.location |
@@ -505,6 +549,34 @@ edges
| addEventListener.js:10:21:10:25 | event | addEventListener.js:12:24:12:28 | event |
| addEventListener.js:12:24:12:28 | event | addEventListener.js:12:24:12:33 | event.data |
| addEventListener.js:12:24:12:28 | event | addEventListener.js:12:24:12:33 | event.data |
| angular2-client.ts:21:44:21:66 | \\u0275getDOM ... ation() | angular2-client.ts:21:44:21:71 | \\u0275getDOM ... ().href |
| angular2-client.ts:21:44:21:66 | \\u0275getDOM ... ation() | angular2-client.ts:21:44:21:71 | \\u0275getDOM ... ().href |
| angular2-client.ts:21:44:21:66 | \\u0275getDOM ... ation() | angular2-client.ts:21:44:21:71 | \\u0275getDOM ... ().href |
| angular2-client.ts:21:44:21:66 | \\u0275getDOM ... ation() | angular2-client.ts:21:44:21:71 | \\u0275getDOM ... ().href |
| angular2-client.ts:23:44:23:69 | this.ro ... .params | angular2-client.ts:23:44:23:73 | this.ro ... ams.foo |
| angular2-client.ts:23:44:23:69 | this.ro ... .params | angular2-client.ts:23:44:23:73 | this.ro ... ams.foo |
| angular2-client.ts:23:44:23:69 | this.ro ... .params | angular2-client.ts:23:44:23:73 | this.ro ... ams.foo |
| angular2-client.ts:23:44:23:69 | this.ro ... .params | angular2-client.ts:23:44:23:73 | this.ro ... ams.foo |
| angular2-client.ts:24:44:24:74 | this.ro ... yParams | angular2-client.ts:24:44:24:78 | this.ro ... ams.foo |
| angular2-client.ts:24:44:24:74 | this.ro ... yParams | angular2-client.ts:24:44:24:78 | this.ro ... ams.foo |
| angular2-client.ts:24:44:24:74 | this.ro ... yParams | angular2-client.ts:24:44:24:78 | this.ro ... ams.foo |
| angular2-client.ts:24:44:24:74 | this.ro ... yParams | angular2-client.ts:24:44:24:78 | this.ro ... ams.foo |
| angular2-client.ts:25:44:25:71 | this.ro ... ragment | angular2-client.ts:25:44:25:71 | this.ro ... ragment |
| angular2-client.ts:26:44:26:82 | this.ro ... ('foo') | angular2-client.ts:26:44:26:82 | this.ro ... ('foo') |
| angular2-client.ts:27:44:27:87 | this.ro ... ('foo') | angular2-client.ts:27:44:27:87 | this.ro ... ('foo') |
| angular2-client.ts:29:46:29:59 | map.get('foo') | angular2-client.ts:29:46:29:59 | map.get('foo') |
| angular2-client.ts:32:44:32:74 | this.ro ... 1].path | angular2-client.ts:32:44:32:74 | this.ro ... 1].path |
| angular2-client.ts:33:44:33:80 | this.ro ... ameters | angular2-client.ts:33:44:33:82 | this.ro ... eters.x |
| angular2-client.ts:33:44:33:80 | this.ro ... ameters | angular2-client.ts:33:44:33:82 | this.ro ... eters.x |
| angular2-client.ts:33:44:33:80 | this.ro ... ameters | angular2-client.ts:33:44:33:82 | this.ro ... eters.x |
| angular2-client.ts:33:44:33:80 | this.ro ... ameters | angular2-client.ts:33:44:33:82 | this.ro ... eters.x |
| angular2-client.ts:34:44:34:91 | this.ro ... et('x') | angular2-client.ts:34:44:34:91 | this.ro ... et('x') |
| angular2-client.ts:35:44:35:89 | this.ro ... .params | angular2-client.ts:35:44:35:91 | this.ro ... arams.x |
| angular2-client.ts:35:44:35:89 | this.ro ... .params | angular2-client.ts:35:44:35:91 | this.ro ... arams.x |
| angular2-client.ts:35:44:35:89 | this.ro ... .params | angular2-client.ts:35:44:35:91 | this.ro ... arams.x |
| angular2-client.ts:35:44:35:89 | this.ro ... .params | angular2-client.ts:35:44:35:91 | this.ro ... arams.x |
| angular2-client.ts:37:44:37:58 | this.router.url | angular2-client.ts:37:44:37:58 | this.router.url |
| angular2-client.ts:41:44:41:76 | routeSn ... ('foo') | angular2-client.ts:41:44:41:76 | routeSn ... ('foo') |
| exception-xss.js:2:6:2:28 | foo | exception-xss.js:86:17:86:19 | foo |
| exception-xss.js:2:6:2:28 | foo | exception-xss.js:86:17:86:19 | foo |
| exception-xss.js:2:12:2:28 | document.location | exception-xss.js:2:6:2:28 | foo |
@@ -937,6 +1009,19 @@ edges
| addEventListener.js:2:20:2:29 | event.data | addEventListener.js:1:43:1:47 | event | addEventListener.js:2:20:2:29 | event.data | Cross-site scripting vulnerability due to $@. | addEventListener.js:1:43:1:47 | event | user-provided value |
| addEventListener.js:6:20:6:23 | data | addEventListener.js:5:43:5:48 | {data} | addEventListener.js:6:20:6:23 | data | Cross-site scripting vulnerability due to $@. | addEventListener.js:5:43:5:48 | {data} | user-provided value |
| addEventListener.js:12:24:12:33 | event.data | addEventListener.js:10:21:10:25 | event | addEventListener.js:12:24:12:33 | event.data | Cross-site scripting vulnerability due to $@. | addEventListener.js:10:21:10:25 | event | user-provided value |
| angular2-client.ts:21:44:21:71 | \\u0275getDOM ... ().href | angular2-client.ts:21:44:21:66 | \\u0275getDOM ... ation() | angular2-client.ts:21:44:21:71 | \\u0275getDOM ... ().href | Cross-site scripting vulnerability due to $@. | angular2-client.ts:21:44:21:66 | \\u0275getDOM ... ation() | user-provided value |
| angular2-client.ts:23:44:23:73 | this.ro ... ams.foo | angular2-client.ts:23:44:23:69 | this.ro ... .params | angular2-client.ts:23:44:23:73 | this.ro ... ams.foo | Cross-site scripting vulnerability due to $@. | angular2-client.ts:23:44:23:69 | this.ro ... .params | user-provided value |
| angular2-client.ts:24:44:24:78 | this.ro ... ams.foo | angular2-client.ts:24:44:24:74 | this.ro ... yParams | angular2-client.ts:24:44:24:78 | this.ro ... ams.foo | Cross-site scripting vulnerability due to $@. | angular2-client.ts:24:44:24:74 | this.ro ... yParams | user-provided value |
| angular2-client.ts:25:44:25:71 | this.ro ... ragment | angular2-client.ts:25:44:25:71 | this.ro ... ragment | angular2-client.ts:25:44:25:71 | this.ro ... ragment | Cross-site scripting vulnerability due to $@. | angular2-client.ts:25:44:25:71 | this.ro ... ragment | user-provided value |
| angular2-client.ts:26:44:26:82 | this.ro ... ('foo') | angular2-client.ts:26:44:26:82 | this.ro ... ('foo') | angular2-client.ts:26:44:26:82 | this.ro ... ('foo') | Cross-site scripting vulnerability due to $@. | angular2-client.ts:26:44:26:82 | this.ro ... ('foo') | user-provided value |
| angular2-client.ts:27:44:27:87 | this.ro ... ('foo') | angular2-client.ts:27:44:27:87 | this.ro ... ('foo') | angular2-client.ts:27:44:27:87 | this.ro ... ('foo') | Cross-site scripting vulnerability due to $@. | angular2-client.ts:27:44:27:87 | this.ro ... ('foo') | user-provided value |
| angular2-client.ts:29:46:29:59 | map.get('foo') | angular2-client.ts:29:46:29:59 | map.get('foo') | angular2-client.ts:29:46:29:59 | map.get('foo') | Cross-site scripting vulnerability due to $@. | angular2-client.ts:29:46:29:59 | map.get('foo') | user-provided value |
| angular2-client.ts:32:44:32:74 | this.ro ... 1].path | angular2-client.ts:32:44:32:74 | this.ro ... 1].path | angular2-client.ts:32:44:32:74 | this.ro ... 1].path | Cross-site scripting vulnerability due to $@. | angular2-client.ts:32:44:32:74 | this.ro ... 1].path | user-provided value |
| angular2-client.ts:33:44:33:82 | this.ro ... eters.x | angular2-client.ts:33:44:33:80 | this.ro ... ameters | angular2-client.ts:33:44:33:82 | this.ro ... eters.x | Cross-site scripting vulnerability due to $@. | angular2-client.ts:33:44:33:80 | this.ro ... ameters | user-provided value |
| angular2-client.ts:34:44:34:91 | this.ro ... et('x') | angular2-client.ts:34:44:34:91 | this.ro ... et('x') | angular2-client.ts:34:44:34:91 | this.ro ... et('x') | Cross-site scripting vulnerability due to $@. | angular2-client.ts:34:44:34:91 | this.ro ... et('x') | user-provided value |
| angular2-client.ts:35:44:35:91 | this.ro ... arams.x | angular2-client.ts:35:44:35:89 | this.ro ... .params | angular2-client.ts:35:44:35:91 | this.ro ... arams.x | Cross-site scripting vulnerability due to $@. | angular2-client.ts:35:44:35:89 | this.ro ... .params | user-provided value |
| angular2-client.ts:37:44:37:58 | this.router.url | angular2-client.ts:37:44:37:58 | this.router.url | angular2-client.ts:37:44:37:58 | this.router.url | Cross-site scripting vulnerability due to $@. | angular2-client.ts:37:44:37:58 | this.router.url | user-provided value |
| angular2-client.ts:41:44:41:76 | routeSn ... ('foo') | angular2-client.ts:41:44:41:76 | routeSn ... ('foo') | angular2-client.ts:41:44:41:76 | routeSn ... ('foo') | Cross-site scripting vulnerability due to $@. | angular2-client.ts:41:44:41:76 | routeSn ... ('foo') | user-provided value |
| exception-xss.js:86:17:86:19 | foo | exception-xss.js:2:12:2:28 | document.location | exception-xss.js:86:17:86:19 | foo | Cross-site scripting vulnerability due to $@. | exception-xss.js:2:12:2:28 | document.location | user-provided value |
| jquery.js:4:5:4:11 | tainted | jquery.js:2:17:2:33 | document.location | jquery.js:4:5:4:11 | tainted | Cross-site scripting vulnerability due to $@. | jquery.js:2:17:2:33 | document.location | user-provided value |
| jquery.js:7:5:7:34 | "<div i ... + "\\">" | jquery.js:2:17:2:33 | document.location | jquery.js:7:5:7:34 | "<div i ... + "\\">" | Cross-site scripting vulnerability due to $@. | jquery.js:2:17:2:33 | document.location | user-provided value |

View File

@@ -0,0 +1,43 @@
import { Component, OnInit } from '@angular/core';
import { ɵgetDOM } from '@angular/common';
import { ActivatedRoute, ActivatedRouteSnapshot, Router } from '@angular/router';
import { DomSanitizer } from '@angular/platform-browser';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
title = 'my-app';
constructor(
private route: ActivatedRoute,
private sanitizer: DomSanitizer,
private router: Router
) {}
ngOnInit() {
this.sanitizer.bypassSecurityTrustHtml(ɵgetDOM().getLocation().href); // NOT OK
this.sanitizer.bypassSecurityTrustHtml(this.route.snapshot.params.foo); // NOT OK
this.sanitizer.bypassSecurityTrustHtml(this.route.snapshot.queryParams.foo); // NOT OK
this.sanitizer.bypassSecurityTrustHtml(this.route.snapshot.fragment); // NOT OK
this.sanitizer.bypassSecurityTrustHtml(this.route.snapshot.paramMap.get('foo')); // NOT OK
this.sanitizer.bypassSecurityTrustHtml(this.route.snapshot.queryParamMap.get('foo')); // NOT OK
this.route.paramMap.subscribe(map => {
this.sanitizer.bypassSecurityTrustHtml(map.get('foo')); // NOT OK
});
this.sanitizer.bypassSecurityTrustHtml(this.route.snapshot.url[1].path); // NOT OK - though depends on route config
this.sanitizer.bypassSecurityTrustHtml(this.route.snapshot.url[1].parameters.x); // NOT OK
this.sanitizer.bypassSecurityTrustHtml(this.route.snapshot.url[1].parameterMap.get('x')); // NOT OK
this.sanitizer.bypassSecurityTrustHtml(this.route.snapshot.url[1].parameterMap.params.x); // NOT OK
this.sanitizer.bypassSecurityTrustHtml(this.router.url); // NOT OK
}
someMethod(routeSnapshot: ActivatedRouteSnapshot) {
this.sanitizer.bypassSecurityTrustHtml(routeSnapshot.paramMap.get('foo')); // NOT OK
}
}