-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathIncompleteUrlSchemeCheck.ql
More file actions
104 lines (96 loc) · 3.66 KB
/
IncompleteUrlSchemeCheck.ql
File metadata and controls
104 lines (96 loc) · 3.66 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
/**
* @name Incomplete URL scheme check
* @description Checking for the "javascript:" URL scheme without also checking for "vbscript:"
* and "data:" suggests a logic error or even a security vulnerability.
* @kind problem
* @problem.severity warning
* @precision high
* @id js/incomplete-url-scheme-check
* @tags security
* correctness
* external/cwe/cwe-020
*/
import javascript
/** A URL scheme that can be used to represent executable code. */
class DangerousScheme extends string {
DangerousScheme() { this = "data:" or this = "javascript:" or this = "vbscript:" }
/** Gets the name of this scheme without the `:`. */
string getWithoutColon() { this = result + ":" }
/** Gets the name of this scheme, with or without the `:`. */
string getWithOrWithoutColon() { result = this or result = getWithoutColon() }
}
/** Returns a node that refers to the scheme of `url`. */
DataFlow::SourceNode schemeOf(DataFlow::Node url) {
// url.split(":")[0]
exists(DataFlow::MethodCallNode split |
split.getMethodName() = "split" and
split.getArgument(0).getStringValue() = ":" and
result = split.getAPropertyRead("0") and
url = split.getReceiver()
)
or
// url.getScheme(), url.getProtocol(), getScheme(url), getProtocol(url)
exists(DataFlow::CallNode call |
result = call and
(call.getCalleeName() = "getScheme" or call.getCalleeName() = "getProtocol")
|
call.getNumArgument() = 1 and
url = call.getArgument(0)
or
call.getNumArgument() = 0 and
url = call.getReceiver()
)
or
// url.scheme, url.protocol
exists(DataFlow::PropRead prop |
result = prop and
(prop.getPropertyName() = "scheme" or prop.getPropertyName() = "protocol") and
url = prop.getBase()
)
}
/** Gets a data-flow node that checks `nd` against the given `scheme`. */
DataFlow::Node schemeCheck(DataFlow::Node nd, DangerousScheme scheme) {
// check of the form `nd.startsWith(scheme)`
exists(StringOps::StartsWith sw | sw = result |
sw.getBaseString() = nd and
sw.getSubstring().mayHaveStringValue(scheme)
)
or
// check of the form `array.includes(getScheme(nd))`
exists(InclusionTest test, DataFlow::ArrayCreationNode array | test = result |
schemeOf(nd).flowsTo(test.getContainedNode()) and
array.flowsTo(test.getContainerNode()) and
array.getAnElement().mayHaveStringValue(scheme.getWithOrWithoutColon())
)
or
// check of the form `getScheme(nd) === scheme`
exists(EqualityTest test, Expr op1, Expr op2 | test.flow() = result |
test.hasOperands(op1, op2) and
schemeOf(nd).flowsToExpr(op1) and
op2.mayHaveStringValue(scheme.getWithOrWithoutColon())
)
or
// propagate through trimming, case conversion, and regexp replace
exists(DataFlow::MethodCallNode stringop |
stringop.getMethodName().matches("trim%") or
stringop.getMethodName().matches("to%Case") or
stringop.getMethodName() = "replace"
|
result = schemeCheck(stringop, scheme) and
nd = stringop.getReceiver()
)
or
// propagate through local data flow
result = schemeCheck(nd.getASuccessor(), scheme)
}
/** Gets a data-flow node that checks an instance of `ap` against the given `scheme`. */
DataFlow::Node schemeCheckOn(DataFlow::SourceNode root, string path, DangerousScheme scheme) {
result = schemeCheck(AccessPath::getAReferenceTo(root, path), scheme)
}
from DataFlow::SourceNode root, string path, int n
where
n = strictcount(DangerousScheme s) and
strictcount(DangerousScheme s | exists(schemeCheckOn(root, path, s))) < n
select schemeCheckOn(root, path, "javascript:"),
"This check does not consider " +
strictconcat(DangerousScheme s | not exists(schemeCheckOn(root, path, s)) | s, " and ") + "."