-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathFunctionalityFromUntrustedSource.ql
More file actions
169 lines (148 loc) · 5.97 KB
/
FunctionalityFromUntrustedSource.ql
File metadata and controls
169 lines (148 loc) · 5.97 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
/**
* @name Inclusion of functionality from an untrusted source
* @description Including functionality from an untrusted source may allow
* an attacker to control the functionality and execute arbitrary code.
* @kind problem
* @problem.severity warning
* @security-severity 6.0
* @precision high
* @id js/functionality-from-untrusted-source
* @tags security
* external/cwe/cwe-830
*/
import javascript
/** 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 {
/** Holds if `host` is an alias of localhost. */
bindingset[host]
predicate isLocalhostPrefix(string host) {
host.toLowerCase()
.regexpMatch([
"(?i)localhost(:[0-9]+)?/.*", "127.0.0.1(:[0-9]+)?/.*", "::1/.*", "\\[::1\\]:[0-9]+/.*"
])
}
/** Holds if `url` is a url that is vulnerable to a MITM attack. */
bindingset[url]
predicate isUntrustedSourceUrl(string url) {
exists(string hostPath | hostPath = url.regexpCapture("(?i)http://(.*)", 1) |
not isLocalhostPrefix(hostPath)
)
}
/** Holds if `url` refers to a CDN 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("(?i)^https?://" +
[
"code\\.jquery\\.com", //
"cdnjs\\.cloudflare\\.com", //
"cdnjs\\.com" //
] + "/.*\\.js$")
}
/** A script element that refers to untrusted content. */
class ScriptElementWithUntrustedContent extends AddsUntrustedUrl instanceof HTML::ScriptElement {
ScriptElementWithUntrustedContent() {
not exists(string digest | not digest = "" | super.getIntegrityDigest() = digest) and
isUntrustedSourceUrl(super.getSourcePath())
}
override string getProblem() { result = "Script loaded using unencrypted connection." }
}
/** A script element that refers to untrusted content. */
class CdnScriptElementWithUntrustedContent extends AddsUntrustedUrl, HTML::ScriptElement {
CdnScriptElementWithUntrustedContent() {
not exists(string digest | not digest = "" | this.getIntegrityDigest() = digest) and
isCdnUrlWithCheckingRequired(this.getSourcePath())
}
override string getProblem() {
result = "Script loaded from content delivery network with no integrity check."
}
}
/** An iframe element that includes untrusted content. */
class IframeElementWithUntrustedContent extends AddsUntrustedUrl instanceof HTML::IframeElement {
IframeElementWithUntrustedContent() { isUntrustedSourceUrl(super.getSourcePath()) }
override string getProblem() { result = "Iframe loaded using unencrypted connection." }
}
}
module DynamicCreation {
/** Holds if `call` creates a tag of kind `name`. */
predicate isCreateElementNode(DataFlow::CallNode call, string name) {
call = DataFlow::globalVarRef("document").getAMethodCall("createElement") and
call.getArgument(0).getStringValue().toLowerCase() = name
}
DataFlow::Node getAttributeAssignmentRhs(DataFlow::CallNode createCall, string name) {
result = createCall.getAPropertyWrite(name).getRhs()
or
exists(DataFlow::InvokeNode inv | inv = createCall.getAMemberInvocation("setAttribute") |
inv.getArgument(0).getStringValue() = name and
result = inv.getArgument(1)
)
}
/**
* Holds if `createCall` creates a `<script ../>` element which never
* has its `integrity` attribute set locally.
*/
predicate isCreateScriptNodeWoIntegrityCheck(DataFlow::CallNode createCall) {
isCreateElementNode(createCall, "script") and
not exists(getAttributeAssignmentRhs(createCall, "integrity"))
}
DataFlow::Node urlTrackedFromUnsafeSourceLiteral(DataFlow::TypeTracker t) {
t.start() and result.getStringValue().regexpMatch("(?i)http:.*")
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())
}
/** Holds if `sink` is assigned to the attribute `name` of any HTML element. */
predicate isAssignedToSrcAttribute(string name, DataFlow::Node sink) {
exists(DataFlow::CallNode createElementCall |
sink = getAttributeAssignmentRhs(createElementCall, "src") and
(
name = "script" and
isCreateScriptNodeWoIntegrityCheck(createElementCall)
or
name = "iframe" and
isCreateElementNode(createElementCall, "iframe")
)
)
}
class IframeOrScriptSrcAssignment extends AddsUntrustedUrl {
string name;
IframeOrScriptSrcAssignment() {
name = ["script", "iframe"] and
exists(DataFlow::Node n | n.asExpr() = this |
isAssignedToSrcAttribute(name, n) and
n = urlTrackedFromUnsafeSourceLiteral()
)
}
override string getProblem() {
name = "script" and result = "Script loaded using unencrypted connection."
or
name = "iframe" and result = "Iframe loaded using unencrypted connection."
}
}
}
from AddsUntrustedUrl s
select s, s.getProblem()