Skip to content

Commit d6721ec

Browse files
committed
implement a isNaN guard for unsafe-shell-command-construction
1 parent 3206384 commit d6721ec

File tree

4 files changed

+66
-2
lines changed

4 files changed

+66
-2
lines changed

javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeShellCommandConstructionCustomizations.qll

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,29 @@ module UnsafeShellCommandConstruction {
286286
}
287287
}
288288

289+
/**
290+
* A guard of the form `isNaN(x)`, which sanitizes `x` in its "else" branch.
291+
*/
292+
class NaNGuard extends TaintTracking::SanitizerGuardNode instanceof DataFlow::CallNode {
293+
Expr x;
294+
295+
NaNGuard() {
296+
this = DataFlow::globalVarRef("isNaN").getACall() and
297+
(
298+
x = this.getArgument(0).asExpr()
299+
or
300+
exists(DataFlow::CallNode parse |
301+
parse = DataFlow::globalVarRef(["parseInt", "parseFloat"]).getACall()
302+
|
303+
parse = this.getArgument(0) and
304+
x = parse.getArgument(0).asExpr()
305+
)
306+
)
307+
}
308+
309+
override predicate sanitizes(boolean outcome, Expr e) { e = x and outcome = false }
310+
}
311+
289312
private import semmle.javascript.dataflow.internal.AccessPaths
290313
private import semmle.javascript.dataflow.InferredTypes
291314

javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeShellCommandConstructionQuery.qll

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ class Configuration extends TaintTracking::Configuration {
2424

2525
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
2626
guard instanceof PathExistsSanitizerGuard or
27-
guard instanceof TaintTracking::AdHocWhitelistCheckSanitizer
27+
guard instanceof TaintTracking::AdHocWhitelistCheckSanitizer or
28+
guard instanceof NaNGuard or
29+
guard instanceof TypeOfSanitizer
2830
}
2931

3032
// override to require that there is a path without unmatched return steps

