Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 155cea7

Browse files
authored
Revert "JavaScript: Improve double-escaping query"
1 parent 429c307 commit 155cea7

5 files changed

Lines changed: 30 additions & 193 deletions

File tree

change-notes/1.23/analysis-javascript.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333

3434
| **Query** | **Expected impact** | **Change** |
3535
|--------------------------------|------------------------------|---------------------------------------------------------------------------|
36-
| Double escaping or unescaping (`js/double-escaping`) | More results | This rule now detects additional escaping and unescaping functions. |
3736
| Incomplete string escaping or encoding (`js/incomplete-sanitization`) | Fewer false-positive results | This rule now recognizes additional ways delimiters can be stripped away. |
3837
| Client-side cross-site scripting (`js/xss`) | More results, fewer false-positive results | More potential vulnerabilities involving functions that manipulate DOM attributes are now recognized, and more sanitizers are detected. |
3938
| Code injection (`js/code-injection`) | More results | More potential vulnerabilities involving functions that manipulate DOM event handler attributes are now recognized. |

javascript/ql/src/Security/CWE-116/DoubleEscaping.qhelp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ attacks such as cross-site scripting. One particular example of this is HTML ent
1010
where HTML special characters are replaced by HTML character entities to prevent them from being
1111
interpreted as HTML markup. For example, the less-than character is encoded as <code>&amp;lt;</code>
1212
and the double-quote character as <code>&amp;quot;</code>.
13-
Other examples include backslash escaping or JSON encoding for including untrusted data in string
14-
literals, and percent-encoding for URI components.
13+
Other examples include backslash-escaping for including untrusted data in string literals and
14+
percent-encoding for URI components.
1515
</p>
1616
<p>
1717
The reverse process of replacing escape sequences with the characters they represent is known as

javascript/ql/src/Security/CWE-116/DoubleEscaping.ql

