|
5 | 5 |
|
6 | 6 | import javascript |
7 | 7 | import semmle.javascript.security.TaintedObject |
| 8 | +import semmle.javascript.dependencies.Dependencies |
| 9 | +import semmle.javascript.dependencies.SemVer |
8 | 10 |
|
9 | 11 | module PrototypePollution { |
10 | 12 | /** |
@@ -47,6 +49,11 @@ module PrototypePollution { |
47 | 49 | * Gets the type of data that can taint this sink. |
48 | 50 | */ |
49 | 51 | abstract DataFlow::FlowLabel getAFlowLabel(); |
| 52 | + |
| 53 | + /** |
| 54 | + * Gets the dependency that defines this sink. |
| 55 | + */ |
| 56 | + abstract Dependency getDependency(); |
50 | 57 | } |
51 | 58 |
|
52 | 59 | /** |
@@ -103,36 +110,73 @@ module PrototypePollution { |
103 | 110 |
|
104 | 111 | override DataFlow::FlowLabel getAFlowLabel() { result = TaintedObject::label() } |
105 | 112 | } |
106 | | - |
107 | | - string getModuleName(ExtendCall call) { |
108 | | - call = DataFlow::moduleImport(result).getACall() or |
109 | | - call = DataFlow::moduleMember(result, _).getACall() |
110 | | - } |
111 | 113 |
|
112 | 114 | class DeepExtendSink extends Sink { |
113 | 115 | ExtendCall call; |
114 | 116 |
|
| 117 | + Dependency dependency; |
| 118 | + |
115 | 119 | DeepExtendSink() { |
116 | 120 | this = call.getASourceOperand() and |
117 | | - call.isDeep() and |
118 | | - exists(string moduleName | moduleName = getModuleName(call) | |
119 | | - moduleName = "lodash" + any(string s) or |
120 | | - moduleName = "just-extend" or |
121 | | - moduleName = "extend" or |
122 | | - moduleName = "extend2" or |
123 | | - moduleName = "node.extend" or |
124 | | - moduleName = "merge" or |
125 | | - moduleName = "smart-extend" or |
126 | | - moduleName = "js-extend" or |
127 | | - moduleName = "deep" or |
128 | | - moduleName = "defaults-deep" |
129 | | - ) |
| 121 | + isVulnerableDeepExtendCall(call, dependency) |
130 | 122 | } |
131 | 123 |
|
132 | 124 | override DataFlow::FlowLabel getAFlowLabel() { |
133 | 125 | result = TaintedObject::label() |
134 | 126 | or |
135 | 127 | result = TaintedObjectWrapper::label() |
136 | 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.11") |
| 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 | + ) |
137 | 181 | } |
138 | 182 | } |
0 commit comments