javascript/ql/test/query-tests/Security/CWE-078/UnsafeShellCommandConstruction.expected

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,14 @@ nodes
254254
| lib/lib.js:498:45:498:48 | name |
255255
| lib/lib.js:499:31:499:34 | name |
256256
| lib/lib.js:499:31:499:34 | name |
257+
| lib/lib.js:509:39:509:42 | name |
258+
| lib/lib.js:509:39:509:42 | name |
259+
| lib/lib.js:510:22:510:25 | name |
260+
| lib/lib.js:510:22:510:25 | name |
261+
| lib/lib.js:513:23:513:26 | name |
262+
| lib/lib.js:513:23:513:26 | name |
263+
| lib/lib.js:519:23:519:26 | name |
264+
| lib/lib.js:519:23:519:26 | name |
257265
| lib/subLib2/compiled-file.ts:3:26:3:29 | name |
258266
| lib/subLib2/compiled-file.ts:3:26:3:29 | name |
259267
| lib/subLib2/compiled-file.ts:4:25:4:28 | name |
@@ -574,6 +582,18 @@ edges
574582
| lib/lib.js:498:45:498:48 | name | lib/lib.js:499:31:499:34 | name |
575583
| lib/lib.js:498:45:498:48 | name | lib/lib.js:499:31:499:34 | name |
576584
| lib/lib.js:498:45:498:48 | name | lib/lib.js:499:31:499:34 | name |
585+
| lib/lib.js:509:39:509:42 | name | lib/lib.js:510:22:510:25 | name |
586+
| lib/lib.js:509:39:509:42 | name | lib/lib.js:510:22:510:25 | name |
587+
| lib/lib.js:509:39:509:42 | name | lib/lib.js:510:22:510:25 | name |
588+
| lib/lib.js:509:39:509:42 | name | lib/lib.js:510:22:510:25 | name |
589+
| lib/lib.js:509:39:509:42 | name | lib/lib.js:513:23:513:26 | name |
590+
| lib/lib.js:509:39:509:42 | name | lib/lib.js:513:23:513:26 | name |
591+
| lib/lib.js:509:39:509:42 | name | lib/lib.js:513:23:513:26 | name |
592+
| lib/lib.js:509:39:509:42 | name | lib/lib.js:513:23:513:26 | name |
593+
| lib/lib.js:509:39:509:42 | name | lib/lib.js:519:23:519:26 | name |
594+
| lib/lib.js:509:39:509:42 | name | lib/lib.js:519:23:519:26 | name |
595+
| lib/lib.js:509:39:509:42 | name | lib/lib.js:519:23:519:26 | name |
596+
| lib/lib.js:509:39:509:42 | name | lib/lib.js:519:23:519:26 | name |
577597
| lib/subLib2/compiled-file.ts:3:26:3:29 | name | lib/subLib2/compiled-file.ts:4:25:4:28 | name |
578598
| lib/subLib2/compiled-file.ts:3:26:3:29 | name | lib/subLib2/compiled-file.ts:4:25:4:28 | name |
579599
| lib/subLib2/compiled-file.ts:3:26:3:29 | name | lib/subLib2/compiled-file.ts:4:25:4:28 | name |
@@ -666,6 +686,9 @@ edges
666686
| lib/lib.js:478:27:478:46 | config.installedPath | lib/lib.js:477:33:477:38 | config | lib/lib.js:478:27:478:46 | config.installedPath | $@ based on $@ is later used in $@. | lib/lib.js:478:27:478:46 | config.installedPath | Path concatenation | lib/lib.js:477:33:477:38 | config | library input | lib/lib.js:479:12:479:20 | exec(cmd) | shell command |
667687
| lib/lib.js:483:13:483:33 | ' my na ... + name | lib/lib.js:482:40:482:43 | name | lib/lib.js:483:30:483:33 | name | $@ based on $@ is later used in $@. | lib/lib.js:483:13:483:33 | ' my na ... + name | String concatenation | lib/lib.js:482:40:482:43 | name | library input | lib/lib.js:485:2:485:20 | cp.exec(cmd + args) | shell command |
668688
| lib/lib.js:499:19:499:34 | "rm -rf " + name | lib/lib.js:498:45:498:48 | name | lib/lib.js:499:31:499:34 | name | $@ based on $@ is later used in $@. | lib/lib.js:499:19:499:34 | "rm -rf " + name | String concatenation | lib/lib.js:498:45:498:48 | name | library input | lib/lib.js:499:3:499:35 | MyThing ... + name) | shell command |
689+
| lib/lib.js:510:10:510:25 | "rm -rf " + name | lib/lib.js:509:39:509:42 | name | lib/lib.js:510:22:510:25 | name | $@ based on $@ is later used in $@. | lib/lib.js:510:10:510:25 | "rm -rf " + name | String concatenation | lib/lib.js:509:39:509:42 | name | library input | lib/lib.js:510:2:510:26 | cp.exec ... + name) | shell command |
690+
| lib/lib.js:513:11:513:26 | "rm -rf " + name | lib/lib.js:509:39:509:42 | name | lib/lib.js:513:23:513:26 | name | $@ based on $@ is later used in $@. | lib/lib.js:513:11:513:26 | "rm -rf " + name | String concatenation | lib/lib.js:509:39:509:42 | name | library input | lib/lib.js:513:3:513:27 | cp.exec ... + name) | shell command |
691+
| lib/lib.js:519:11:519:26 | "rm -rf " + name | lib/lib.js:509:39:509:42 | name | lib/lib.js:519:23:519:26 | name | $@ based on $@ is later used in $@. | lib/lib.js:519:11:519:26 | "rm -rf " + name | String concatenation | lib/lib.js:509:39:509:42 | name | library input | lib/lib.js:519:3:519:27 | cp.exec ... + name) | shell command |
669692
| lib/subLib2/compiled-file.ts:4:13:4:28 | "rm -rf " + name | lib/subLib2/compiled-file.ts:3:26:3:29 | name | lib/subLib2/compiled-file.ts:4:25:4:28 | name | $@ based on $@ is later used in $@. | lib/subLib2/compiled-file.ts:4:13:4:28 | "rm -rf " + name | String concatenation | lib/subLib2/compiled-file.ts:3:26:3:29 | name | library input | lib/subLib2/compiled-file.ts:4:5:4:29 | cp.exec ... + name) | shell command |
670693
| lib/subLib2/special-file.js:4:10:4:25 | "rm -rf " + name | lib/subLib2/special-file.js:3:28:3:31 | name | lib/subLib2/special-file.js:4:22:4:25 | name | $@ based on $@ is later used in $@. | lib/subLib2/special-file.js:4:10:4:25 | "rm -rf " + name | String concatenation | lib/subLib2/special-file.js:3:28:3:31 | name | library input | lib/subLib2/special-file.js:4:2:4:26 | cp.exec ... + name) | shell command |
671694
| lib/subLib3/my-file.ts:4:10:4:25 | "rm -rf " + name | lib/subLib3/my-file.ts:3:28:3:31 | name | lib/subLib3/my-file.ts:4:22:4:25 | name | $@ based on $@ is later used in $@. | lib/subLib3/my-file.ts:4:10:4:25 | "rm -rf " + name | String concatenation | lib/subLib3/my-file.ts:3:28:3:31 | name | library input | lib/subLib3/my-file.ts:4:2:4:26 | cp.exec ... + name) | shell command |

javascript/ql/test/query-tests/Security/CWE-078/lib/lib.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -504,4 +504,20 @@ module.exports.myCommand = function (myCommand) {
504504
var imp = require('./isImported');
505505
for (var name in imp){
506506
module.exports[name] = imp[name];
507-
}
507+
}
508+
509+
module.exports.sanitizer4 = function (name) {
510+
cp.exec("rm -rf " + name); // NOT OK
511+
512+
if (isNaN(name)) {
513+
cp.exec("rm -rf " + name); // NOT OK
514+
} else {
515+
cp.exec("rm -rf " + name); // OK
516+
}
517+
518+
if (isNaN(parseInt(name))) {
519+
cp.exec("rm -rf " + name); // NOT OK
520+
} else {
521+
cp.exec("rm -rf " + name); // OK
522+
}
523+
}

0 commit comments

Comments
 (0)