Lines changed: 28 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -46,44 +46,46 @@ string getStringValue(RegExpLiteral rl) {
4646
*/
4747
DataFlow::Node getASimplePredecessor(DataFlow::Node nd) {
4848
result = nd.getAPredecessor() and
49-
not exists(SsaDefinition ssa |
50-
ssa = nd.(DataFlow::SsaDefinitionNode).getSsaVariable().getDefinition()
51-
|
52-
ssa instanceof SsaPhiNode or
53-
ssa instanceof SsaVariableCapture
54-
)
49+
not nd.(DataFlow::SsaDefinitionNode).getSsaVariable().getDefinition() instanceof SsaPhiNode
5550
}
5651

5752
/**
5853
* Holds if `metachar` is a meta-character that is used to escape special characters
5954
* into a form described by regular expression `regex`.
6055
*/
6156
predicate escapingScheme(string metachar, string regex) {
62-
metachar = "&" and regex = "&.+;"
57+
metachar = "&" and regex = "&.*;"
6358
or
64-
metachar = "%" and regex = "%.+"
59+
metachar = "%" and regex = "%.*"
6560
or
66-
metachar = "\\" and regex = "\\\\.+"
61+
metachar = "\\" and regex = "\\\\.*"
6762
}
6863

6964
/**
70-
* A method call that performs string replacement.
65+
* A call to `String.prototype.replace` that replaces all instances of a pattern.
7166
*/
72-
abstract class Replacement extends DataFlow::Node {
73-
/**
74-
* Holds if this replacement replaces the string `input` with `output`.
75-
*/
76-
abstract predicate replaces(string input, string output);
67+
class Replacement extends DataFlow::Node {
68+
RegExpLiteral pattern;
7769

78-
/**
79-
* Gets the input of this replacement.
80-
*/
81-
abstract DataFlow::Node getInput();
70+
Replacement() {
71+
exists(DataFlow::MethodCallNode mcn | this = mcn |
72+
mcn.getMethodName() = "replace" and
73+
pattern.flow().(DataFlow::SourceNode).flowsTo(mcn.getArgument(0)) and
74+
mcn.getNumArgument() = 2 and
75+
pattern.isGlobal()
76+
)
77+
}
8278

8379
/**
84-
* Gets the output of this replacement.
80+
* Holds if this replacement replaces the string `input` with `output`.
8581
*/
86-
abstract DataFlow::SourceNode getOutput();
82+
predicate replaces(string input, string output) {
83+
exists(DataFlow::MethodCallNode mcn |
84+
mcn = this and
85+
input = getStringValue(pattern) and
86+
output = mcn.getArgument(1).getStringValue()
87+
)
88+
}
8789

8890
/**
8991
* Holds if this replacement escapes `char` using `metachar`.
@@ -116,12 +118,9 @@ abstract class Replacement extends DataFlow::Node {
116118
/**
117119
* Gets the previous replacement in this chain of replacements.
118120
*/
119-
Replacement getPreviousReplacement() { result.getOutput() = getASimplePredecessor*(getInput()) }
120-
121-
/**
122-
* Gets the next replacement in this chain of replacements.
123-
*/
124-
Replacement getNextReplacement() { this = result.getPreviousReplacement() }
121+
Replacement getPreviousReplacement() {
122+
result = getASimplePredecessor*(this.(DataFlow::MethodCallNode).getReceiver())
123+
}
125124

126125
/**
127126
* Gets an earlier replacement in this chain of replacements that
@@ -131,9 +130,7 @@ abstract class Replacement extends DataFlow::Node {
131130
exists(Replacement pred | pred = this.getPreviousReplacement() |
132131
if pred.escapes(_, metachar)
133132
then result = pred
134-
else (
135-
not pred.unescapes(metachar, _) and result = pred.getAnEarlierEscaping(metachar)
136-
)
133+
else result = pred.getAnEarlierEscaping(metachar)
137134
)
138135
}
139136

@@ -145,98 +142,9 @@ abstract class Replacement extends DataFlow::Node {
145142
exists(Replacement succ | this = succ.getPreviousReplacement() |
146143
if succ.unescapes(metachar, _)
147144
then result = succ
148-
else (
149-
not succ.escapes(_, metachar) and result = succ.getALaterUnescaping(metachar)
150-
)
151-
)
152-
}
153-
}
154-
155-
/**
156-
* A call to `String.prototype.replace` that replaces all instances of a pattern.
157-
*/
158-
class GlobalStringReplacement extends Replacement, DataFlow::MethodCallNode {
159-
RegExpLiteral pattern;
160-
161-
GlobalStringReplacement() {
162-
this.getMethodName() = "replace" and
163-
pattern.flow().(DataFlow::SourceNode).flowsTo(this.getArgument(0)) and
164-
this.getNumArgument() = 2 and
165-
pattern.isGlobal()
166-
}
167-
168-
override predicate replaces(string input, string output) {
169-
input = getStringValue(pattern) and
170-
output = this.getArgument(1).getStringValue()
171-
or
172-
exists(DataFlow::FunctionNode replacer, DataFlow::PropRead pr, DataFlow::ObjectLiteralNode map |
173-
replacer = getCallback(1) and
174-
replacer.getParameter(0).flowsToExpr(pr.getPropertyNameExpr()) and
175-
pr = map.getAPropertyRead() and
176-
pr.flowsTo(replacer.getAReturn()) and
177-
map.asExpr().(ObjectExpr).getPropertyByName(input).getInit().getStringValue() = output
145+
else result = succ.getALaterUnescaping(metachar)
178146
)
179147
}
180-
181-
override DataFlow::Node getInput() { result = this.getReceiver() }
182-
183-
override DataFlow::SourceNode getOutput() { result = this }
184-
}
185-
186-
/**
187-
* A call to `JSON.stringify`, viewed as a string replacement.
188-
*/
189-
class JsonStringifyReplacement extends Replacement, DataFlow::CallNode {
190-
JsonStringifyReplacement() { this = DataFlow::globalVarRef("JSON").getAMemberCall("stringify") }
191-
192-
override predicate replaces(string input, string output) {
193-
input = "\\" and output = "\\\\"
194-
// the other replacements are not relevant for this query
195-
}
196-
197-
override DataFlow::Node getInput() { result = this.getArgument(0) }
198-
199-
override DataFlow::SourceNode getOutput() { result = this }
200-
}
201-
202-
/**
203-
* A call to `JSON.parse`, viewed as a string replacement.
204-
*/
205-
class JsonParseReplacement extends Replacement {
206-
JsonParserCall self;
207-
208-
JsonParseReplacement() { this = self }
209-
210-
override predicate replaces(string input, string output) {
211-
input = "\\\\" and output = "\\"
212-
// the other replacements are not relevant for this query
213-
}
214-
215-
override DataFlow::Node getInput() { result = self.getInput() }
216-
217-
override DataFlow::SourceNode getOutput() { result = self.getOutput() }
218-
}
219-
220-
/**
221-
* A string replacement wrapped in a utility function.
222-
*/
223-
class WrappedReplacement extends Replacement, DataFlow::CallNode {
224-
int i;
225-
226-
Replacement inner;
227-
228-
WrappedReplacement() {
229-
exists(DataFlow::FunctionNode wrapped | wrapped.getFunction() = getACallee() |
230-
wrapped.getParameter(i).flowsTo(inner.getPreviousReplacement*().getInput()) and
231-
inner.getNextReplacement*().getOutput().flowsTo(wrapped.getAReturn())
232-
)
233-
}
234-
235-
override predicate replaces(string input, string output) { inner.replaces(input, output) }
236-
237-
override DataFlow::Node getInput() { result = getArgument(i) }
238-
239-
override DataFlow::SourceNode getOutput() { result = this }
240148
}
241149

242150
from Replacement primary, Replacement supplementary, string message, string metachar

javascript/ql/test/query-tests/Security/CWE-116/DoubleEscaping/DoubleEscaping.expected

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,3 @@
55
| tst.js:53:10:53:33 | s.repla ... , '\\\\') | This replacement may produce '\\' characters that are double-unescaped $@. | tst.js:53:10:54:33 | s.repla ... , '\\'') | here |
66
| tst.js:60:7:60:28 | s.repla ... '%25') | This replacement may double-escape '%' characters from $@. | tst.js:59:7:59:28 | s.repla ... '%26') | here |
77
| tst.js:68:10:70:38 | s.repla ... &amp;") | This replacement may double-escape '&' characters from $@. | tst.js:68:10:69:39 | s.repla ... apos;") | here |
8-
| tst.js:74:10:77:10 | JSON.st ... ) | This replacement may double-escape '\\' characters from $@. | tst.js:75:12:76:37 | s.repla ... u003E") | here |
9-
| tst.js:86:10:86:22 | JSON.parse(s) | This replacement may produce '\\' characters that are double-unescaped $@. | tst.js:86:10:86:47 | JSON.pa ... g, "<") | here |
10-
| tst.js:99:10:99:66 | s.repla ... &amp;") | This replacement may double-escape '&' characters from $@. | tst.js:99:10:99:43 | s.repla ... epl[c]) | here |
11-
| tst.js:107:10:107:53 | encodeD ... &amp;") | This replacement may double-escape '&' characters from $@. | tst.js:107:10:107:30 | encodeD ... otes(s) | here |
12-
| tst.js:115:10:115:47 | encodeQ ... &amp;") | This replacement may double-escape '&' characters from $@. | tst.js:115:10:115:24 | encodeQuotes(s) | here |

