-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathFunctionalityFromUntrustedSource.ql
More file actions
181 lines (158 loc) · 6.36 KB
/
FunctionalityFromUntrustedSource.ql
File metadata and controls
181 lines (158 loc) · 6.36 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
/**
* @name Inclusion of untrusted functionality by a HTML element.
* @description Including untrusted functionality by a HTML element
* opens up for potential man-in-the-middle attacks.
* @kind problem
* @problem.severity warning
* @security-severity 8.1
* @precision high
* @id js/functionality-from-untrusted-source
* @tags security
* external/cwe/cwe-830
*/
import javascript
import semmle.javascript.HTML
import semmle.javascript.dataflow.TaintTracking
module Generic {
/** A `CallNode` that creates an element of kind `name`. */
predicate isCreateElementNode(DataFlow::CallNode call, string name) {
call = DataFlow::globalVarRef("document").getAMethodCall("createElement") and
call.getArgument(0).getStringValue().toLowerCase() = name
}
/**
* True if `rhs` is being assigned to the `propName` property of an HTML
* element created by `createElementCall`.
*/
predicate isPropWrite(DataFlow::CallNode createElementCall, string propName, DataFlow::Node rhs) {
exists(DataFlow::PropWrite assignment |
isCreateElementNode(createElementCall, _) and
assignment.writes(createElementCall.getALocalUse(), propName, rhs)
)
}
/**
* A `createElement` call that creates a `<script ../>` element which never
* has its `integrity` attribute set locally.
*/
predicate isCreateScriptNodeWoIntegrityCheck(DataFlow::CallNode createCall) {
isCreateElementNode(createCall, "script") and
not exists(DataFlow::Node rhs | isPropWrite(createCall, "integrity", rhs))
}
/** A location that adds a reference to an untrusted source. */
abstract class AddsUntrustedUrl extends Locatable {
/** Gets an explanation why this source is untrusted. */
abstract string getProblem();
}
}
module StaticCreation {
bindingset[host]
predicate isLocalhostPrefix(string host) {
host.toLowerCase()
.regexpMatch([
"localhost(:[0-9]+)?/.*", "127.0.0.1(:[0-9]+)?/.*", "::1/.*", "\\[::1\\]:[0-9]+/.*"
])
}
/** A path that is vulnerable to a MITM attack. */
bindingset[url]
predicate isUntrustedSourceUrl(string url) {
exists(string hostPath | hostPath = url.regexpCapture("http://(.*)", 1) |
not isLocalhostPrefix(hostPath)
)
}
/** A path that needs an integrity check - even with https. */
bindingset[url]
predicate isCdnUrlWithCheckingRequired(string url) {
// Some CDN URLs are required to have an integrity attribute. We only add CDNs to that list
// that recommend integrity-checking.
url.regexpMatch([
"^https?://code\\.jquery\\.com/.*\\.js$", "^https?://cdnjs\\.cloudflare\\.com/.*\\.js$",
"^https?://cdnjs\\.com/.*\\.js$"
])
}
/** A script element that refers to untrusted content. */
class ScriptElementWithUntrustedContent extends Generic::AddsUntrustedUrl, HTML::ScriptElement {
ScriptElementWithUntrustedContent() {
not exists(string digest | not digest = "" | this.getIntegrityDigest() = digest) and
isUntrustedSourceUrl(this.getSourcePath())
}
override string getProblem() {
result = "script elements should use an HTTPS url and/or use the integrity attribute"
}
}
/** A script element that refers to untrusted content. */
class CDNScriptElementWithUntrustedContent extends Generic::AddsUntrustedUrl, HTML::ScriptElement {
CDNScriptElementWithUntrustedContent() {
not exists(string digest | not digest = "" | this.getIntegrityDigest() = digest) and
isCdnUrlWithCheckingRequired(this.getSourcePath())
}
override string getProblem() {
result =
"script elements that depend on this CDN should use an HTTPS url and use the integrity attribute"
}
}
/** An iframe element that includes untrusted content. */
class IframeElementWithUntrustedContent extends HTML::IframeElement, Generic::AddsUntrustedUrl {
IframeElementWithUntrustedContent() { isUntrustedSourceUrl(this.getSourcePath()) }
override string getProblem() { result = "iframe elements should use an HTTPS url" }
}
}
module DynamicCreation {
import DataFlow::TypeTracker
predicate isUnsafeSourceLiteral(DataFlow::Node source) {
exists(StringLiteral s | source = s.flow() |
s.getValue().toLowerCase() = "http:" + any(string rest)
)
}
DataFlow::Node urlTrackedFromUnsafeSourceLiteral(DataFlow::TypeTracker t) {
t.start() and isUnsafeSourceLiteral(result)
or
exists(DataFlow::TypeTracker t2, DataFlow::Node prev |
prev = urlTrackedFromUnsafeSourceLiteral(t2)
|
not exists(string httpsUrl | httpsUrl.toLowerCase() = "https:" + any(string rest) |
// when the result may have a string value starting with https,
// we're most likely with an assignment like:
// e.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'
// these assignments, we don't want to fix - once the browser is using http,
// MITM attacks are possible anyway.
result.mayHaveStringValue(httpsUrl)
) and
(
t2 = t.smallstep(prev, result)
or
TaintTracking::sharedTaintStep(prev, result) and
t = t2
)
)
}
DataFlow::Node urlTrackedFromUnsafeSourceLiteral() {
result = urlTrackedFromUnsafeSourceLiteral(DataFlow::TypeTracker::end())
}
predicate isAssignedToSrcAttribute(string name, DataFlow::Node sink) {
exists(DataFlow::CallNode createElementCall |
name = "script" and
Generic::isCreateScriptNodeWoIntegrityCheck(createElementCall) and
Generic::isPropWrite(createElementCall, "src", sink)
or
name = "iframe" and
Generic::isCreateElementNode(createElementCall, "iframe") and
Generic::isPropWrite(createElementCall, "src", sink)
)
}
class IframeOrScriptSrcAssignment extends Expr, Generic::AddsUntrustedUrl {
string name;
IframeOrScriptSrcAssignment() {
exists(DataFlow::Node n | n.asExpr() = this |
DynamicCreation::isAssignedToSrcAttribute(name, n) and
n = DynamicCreation::urlTrackedFromUnsafeSourceLiteral()
)
}
override string getProblem() {
name = "script" and
result = "script elements should use an HTTPS url and/or use the integrity attribute"
or
name = "iframe" and result = "iframe elements should use an HTTPS url"
}
}
}
from Generic::AddsUntrustedUrl s
select s, "HTML-element uses untrusted content (" + s.getProblem() + ")"