Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions change-notes/1.18/analysis-javascript.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
| Arguments redefined | Fewer results | This rule previously also flagged redefinitions of `eval`. This was an oversight that is now fixed. |
| CORS misconfiguration for credentials transfer | More true-positive results | This rule now treats header names case-insensitively. |
| Hard-coded credentials | More true-positive results | This rule now recognizes secret cryptographic keys. |
| Incomplete sanitization | More true-positive results | This rule now recognizes incomplete URL encoding and decoding. |
| Insecure randomness | More true-positive results | This rule now recognizes secret cryptographic keys. |
| Missing rate limiting | More true-positive results, fewer false-positive results | This rule now recognizes additional rate limiters and expensive route handlers. |
| Missing X-Frame-Options HTTP header | Fewer false-positive results | This rule now treats header names case-insensitively. |
Expand Down
15 changes: 13 additions & 2 deletions javascript/ql/src/Security/CWE-116/IncompleteSanitization.ql
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,19 @@ where repl.getMethodName() = "replace" and
(
not old.(RegExpLiteral).isGlobal() and
msg = "This replaces only the first occurrence of " + old + "." and
// only flag if this is likely to be a sanitizer
getAMatchedString(old) = metachar() and
// only flag if this is likely to be a sanitizer or URL encoder or decoder
exists (string m | m = getAMatchedString(old) |
// sanitizer
m = metachar()
or
exists (string urlEscapePattern | urlEscapePattern = "(%[0-9A-Fa-f]{2})+" |
// URL decoder
m.regexpMatch(urlEscapePattern)
or
// URL encoder
repl.getArgument(1).getStringValue().regexpMatch(urlEscapePattern)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a real problem in this case, but it does bother me a bit that m isn't bound in the last case. It's not that obvious that there is no cartesian product here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can pull it out of the exists if you like and add and exists(getAMatchedString(old)) instead (that bit is important to avoid false positives from cases where we cannot analyse old).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah right, if the exists matters then let's keep it like this.

)
) and
// don't flag replace operations in a loop
not DataFlow::valueNode(repl.getReceiver()) = DataFlow::valueNode(repl).getASuccessor+()
or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@
| tst.js:29:20:29:27 | /('\|")/g | This does not backslash-escape the backslash character. |
| tst.js:33:20:33:22 | '\|' | This replaces only the first occurrence of '\|'. |
| tst.js:37:20:37:23 | /"/g | This does not backslash-escape the backslash character. |
| tst.js:41:20:41:22 | "/" | This replaces only the first occurrence of "/". |
| tst.js:45:20:45:24 | "%25" | This replaces only the first occurrence of "%25". |
15 changes: 15 additions & 0 deletions javascript/ql/test/query-tests/Security/CWE-116/tst.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ function bad9(s) {
return s.replace(/"/g, "\\\""); // NOT OK
}

function bad10(s) {
return s.replace("/", "%2F"); // NOT OK
}

function bad11(s) {
return s.replace("%25", "%"); // NOT OK
}


function good1(s) {
while (s.indexOf("'") > 0)
Expand Down Expand Up @@ -91,6 +99,10 @@ function flowifyComments(s) {
return s.replace(/#/g, '💩'); // OK
}

function good11(s) {
return s.replace("%d", "42");
}

app.get('/some/path', function(req, res) {
let untrusted = req.param("p");

Expand All @@ -106,6 +118,8 @@ app.get('/some/path', function(req, res) {
bad7(untrusted);
bad8(untrusted);
bad9(untrusted);
bad10(untrusted);
bad11(untrusted);

good1(untrusted);
good2(untrusted);
Expand All @@ -118,4 +132,5 @@ app.get('/some/path', function(req, res) {
good9(untrusted);
good10(untrusted);
flowifyComments(untrusted);
good11(untrusted);
});