-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathDoubleEscaping.ql
More file actions
187 lines (169 loc) · 5.64 KB
/
DoubleEscaping.ql
File metadata and controls
187 lines (169 loc) · 5.64 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
/**
* @name Double escaping or unescaping
* @description When escaping special characters using a meta-character like backslash or
* ampersand, the meta-character has to be escaped first to avoid double-escaping,
* and conversely it has to be unescaped last to avoid double-unescaping.
* @kind problem
* @problem.severity warning
* @precision high
* @id js/double-escaping
* @tags correctness
* security
* external/cwe/cwe-116
* external/cwe/cwe-20
*/
import javascript
/**
* Holds if `rl` is a simple constant, which is bound to the result of the predicate.
*
* For example, `/a/g` has string value `"a"` and `/abc/` has string value `"abc"`,
* while `/ab?/` and `/a(?=b)/` do not have a string value.
*
* Flags are ignored, so `/a/i` is still considered to have string value `"a"`,
* even though it also matches `"A"`.
*
* Note the somewhat subtle use of monotonic aggregate semantics, which makes the
* `strictconcat` fail if one of the children of the root is not a constant (legacy
* semantics would simply skip such children).
*/
language[monotonicAggregates]
string getStringValue(RegExpLiteral rl) {
exists(RegExpTerm root | root = rl.getRoot() |
result = root.(RegExpConstant).getValue()
or
result = strictconcat(RegExpTerm ch, int i |
ch = root.(RegExpSequence).getChild(i)
|
ch.(RegExpConstant).getValue() order by i
)
)
}
/**
* Gets a predecessor of `nd` that is not an SSA phi node.
*/
DataFlow::Node getASimplePredecessor(DataFlow::Node nd) {
result = nd.getAPredecessor() and
not nd.(DataFlow::SsaDefinitionNode).getSsaVariable().getDefinition() instanceof SsaPhiNode
}
/**
* Holds if `metachar` is a meta-character that is used to escape special characters
* into a form described by regular expression `regex`.
*/
predicate escapingScheme(string metachar, string regex) {
metachar = "&" and regex = "&.*;"
or
metachar = "%" and regex = "%.*"
or
metachar = "\\" and regex = "\\\\.*"
}
/**
* A method call that performs string replacement.
*/
abstract class Replacement extends DataFlow::Node {
/**
* Holds if this replacement replaces the string `input` with `output`.
*/
abstract predicate replaces(string input, string output);
/**
* Gets the input of this replacement.
*/
abstract DataFlow::Node getInput();
/**
* Gets the output of this replacement.
*/
abstract DataFlow::SourceNode getOutput();
/**
* Holds if this replacement escapes `char` using `metachar`.
*
* For example, during HTML entity escaping `<` is escaped (to `<`)
* using `&`.
*/
predicate escapes(string char, string metachar) {
exists(string regexp, string repl |
escapingScheme(metachar, regexp) and
replaces(char, repl) and
repl.regexpMatch(regexp)
)
}
/**
* Holds if this replacement unescapes `char` using `metachar`.
*
* For example, during HTML entity unescaping `<` is unescaped (from
* `<`) using `<`.
*/
predicate unescapes(string metachar, string char) {
exists(string regexp, string orig |
escapingScheme(metachar, regexp) and
replaces(orig, char) and
orig.regexpMatch(regexp)
)
}
/**
* Gets the previous replacement in this chain of replacements.
*/
Replacement getPreviousReplacement() {
result.getOutput() = getASimplePredecessor*(getInput())
}
/**
* Gets an earlier replacement in this chain of replacements that
* performs an escaping.
*/
Replacement getAnEarlierEscaping(string metachar) {
exists(Replacement pred | pred = this.getPreviousReplacement() |
if pred.escapes(_, metachar)
then result = pred
else result = pred.getAnEarlierEscaping(metachar)
)
}
/**
* Gets an earlier replacement in this chain of replacements that
* performs a unescaping.
*/
Replacement getALaterUnescaping(string metachar) {
exists(Replacement succ | this = succ.getPreviousReplacement() |
if succ.unescapes(metachar, _)
then result = succ
else result = succ.getALaterUnescaping(metachar)
)
}
}
/**
* A call to `String.prototype.replace` that replaces all instances of a pattern.
*/
class GlobalStringReplacement extends Replacement, DataFlow::MethodCallNode {
RegExpLiteral pattern;
GlobalStringReplacement() {
this.getMethodName() = "replace" and
pattern.flow().(DataFlow::SourceNode).flowsTo(this.getArgument(0)) and
this.getNumArgument() = 2 and
pattern.isGlobal()
}
override predicate replaces(string input, string output) {
input = getStringValue(pattern) and
output = this.getArgument(1).getStringValue()
or
exists(DataFlow::FunctionNode replacer, DataFlow::PropRead pr, DataFlow::ObjectLiteralNode map |
replacer = getCallback(1) and
replacer.getParameter(0).flowsToExpr(pr.getPropertyNameExpr()) and
pr = map.getAPropertyRead() and
pr.flowsTo(replacer.getAReturn()) and
map.asExpr().(ObjectExpr).getPropertyByName(input).getInit().getStringValue() = output
)
}
override DataFlow::Node getInput() {
result = this.getReceiver()
}
override DataFlow::SourceNode getOutput() {
result = this
}
}
from Replacement primary, Replacement supplementary, string message, string metachar
where
primary.escapes(metachar, _) and
supplementary = primary.getAnEarlierEscaping(metachar) and
message = "may double-escape '" + metachar + "' characters from $@"
or
primary.unescapes(_, metachar) and
supplementary = primary.getALaterUnescaping(metachar) and
message = "may produce '" + metachar + "' characters that are double-unescaped $@"
select primary, "This replacement " + message + ".", supplementary, "here"