forked from github/codeql
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDOM.qll
More file actions
423 lines (362 loc) · 13.1 KB
/
DOM.qll
File metadata and controls
423 lines (362 loc) · 13.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
/**
* Provides classes for working with DOM elements.
*/
import javascript
import semmle.javascript.frameworks.Templating
private import semmle.javascript.dataflow.InferredTypes
module DOM {
/**
* A definition of a DOM element, for instance by an HTML element in an HTML file
* or a JSX element in a JavaScript file.
*/
abstract class ElementDefinition extends Locatable {
/**
* Gets the name of the DOM element; for example, a `<p>` element has
* name `p`.
*/
abstract string getName();
/**
* Gets the `i`th attribute of this DOM element, if it can be determined.
*
* For example, the 0th (and only) attribute of `<a href="https://semmle.com">Semmle</a>`
* is `href="https://semmle.com"`.
*/
AttributeDefinition getAttribute(int i) { none() }
/**
* Gets an attribute of this DOM element with name `name`.
*
* For example, the DOM element `<a href="https://semmle.com">Semmle</a>`
* has a single attribute `href="https://semmle.com"` with the name `href`.
*/
AttributeDefinition getAttributeByName(string name) {
result.getElement() = this and
result.getName() = name
}
/**
* Gets an attribute of this DOM element.
*/
AttributeDefinition getAnAttribute() { result.getElement() = this }
/**
* Gets the parent element of this element.
*/
abstract ElementDefinition getParent();
/**
* Gets the root element (i.e. an element without a parent) in which this element is contained.
*/
ElementDefinition getRoot() {
if not exists(getParent()) then result = this else result = getParent().getRoot()
}
/**
* Gets the document element to which this element belongs, if it can be determined.
*/
DocumentElementDefinition getDocument() { result = getRoot() }
}
/**
* An HTML element, viewed as an `ElementDefinition`.
*/
private class HtmlElementDefinition extends ElementDefinition, @xmlelement {
HtmlElementDefinition() { this instanceof HTML::Element }
override string getName() { result = this.(HTML::Element).getName() }
override AttributeDefinition getAttribute(int i) {
result = this.(HTML::Element).getAttribute(i)
}
override ElementDefinition getParent() { result = this.(HTML::Element).getParent() }
}
/**
* A JSX element, viewed as an `ElementDefinition`.
*/
private class JsxElementDefinition extends ElementDefinition, @jsxelement {
JsxElementDefinition() { this instanceof JSXElement }
override string getName() { result = this.(JSXElement).getName() }
override AttributeDefinition getAttribute(int i) { result = this.(JSXElement).getAttribute(i) }
override ElementDefinition getParent() { result = this.(JSXElement).getJsxParent() }
}
/**
* A DOM attribute as defined, for instance, by an HTML attribute in an HTML file
* or a JSX attribute in a JavaScript file.
*/
abstract class AttributeDefinition extends Locatable {
/**
* Gets the name of this attribute, if any.
*
* JSX spread attributes do not have a name.
*/
abstract string getName();
/**
* Gets the data flow node whose value is the value of this attribute,
* if any.
*
* This is undefined for HTML elements, where the attribute value is not
* computed but specified directly.
*/
DataFlow::Node getValueNode() { none() }
/**
* Gets the value of this attribute, if it can be determined.
*/
string getStringValue() { result = getValueNode().getStringValue() }
/**
* Gets the DOM element this attribute belongs to.
*/
ElementDefinition getElement() { this = result.getAttributeByName(_) }
/**
* Holds if the value of this attribute might be a template value
* such as `{{window.location.url}}`.
*/
predicate mayHaveTemplateValue() {
getStringValue().regexpMatch(Templating::getDelimiterMatchingRegexp())
}
}
/**
* An HTML attribute, viewed as an `AttributeDefinition`.
*/
private class HtmlAttributeDefinition extends AttributeDefinition, @xmlattribute {
HtmlAttributeDefinition() { this instanceof HTML::Attribute }
override string getName() { result = this.(HTML::Attribute).getName() }
override string getStringValue() { result = this.(HTML::Attribute).getValue() }
override ElementDefinition getElement() { result = this.(HTML::Attribute).getElement() }
}
/**
* A JSX attribute, viewed as an `AttributeDefinition`.
*/
private class JsxAttributeDefinition extends AttributeDefinition, @jsx_attribute {
JSXAttribute attr;
JsxAttributeDefinition() { this = attr }
override string getName() { result = attr.getName() }
override DataFlow::Node getValueNode() { result = DataFlow::valueNode(attr.getValue()) }
override ElementDefinition getElement() { result = attr.getElement() }
}
/**
* An HTML `<document>` element.
*/
class DocumentElementDefinition extends ElementDefinition {
DocumentElementDefinition() { this.getName() = "html" }
override string getName() { none() }
override AttributeDefinition getAttribute(int i) { none() }
override AttributeDefinition getAttributeByName(string name) { none() }
override ElementDefinition getParent() { none() }
}
/**
* Holds if the value of attribute `attr` is interpreted as a URL.
*/
predicate isUrlValuedAttribute(AttributeDefinition attr) {
exists(string eltName, string attrName |
eltName = attr.getElement().getName() and
attrName = attr.getName()
|
(
eltName = "script" or
eltName = "iframe" or
eltName = "embed" or
eltName = "video" or
eltName = "audio" or
eltName = "source" or
eltName = "track"
) and
attrName = "src"
or
(
eltName = "link" or
eltName = "a" or
eltName = "base" or
eltName = "area"
) and
attrName = "href"
or
eltName = "form" and
attrName = "action"
or
(eltName = "input" or eltName = "button") and
attrName = "formaction"
)
}
/**
* A data flow node or other program element that may refer to
* a DOM element.
*/
abstract class Element extends Locatable {
ElementDefinition defn;
/** Gets the definition of this element. */
ElementDefinition getDefinition() { result = defn }
/** Gets the tag name of this DOM element. */
string getName() { result = defn.getName() }
/** Gets the `i`th attribute of this DOM element, if it can be determined. */
AttributeDefinition getAttribute(int i) { result = defn.getAttribute(i) }
/** Gets an attribute of this DOM element with the given `name`. */
AttributeDefinition getAttributeByName(string name) { result = defn.getAttributeByName(name) }
}
/**
* The default implementation of `Element`, including both
* element definitions and data flow nodes that may refer to them.
*/
private class DefaultElement extends Element {
DefaultElement() {
defn = this
or
exists(Element that |
this.(Expr).flow().getALocalSource().asExpr() = that and
defn = that.getDefinition()
)
}
}
/**
* Holds if `attr` is an invalid id attribute because of `reason`.
*/
predicate isInvalidHtmlIdAttributeValue(DOM::AttributeDefinition attr, string reason) {
attr.getName() = "id" and
exists(string v | v = attr.getStringValue() |
v = "" and
reason = "must contain at least one character"
or
v.regexpMatch(".*\\s.*") and
reason = "must not contain any space characters"
)
}
/** Gets a call that queries the DOM for a collection of DOM nodes. */
private DataFlow::SourceNode domElementCollection() {
exists(string collectionName |
collectionName = "getElementsByClassName" or
collectionName = "getElementsByName" or
collectionName = "getElementsByTagName" or
collectionName = "getElementsByTagNameNS" or
collectionName = "querySelectorAll"
|
(
result = documentRef().getAMethodCall(collectionName) or
result = DataFlow::globalVarRef(collectionName).getACall()
)
)
}
/** Gets a call that creates a DOM node or queries the DOM for a DOM node. */
private DataFlow::SourceNode domElementCreationOrQuery() {
exists(string methodName |
methodName = "createElement" or
methodName = "createElementNS" or
methodName = "createRange" or
methodName = "getElementById" or
methodName = "querySelector"
|
result = documentRef().getAMethodCall(methodName) or
result = DataFlow::globalVarRef(methodName).getACall()
)
}
module DomValueSource {
/**
* A data flow node that should be considered a source of DOM values.
*/
abstract class Range extends DataFlow::Node { }
private string getADomPropertyName() {
exists(ExternalInstanceMemberDecl decl |
result = decl.getName() and
isDomRootType(decl.getDeclaringType().getASupertype*())
)
}
private class DefaultRange extends Range {
DefaultRange() {
this.asExpr().(VarAccess).getVariable() instanceof DOMGlobalVariable
or
exists(DataFlow::PropRead read |
this = read and
read = domValueRef().getAPropertyRead()
|
not read.mayHavePropertyName(_)
or
read.mayHavePropertyName(getADomPropertyName())
or
read.mayHavePropertyName(any(string s | exists(s.toInt())))
)
or
this = domElementCreationOrQuery()
or
this = domElementCollection()
or
exists(JQuery::MethodCall call | this = call and call.getMethodName() = "get" |
call.getNumArgument() = 1 and
forex(InferredType t | t = call.getArgument(0).analyze().getAType() | t = TTNumber())
)
or
// A `this` node from a callback given to a `$().each(callback)` call.
// purposely not using JQuery::MethodCall to avoid `jquery.each()`.
exists(DataFlow::CallNode eachCall | eachCall = JQuery::objectRef().getAMethodCall("each") |
this = DataFlow::thisNode(eachCall.getCallback(0).getFunction()) or
this = eachCall.getABoundCallbackParameter(0, 1)
)
}
}
}
/** Gets a data flow node that refers directly to a value from the DOM. */
DataFlow::SourceNode domValueSource() { result instanceof DomValueSource::Range }
/** Gets a data flow node that may refer to a value from the DOM. */
private DataFlow::SourceNode domValueRef(DataFlow::TypeTracker t) {
t.start() and
result = domValueSource()
or
t.start() and
result = domValueRef().getAMethodCall(["item", "namedItem"])
or
exists(DataFlow::TypeTracker t2 | result = domValueRef(t2).track(t2, t))
}
/** Gets a data flow node that may refer to a value from the DOM. */
DataFlow::SourceNode domValueRef() { result = domValueRef(DataFlow::TypeTracker::end()) }
module LocationSource {
/**
* A data flow node that should be considered a source of the DOM `location` object.
*
* Can be subclassed to add additional such nodes.
*/
abstract class Range extends DataFlow::Node { }
private class DefaultRange extends Range {
DefaultRange() {
exists(string propName | this = documentRef().getAPropertyRead(propName) |
propName = "documentURI" or
propName = "documentURIObject" or
propName = "location" or
propName = "referrer" or
propName = "URL"
)
or
this = DOM::domValueRef().getAPropertyRead("baseUri")
or
this = DataFlow::globalVarRef("location")
}
}
}
/** Gets a data flow node that directly refers to a DOM `location` object. */
DataFlow::SourceNode locationSource() { result instanceof LocationSource::Range }
/** Gets a reference to a DOM `location` object. */
private DataFlow::SourceNode locationRef(DataFlow::TypeTracker t) {
t.start() and
result = locationSource()
or
exists(DataFlow::TypeTracker t2 | result = locationRef(t2).track(t2, t))
}
/** Gets a reference to a DOM `location` object. */
DataFlow::SourceNode locationRef() { result = locationRef(DataFlow::TypeTracker::end()) }
module DocumentSource {
/**
* A data flow node that should be considered a source of the `document` object.
*
* Can be subclassed to add additional such nodes.
*/
abstract class Range extends DataFlow::Node { }
private class DefaultRange extends Range {
DefaultRange() { this = DataFlow::globalVarRef("document") }
}
}
/**
* Gets a direct reference to the `document` object.
*/
DataFlow::SourceNode documentSource() { result instanceof DocumentSource::Range }
/**
* Gets a reference to the `document` object.
*/
private DataFlow::SourceNode documentRef(DataFlow::TypeTracker t) {
t.start() and
result instanceof DocumentSource::Range
or
exists(DataFlow::TypeTracker t2 | result = documentRef(t2).track(t2, t))
}
/**
* Gets a reference to the 'document' object.
*/
DataFlow::SourceNode documentRef() { result = documentRef(DataFlow::TypeTracker::end()) }
}