Merge pull request #19422 from Napalys/js/shelljs

JS: Modeling of `ShellJS` functions
This commit is contained in:
Napalys Klicius
2025-05-02 14:18:44 +02:00
committed by GitHub
10 changed files with 63 additions and 11 deletions

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Improved modeling of the [`shelljs`](https://www.npmjs.com/package/shelljs) and [`async-shelljs`](https://www.npmjs.com/package/async-shelljs) libraries by adding support for the `which`, `cmd`, `asyncExec` and `env`.

View File

@@ -0,0 +1,6 @@
extensions:
- addsTo:
pack: codeql/javascript-all
extensible: sourceModel
data:
- ["shelljs", "Member[env]", "environment"]

View File

@@ -14,7 +14,8 @@ module ShellJS {
shellJSMember()
.getMember([
"exec", "cd", "cp", "touch", "chmod", "pushd", "find", "ls", "ln", "mkdir", "mv",
"rm", "cat", "head", "sort", "tail", "uniq", "grep", "sed", "to", "toEnd", "echo"
"rm", "cat", "head", "sort", "tail", "uniq", "grep", "sed", "to", "toEnd", "echo",
"which", "cmd", "asyncExec"
])
.getReturn()
}
@@ -99,7 +100,8 @@ module ShellJS {
*/
private class ShellJSGenericFileAccess extends FileSystemAccess, ShellJSCall {
ShellJSGenericFileAccess() {
name = ["cd", "cp", "touch", "chmod", "pushd", "find", "ls", "ln", "mkdir", "mv", "rm"]
name =
["cd", "cp", "touch", "chmod", "pushd", "find", "ls", "ln", "mkdir", "mv", "rm", "which"]
}
override DataFlow::Node getAPathArgument() { result = this.getAnArgument() }
@@ -111,7 +113,8 @@ module ShellJS {
private class ShellJSFilenameSource extends FileNameSource, ShellJSCall {
ShellJSFilenameSource() {
name = "find" or
name = "ls"
name = "ls" or
name = "which"
}
}
@@ -151,16 +154,24 @@ module ShellJS {
}
/**
* A call to `shelljs.exec()` modeled as command execution.
* A call to `shelljs.exec()`, `shelljs.cmd()`, or `async-shelljs.asyncExec()` modeled as command execution.
*/
private class ShellJSExec extends SystemCommandExecution, ShellJSCall {
ShellJSExec() { name = "exec" }
ShellJSExec() { name = ["exec", "cmd", "asyncExec"] }
override DataFlow::Node getACommandArgument() { result = this.getArgument(0) }
override DataFlow::Node getACommandArgument() {
if name = "cmd"
then
result = this.getArgument(_) and
not result = this.getOptionsArg()
else
// For exec/asyncExec: only first argument is command
result = this.getArgument(0)
}
override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getACommandArgument() }
override predicate isSync() { none() }
override predicate isSync() { name = "cmd" }
override DataFlow::Node getOptionsArg() {
result = this.getLastArgument() and

View File

@@ -171,7 +171,7 @@ module CleartextLogging {
/** An access to the sensitive object `process.env`. */
class ProcessEnvSource extends Source {
ProcessEnvSource() { this = NodeJSLib::process().getAPropertyRead("env") }
ProcessEnvSource() { this.(ThreatModelSource).getThreatModel() = "environment" }
override string describe() { result = "process environment" }
}

View File

@@ -29,7 +29,7 @@ module IndirectCommandInjection {
* A read of `process.env`, considered as a flow source for command injection.
*/
private class ProcessEnvAsSource extends Source {
ProcessEnvAsSource() { this = NodeJSLib::process().getAPropertyRead("env") }
ProcessEnvAsSource() { this.(ThreatModelSource).getThreatModel() = "environment" }
override string describe() { result = "environment variable" }
}
@@ -37,7 +37,7 @@ module IndirectCommandInjection {
/** Gets a data flow node referring to `process.env`. */
private DataFlow::SourceNode envObject(DataFlow::TypeTracker t) {
t.start() and
result = NodeJSLib::process().getAPropertyRead("env")
result.(ThreatModelSource).getThreatModel() = "environment"
or
exists(DataFlow::TypeTracker t2 | result = envObject(t2).track(t2, t))
}