Skip to content

Commit 3be219c

Browse files
authored
Merge pull request #17243 from asgerf/js/post-message-source-client-side
JS: Classify post-message events as client side taint sources
2 parents a25d9c7 + 7a7ab45 commit 3be219c

File tree

12 files changed

+2633
-2633
lines changed

12 files changed

+2633
-2633
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,15 @@ module ClientSideUrlRedirect {
237237
override predicate isXssSink() { any() }
238238
}
239239

240+
/**
241+
* A `templateUrl` member of an AngularJS directive.
242+
*/
243+
private class AngularJSTemplateUrlSink extends Sink {
244+
AngularJSTemplateUrlSink() { this = any(AngularJS::CustomDirective d).getMember("templateUrl") }
245+
246+
override predicate isXssSink() { any() }
247+
}
248+
240249
private class SinkFromModel extends Sink {
241250
SinkFromModel() { this = ModelOutput::getASinkNode("url-redirection").asSink() }
242251
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,12 +207,14 @@ class PostMessageEventHandler extends Function {
207207
* An event parameter for a `postMessage` event handler, considered as an untrusted
208208
* source of data.
209209
*/
210-
private class PostMessageEventParameter extends RemoteFlowSource {
210+
private class PostMessageEventParameter extends ClientSideRemoteFlowSource {
211211
PostMessageEventParameter() {
212212
this = DataFlow::parameterNode(any(PostMessageEventHandler pmeh).getEventParameter())
213213
}
214214

215215
override string getSourceType() { result = "postMessage event" }
216+
217+
override ClientSideRemoteFlowKind getKind() { result.isMessageEvent() }
216218
}
217219

218220
/**

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ import Cached
4040
* A type of remote flow source that is specific to the browser environment.
4141
*/
4242
class ClientSideRemoteFlowKind extends string {
43-
ClientSideRemoteFlowKind() { this = ["query", "fragment", "path", "url", "name"] }
43+
ClientSideRemoteFlowKind() {
44+
this = ["query", "fragment", "path", "url", "name", "message-event"]
45+
}
4446

4547
/**
4648
* Holds if this is the `query` kind, describing sources derived from the query parameters of the browser URL,
@@ -77,6 +79,12 @@ class ClientSideRemoteFlowKind extends string {
7779

7880
/** Holds if this is the `name` kind, describing sources derived from the window name, such as `window.name`. */
7981
predicate isWindowName() { this = "name" }
82+
83+
/**
84+
* Holds if this is the `message-event` kind, describing sources derived from cross-window message passing,
85+
* such as `event` in `window.onmessage = event => {...}`.
86+
*/
87+
predicate isMessageEvent() { this = "message-event" }
8088
}
8189

8290
/**

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -653,10 +653,11 @@ module TaintedPath {
653653
}
654654

655655
/**
656-
* A `templateUrl` member of an AngularJS directive.
656+
* DEPRECATED. This is no longer seen as a path-injection sink. It is tentatively handled
657+
* by the client-side URL redirection query for now.
657658
*/
658-
class AngularJSTemplateUrlSink extends Sink, DataFlow::ValueNode {
659-
AngularJSTemplateUrlSink() { this = any(AngularJS::CustomDirective d).getMember("templateUrl") }
659+
deprecated class AngularJSTemplateUrlSink extends DataFlow::ValueNode instanceof Sink {
660+
AngularJSTemplateUrlSink() { none() }
660661
}
661662

662663
/**
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Message events in the browser are now properly classified as client-side taint sources. Previously they were
5+
incorrectly classified as server-side taint sources, which resulted in some alerts being reported by
6+
the wrong query, such as server-side URL redirection instead of client-side URL redirection.

javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected

Lines changed: 2535 additions & 2574 deletions
Large diffs are not rendered by default.

javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.js

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,6 @@ var server = http.createServer(function(req, res) {
6060
res.write(fs.readFileSync(pathModule.toNamespacedPath(path)));
6161
});
6262

63-
angular.module('myApp', [])
64-
.directive('myCustomer', function() {
65-
return {
66-
templateUrl: "SAFE" // OK
67-
}
68-
})
69-
.directive('myCustomer', function() {
70-
return {
71-
templateUrl: Cookie.get("unsafe") // NOT OK
72-
}
73-
})
74-
7563
var server = http.createServer(function(req, res) {
7664
// tests for a few uri-libraries
7765
res.write(fs.readFileSync(require("querystringify").parse(req.url).query)); // NOT OK
@@ -92,10 +80,6 @@ var server = http.createServer(function(req, res) {
9280

9381
})();
9482

95-
addEventListener('message', (ev) => {
96-
Cookie.set("unsafe", ev.data);
97-
});
98-
9983
var server = http.createServer(function(req, res) {
10084
let path = url.parse(req.url, true).query.path;
10185

@@ -110,25 +94,25 @@ var server = http.createServer(function(req, res) {
11094

11195
var server = http.createServer(function(req, res) {
11296
let path = url.parse(req.url, true).query.path;
113-
97+
11498
if (path) { // sanitization
11599
path = path.replace(/[\]\[*,;'"`<>\\?\/]/g, ''); // remove all invalid characters from states plus slashes
116100
path = path.replace(/\.\./g, ''); // remove all ".."
117101
}
118-
102+
119103
res.write(fs.readFileSync(path)); // OK. Is sanitized above.
120104
});
121105

122106
var server = http.createServer(function(req, res) {
123107
let path = url.parse(req.url, true).query.path;
124-
108+
125109
if (!path) {
126-
110+
127111
} else { // sanitization
128112
path = path.replace(/[\]\[*,;'"`<>\\?\/]/g, ''); // remove all invalid characters from states plus slashes
129113
path = path.replace(/\.\./g, ''); // remove all ".."
130114
}
131-
115+
132116
res.write(fs.readFileSync(path)); // OK. Is sanitized above.
133117
});
134118

@@ -142,29 +126,29 @@ var server = http.createServer(function(req, res) {
142126
let path = url.parse(req.url, true).query.path;
143127

144128
fs.readFileSync(path); // NOT OK
145-
129+
146130
var split = path.split("/");
147-
131+
148132
fs.readFileSync(split.join("/")); // NOT OK
149133

150134
fs.readFileSync(prefix + split[split.length - 1]) // OK
151135

152136
fs.readFileSync(split[x]) // NOT OK
153-
fs.readFileSync(prefix + split[x]) // NOT OK
137+
fs.readFileSync(prefix + split[x]) // NOT OK
154138

155139
var concatted = prefix.concat(split);
156140
fs.readFileSync(concatted.join("/")); // NOT OK
157141

158142
var concatted2 = split.concat(prefix);
159143
fs.readFileSync(concatted2.join("/")); // NOT OK
160144

161-
fs.readFileSync(split.pop()); // NOT OK
145+
fs.readFileSync(split.pop()); // NOT OK
162146

163147
});
164148

165149
var server = http.createServer(function(req, res) {
166150
let path = url.parse(req.url, true).query.path;
167-
151+
168152
// Removal of forward-slash or dots.
169153
res.write(fs.readFileSync(path.replace(/[\]\[*,;'"`<>\\?\/]/g, ''))); // OK.
170154
res.write(fs.readFileSync(path.replace(/[abcd]/g, ''))); // NOT OK

javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/Xss.expected

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ nodes
8686
| angular2-client.ts:44:44:44:76 | routeSn ... ('foo') |
8787
| angular2-client.ts:44:44:44:76 | routeSn ... ('foo') |
8888
| angular2-client.ts:44:44:44:76 | routeSn ... ('foo') |
89+
| angular-tempate-url.js:9:26:9:45 | Cookie.get("unsafe") |
90+
| angular-tempate-url.js:9:26:9:45 | Cookie.get("unsafe") |
91+
| angular-tempate-url.js:13:30:13:31 | ev |
92+
| angular-tempate-url.js:13:30:13:31 | ev |
93+
| angular-tempate-url.js:14:26:14:27 | ev |
94+
| angular-tempate-url.js:14:26:14:32 | ev.data |
8995
| classnames.js:7:31:7:84 | `<span ... <span>` |
9096
| classnames.js:7:31:7:84 | `<span ... <span>` |
9197
| classnames.js:7:47:7:69 | classNa ... w.name) |
@@ -1275,6 +1281,11 @@ edges
12751281
| angular2-client.ts:38:44:38:58 | this.router.url | angular2-client.ts:38:44:38:58 | this.router.url |
12761282
| angular2-client.ts:40:45:40:59 | this.router.url | angular2-client.ts:40:45:40:59 | this.router.url |
12771283
| angular2-client.ts:44:44:44:76 | routeSn ... ('foo') | angular2-client.ts:44:44:44:76 | routeSn ... ('foo') |
1284+
| angular-tempate-url.js:13:30:13:31 | ev | angular-tempate-url.js:14:26:14:27 | ev |
1285+
| angular-tempate-url.js:13:30:13:31 | ev | angular-tempate-url.js:14:26:14:27 | ev |
1286+
| angular-tempate-url.js:14:26:14:27 | ev | angular-tempate-url.js:14:26:14:32 | ev.data |
1287+
| angular-tempate-url.js:14:26:14:32 | ev.data | angular-tempate-url.js:9:26:9:45 | Cookie.get("unsafe") |
1288+
| angular-tempate-url.js:14:26:14:32 | ev.data | angular-tempate-url.js:9:26:9:45 | Cookie.get("unsafe") |
12781289
| classnames.js:7:47:7:69 | classNa ... w.name) | classnames.js:7:31:7:84 | `<span ... <span>` |
12791290
| classnames.js:7:47:7:69 | classNa ... w.name) | classnames.js:7:31:7:84 | `<span ... <span>` |
12801291
| classnames.js:7:58:7:68 | window.name | classnames.js:7:47:7:69 | classNa ... w.name) |
@@ -2407,6 +2418,7 @@ edges
24072418
| angular2-client.ts:38:44:38:58 | this.router.url | angular2-client.ts:38:44:38:58 | this.router.url | angular2-client.ts:38:44:38:58 | this.router.url | Cross-site scripting vulnerability due to $@. | angular2-client.ts:38:44:38:58 | this.router.url | user-provided value |
24082419
| angular2-client.ts:40:45:40:59 | this.router.url | angular2-client.ts:40:45:40:59 | this.router.url | angular2-client.ts:40:45:40:59 | this.router.url | Cross-site scripting vulnerability due to $@. | angular2-client.ts:40:45:40:59 | this.router.url | user-provided value |
24092420
| angular2-client.ts:44:44:44:76 | routeSn ... ('foo') | angular2-client.ts:44:44:44:76 | routeSn ... ('foo') | angular2-client.ts:44:44:44:76 | routeSn ... ('foo') | Cross-site scripting vulnerability due to $@. | angular2-client.ts:44:44:44:76 | routeSn ... ('foo') | user-provided value |
2421+
| angular-tempate-url.js:9:26:9:45 | Cookie.get("unsafe") | angular-tempate-url.js:13:30:13:31 | ev | angular-tempate-url.js:9:26:9:45 | Cookie.get("unsafe") | Cross-site scripting vulnerability due to $@. | angular-tempate-url.js:13:30:13:31 | ev | user-provided value |
24102422
| classnames.js:7:31:7:84 | `<span ... <span>` | classnames.js:7:58:7:68 | window.name | classnames.js:7:31:7:84 | `<span ... <span>` | Cross-site scripting vulnerability due to $@. | classnames.js:7:58:7:68 | window.name | user-provided value |
24112423
| classnames.js:8:31:8:85 | `<span ... <span>` | classnames.js:8:59:8:69 | window.name | classnames.js:8:31:8:85 | `<span ... <span>` | Cross-site scripting vulnerability due to $@. | classnames.js:8:59:8:69 | window.name | user-provided value |
24122424
| classnames.js:9:31:9:85 | `<span ... <span>` | classnames.js:9:59:9:69 | window.name | classnames.js:9:31:9:85 | `<span ... <span>` | Cross-site scripting vulnerability due to $@. | classnames.js:9:59:9:69 | window.name | user-provided value |

javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/XssWithAdditionalSources.expected

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ nodes
8686
| angular2-client.ts:44:44:44:76 | routeSn ... ('foo') |
8787
| angular2-client.ts:44:44:44:76 | routeSn ... ('foo') |
8888
| angular2-client.ts:44:44:44:76 | routeSn ... ('foo') |
89+
| angular-tempate-url.js:9:26:9:45 | Cookie.get("unsafe") |
90+
| angular-tempate-url.js:9:26:9:45 | Cookie.get("unsafe") |
91+
| angular-tempate-url.js:13:30:13:31 | ev |
92+
| angular-tempate-url.js:13:30:13:31 | ev |
93+
| angular-tempate-url.js:14:26:14:27 | ev |
94+
| angular-tempate-url.js:14:26:14:32 | ev.data |
8995
| classnames.js:7:31:7:84 | `<span ... <span>` |
9096
| classnames.js:7:31:7:84 | `<span ... <span>` |
9197
| classnames.js:7:47:7:69 | classNa ... w.name) |
@@ -1325,6 +1331,11 @@ edges
13251331
| angular2-client.ts:38:44:38:58 | this.router.url | angular2-client.ts:38:44:38:58 | this.router.url |
13261332
| angular2-client.ts:40:45:40:59 | this.router.url | angular2-client.ts:40:45:40:59 | this.router.url |
13271333
| angular2-client.ts:44:44:44:76 | routeSn ... ('foo') | angular2-client.ts:44:44:44:76 | routeSn ... ('foo') |
1334+
| angular-tempate-url.js:13:30:13:31 | ev | angular-tempate-url.js:14:26:14:27 | ev |
1335+
| angular-tempate-url.js:13:30:13:31 | ev | angular-tempate-url.js:14:26:14:27 | ev |
1336+
| angular-tempate-url.js:14:26:14:27 | ev | angular-tempate-url.js:14:26:14:32 | ev.data |
1337+
| angular-tempate-url.js:14:26:14:32 | ev.data | angular-tempate-url.js:9:26:9:45 | Cookie.get("unsafe") |
1338+
| angular-tempate-url.js:14:26:14:32 | ev.data | angular-tempate-url.js:9:26:9:45 | Cookie.get("unsafe") |
13281339
| classnames.js:7:47:7:69 | classNa ... w.name) | classnames.js:7:31:7:84 | `<span ... <span>` |
13291340
| classnames.js:7:47:7:69 | classNa ... w.name) | classnames.js:7:31:7:84 | `<span ... <span>` |
13301341
| classnames.js:7:58:7:68 | window.name | classnames.js:7:47:7:69 | classNa ... w.name) |
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
angular.module('myApp', [])
2+
.directive('myCustomer', function() {
3+
return {
4+
templateUrl: "SAFE" // OK
5+
}
6+
})
7+
.directive('myCustomer', function() {
8+
return {
9+
templateUrl: Cookie.get("unsafe") // NOT OK
10+
}
11+
});
12+
13+
addEventListener('message', (ev) => {
14+
Cookie.set("unsafe", ev.data);
15+
});

0 commit comments

Comments
 (0)