javascript/ql/test/query-tests/Security/CWE-116/DoubleEscaping/tst.js

Lines changed: 0 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -69,68 +69,3 @@ function badEncode(s) {
6969
.replace(indirect2, "&apos;")
7070
.replace(indirect3, "&amp;");
7171
}
72-
73-
function badEscape1(s) {
74-
return JSON.stringify(
75-
s.replace(/</g, "\\u003C")
76-
.replace(/>/g, "\\u003E")
77-
);
78-
}
79-
80-
function goodEscape1(s) {
81-
return JSON.stringify(s)
82-
.replace(/</g, "\\u003C").replace(/>/g, "\\u003E");
83-
}
84-
85-
function badUnescape2(s) {
86-
return JSON.parse(s).replace(/\\u003C/g, "<").replace(/\\u003E/g, ">");
87-
}
88-
89-
function goodUnescape2(s) {
90-
return JSON.parse(s.replace(/\\u003C/g, "<").replace(/\\u003E/g, ">"));
91-
}
92-
93-
function badEncodeWithReplacer(s) {
94-
var repl = {
95-
'"': "&quot;",
96-
"'": "&apos;",
97-
"&": "&amp;"
98-
};
99-
return s.replace(/["']/g, (c) => repl[c]).replace(/&/g, "&amp;");
100-
}
101-
102-
function encodeDoubleQuotes(s) {
103-
return s.replace(/"/g, "&quot;");
104-
}
105-
106-
function badWrappedEncode(s) {
107-
return encodeDoubleQuotes(s).replace(/&/g, "&amp;");
108-
}
109-
110-
function encodeQuotes(s) {
111-
return s.replace(/"/g, "&quot;").replace(/'/g, "&apos;");
112-
}
113-
114-
function badWrappedEncode2(s) {
115-
return encodeQuotes(s).replace(/&/g, "&amp;");
116-
}
117-
118-
function roundtrip(s) {
119-
return JSON.parse(JSON.stringify(s));
120-
}
121-
122-
// dubious, but out of scope for this query
123-
function badRoundtrip(s) {
124-
return s.replace(/\\\\/g, "\\").replace(/\\/g, "\\\\");
125-
}
126-
127-
function testWithCapturedVar(x) {
128-
var captured = x;
129-
(function() {
130-
captured = captured.replace(/\\/g, "\\\\");
131-
})();
132-
}
133-
134-
function cloneAndStringify(s) {
135-
return JSON.stringify(JSON.parse(JSON.stringify(s)));
136-
}

0 commit comments

Comments
 (0)