Skip to content

Commit 8664908

Browse files
author
Esben Sparre Andreasen
committed
JS: split PrototypePollution.qll
1 parent 289c298 commit 8664908

File tree

2 files changed

+156
-138
lines changed

2 files changed

+156
-138
lines changed
Lines changed: 7 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
/**
2-
* Provides a taint-tracking configuration for tracking user-controlled objects flowing
3-
* into a vulnerable `extends` call.
2+
* Provides a taint-tracking configuration for tracking
3+
* user-controlled objects flowing into a vulnerable `extends` call.
4+
*
5+
* Note, for performance reasons: only import this file if
6+
* `PrototypePollution::Configuration` is needed, otherwise
7+
* `PrototypePollutionCustomizations` should be imported instead.
48
*/
59

610
import javascript
@@ -9,52 +13,7 @@ import semmle.javascript.dependencies.Dependencies
913
import semmle.javascript.dependencies.SemVer
1014

1115
module PrototypePollution {
12-
/**
13-
* Label for wrappers around tainted objects, that is, objects that are
14-
* not completely user-controlled, but contain a user-controlled object.
15-
*
16-
* For example, `options` below is is a tainted wrapper, but is not itself
17-
* a tainted object:
18-
* ```
19-
* let options = {
20-
* prefs: {
21-
* locale: req.query.locale
22-
* }
23-
* }
24-
* ```
25-
*/
26-
module TaintedObjectWrapper {
27-
private class TaintedObjectWrapper extends DataFlow::FlowLabel {
28-
TaintedObjectWrapper() { this = "tainted-object-wrapper" }
29-
}
30-
31-
TaintedObjectWrapper label() { any() }
32-
}
33-
34-
/**
35-
* A data flow source for prototype pollution.
36-
*/
37-
abstract class Source extends DataFlow::Node {
38-
/**
39-
* Gets the type of data coming from this source.
40-
*/
41-
abstract DataFlow::FlowLabel getAFlowLabel();
42-
}
43-
44-
/**
45-
* A data flow sink for prototype pollution.
46-
*/
47-
abstract class Sink extends DataFlow::Node {
48-
/**
49-
* Gets the type of data that can taint this sink.
50-
*/
51-
abstract DataFlow::FlowLabel getAFlowLabel();
52-
53-
/**
54-
* Gets the dependency that defines this sink.
55-
*/
56-
abstract Dependency getDependency();
57-
}
16+
import PrototypePollutionCustomizations::PrototypePollution
5817

5918
/**
6019
* A taint tracking configuration for user-controlled objects flowing into deep `extend` calls,
@@ -89,94 +48,4 @@ module PrototypePollution {
8948
node instanceof TaintedObject::SanitizerGuard
9049
}
9150
}
92-
93-
/**
94-
* A user-controlled string value, as a source of prototype pollution.
95-
*
96-
* Note that values from this type of source will need to flow through a `JSON.parse` call
97-
* in order to be flagged for prototype pollution.
98-
*/
99-
private class RemoteFlowAsSource extends Source {
100-
RemoteFlowAsSource() { this instanceof RemoteFlowSource }
101-
102-
override DataFlow::FlowLabel getAFlowLabel() { result = DataFlow::FlowLabel::data() }
103-
}
104-
105-
/**
106-
* A source of user-controlled objects.
107-
*/
108-
private class TaintedObjectSource extends Source {
109-
TaintedObjectSource() { this instanceof TaintedObject::Source }
110-
111-
override DataFlow::FlowLabel getAFlowLabel() { result = TaintedObject::label() }
112-
}
113-
114-
class DeepExtendSink extends Sink {
115-
ExtendCall call;
116-
117-
Dependency dependency;
118-
119-
DeepExtendSink() {
120-
this = call.getASourceOperand() and
121-
isVulnerableDeepExtendCall(call, dependency)
122-
}
123-
124-
override DataFlow::FlowLabel getAFlowLabel() {
125-
result = TaintedObject::label()
126-
or
127-
result = TaintedObjectWrapper::label()
128-
}
129-
130-
override Dependency getDependency() { result = dependency }
131-
}
132-
133-
/**
134-
* Holds if `call` is vulnerable to prototype pollution because the callee is defined by `dep`.
135-
*/
136-
predicate isVulnerableDeepExtendCall(ExtendCall call, Dependency dep) {
137-
call.isDeep() and
138-
(
139-
call = DataFlow::dependencyModuleImport(dep).getAMemberCall(_) or
140-
call = DataFlow::dependencyModuleImport(dep).getACall()
141-
) and
142-
exists(DependencySemVer version, string id | dep.info(id, version) |
143-
id = "assign-deep" and
144-
version.maybeBefore("0.4.7")
145-
or
146-
id = "deep"
147-
or
148-
id = "deep-extend" and
149-
version.maybeBefore("0.5.1")
150-
or
151-
id = "defaults-deep" and
152-
version.maybeBefore("0.2.4")
153-
or
154-
id = "extend" and
155-
(version.maybeBefore("2.0.2") or version.maybeBetween("3.0.0", "3.0.2"))
156-
or
157-
id = "extend2"
158-
or
159-
id = "js-extend"
160-
or
161-
id = "just-extend" and
162-
version.maybeBefore("4.0.1")
163-
or
164-
id = "lodash" + any(string s) and
165-
version.maybeBefore("4.17.12")
166-
or
167-
id = "merge" and
168-
version.maybeBefore("1.2.1")
169-
or
170-
id = "merge-deep" and
171-
version.maybeBefore("3.0.1")
172-
or
173-
id = "merge-options" and
174-
version.maybeBefore("1.0.1")
175-
or
176-
id = "node.extend" and
177-
(version.maybeBefore("1.1.7") or version.maybeBetween("2.0.0", "2.0.1"))
178-
or
179-
id = "smart-extend"
180-
)
181-
}
18251
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/**
2+
* Provides default sources, sinks and sanitisers for reasoning about
3+
* user-controlled objects flowing into a vulnerable `extends` call,
4+
* as well as extension points for adding your own.
5+
*/
6+
7+
import javascript
8+
import semmle.javascript.security.TaintedObject
9+
import semmle.javascript.dependencies.Dependencies
10+
import semmle.javascript.dependencies.SemVer
11+
12+
module PrototypePollution {
13+
/**
14+
* Label for wrappers around tainted objects, that is, objects that are
15+
* not completely user-controlled, but contain a user-controlled object.
16+
*
17+
* For example, `options` below is is a tainted wrapper, but is not itself
18+
* a tainted object:
19+
* ```
20+
* let options = {
21+
* prefs: {
22+
* locale: req.query.locale
23+
* }
24+
* }
25+
* ```
26+
*/
27+
module TaintedObjectWrapper {
28+
private class TaintedObjectWrapper extends DataFlow::FlowLabel {
29+
TaintedObjectWrapper() { this = "tainted-object-wrapper" }
30+
}
31+
32+
TaintedObjectWrapper label() { any() }
33+
}
34+
35+
/**
36+
* A data flow source for prototype pollution.
37+
*/
38+
abstract class Source extends DataFlow::Node {
39+
/**
40+
* Gets the type of data coming from this source.
41+
*/
42+
abstract DataFlow::FlowLabel getAFlowLabel();
43+
}
44+
45+
/**
46+
* A data flow sink for prototype pollution.
47+
*/
48+
abstract class Sink extends DataFlow::Node {
49+
/**
50+
* Gets the type of data that can taint this sink.
51+
*/
52+
abstract DataFlow::FlowLabel getAFlowLabel();
53+
54+
/**
55+
* Gets the dependency that defines this sink.
56+
*/
57+
abstract Dependency getDependency();
58+
}
59+
60+
/**
61+
* A user-controlled string value, as a source of prototype pollution.
62+
*
63+
* Note that values from this type of source will need to flow through a `JSON.parse` call
64+
* in order to be flagged for prototype pollution.
65+
*/
66+
private class RemoteFlowAsSource extends Source {
67+
RemoteFlowAsSource() { this instanceof RemoteFlowSource }
68+
69+
override DataFlow::FlowLabel getAFlowLabel() { result = DataFlow::FlowLabel::data() }
70+
}
71+
72+
/**
73+
* A source of user-controlled objects.
74+
*/
75+
private class TaintedObjectSource extends Source {
76+
TaintedObjectSource() { this instanceof TaintedObject::Source }
77+
78+
override DataFlow::FlowLabel getAFlowLabel() { result = TaintedObject::label() }
79+
}
80+
81+
class DeepExtendSink extends Sink {
82+
ExtendCall call;
83+
84+
Dependency dependency;
85+
86+
DeepExtendSink() {
87+
this = call.getASourceOperand() and
88+
isVulnerableDeepExtendCall(call, dependency)
89+
}
90+
91+
override DataFlow::FlowLabel getAFlowLabel() {
92+
result = TaintedObject::label()
93+
or
94+
result = TaintedObjectWrapper::label()
95+
}
96+
97+
override Dependency getDependency() { result = dependency }
98+
}
99+
100+
/**
101+
* Holds if `call` is vulnerable to prototype pollution because the callee is defined by `dep`.
102+
*/
103+
predicate isVulnerableDeepExtendCall(ExtendCall call, Dependency dep) {
104+
call.isDeep() and
105+
(
106+
call = DataFlow::dependencyModuleImport(dep).getAMemberCall(_) or
107+
call = DataFlow::dependencyModuleImport(dep).getACall()
108+
) and
109+
exists(DependencySemVer version, string id | dep.info(id, version) |
110+
id = "assign-deep" and
111+
version.maybeBefore("0.4.7")
112+
or
113+
id = "deep"
114+
or
115+
id = "deep-extend" and
116+
version.maybeBefore("0.5.1")
117+
or
118+
id = "defaults-deep" and
119+
version.maybeBefore("0.2.4")
120+
or
121+
id = "extend" and
122+
(version.maybeBefore("2.0.2") or version.maybeBetween("3.0.0", "3.0.2"))
123+
or
124+
id = "extend2"
125+
or
126+
id = "js-extend"
127+
or
128+
id = "just-extend" and
129+
version.maybeBefore("4.0.1")
130+
or
131+
id = "lodash" + any(string s) and
132+
version.maybeBefore("4.17.12")
133+
or
134+
id = "merge" and
135+
version.maybeBefore("1.2.1")
136+
or
137+
id = "merge-deep" and
138+
version.maybeBefore("3.0.1")
139+
or
140+
id = "merge-options" and
141+
version.maybeBefore("1.0.1")
142+
or
143+
id = "node.extend" and
144+
(version.maybeBefore("1.1.7") or version.maybeBetween("2.0.0", "2.0.1"))
145+
or
146+
id = "smart-extend"
147+
)
148+
}
149+
}

0 commit comments

Comments
 (0)