Skip to content

JS: Env Injection query #15060

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 20, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>

<overview>

<p>
Controlling the value of arbitrary environment variables from user-controllable data is not safe.
</p>

</overview>

<recommendation>

<p>
Restrict this operation only to privileged users or only for some not important environment variables.
</p>

</recommendation>

<example>

<p>
The following example allows unauthorized users to assign a value to any environment variable.
</p>

<sample src="examples/Bad_Value_And_Key_Assignment.js" />

</example>

<references>
<li> <a href="https://huntr.com/bounties/00ec6847-125b-43e9-9658-d3cace1751d6/">Admin account TakeOver in mintplex-labs/anything-llm</a></li>
</references>

</qhelp>
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* @name User controlled arbitrary environment variable injection
* @description creating arbitrary environment variables from user controlled data is not secure
* @kind path-problem
* @id js/env-key-and-value-injection
* @problem.severity error
* @security-severity 7.5
* @precision medium
* @tags security
* external/cwe/cwe-089
*/

import javascript
import DataFlow::PathGraph

/** A taint tracking configuration for unsafe environment injection. */
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "envInjection" }

override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }

override predicate isSink(DataFlow::Node sink) {
sink = keyOfEnv() or
sink = valueOfEnv()
}

override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::InvokeNode ikn |
ikn = DataFlow::globalVarRef("Object").getAMemberInvocation("keys")
|
pred = ikn.getArgument(0) and
(
succ = ikn.getAChainedMethodCall(["filter", "map"]) or
succ = ikn or
succ = ikn.getAChainedMethodCall("forEach").getABoundCallbackParameter(0, 0)
)
)
}
}

DataFlow::Node keyOfEnv() {
result =
NodeJSLib::process().getAPropertyRead("env").getAPropertyWrite().getPropertyNameExpr().flow()
}

DataFlow::Node valueOfEnv() {
result = API::moduleImport("process").getMember("env").getAMember().asSink()
}

private predicate readToProcessEnv(DataFlow::Node envKey, DataFlow::Node envValue) {
exists(DataFlow::PropWrite env |
env = NodeJSLib::process().getAPropertyRead("env").getAPropertyWrite()
|
envKey = env.getPropertyNameExpr().flow() and
envValue = env.getRhs()
)
}

from
Configuration cfgForValue, Configuration cfgForKey, DataFlow::PathNode source,
DataFlow::PathNode envKey, DataFlow::PathNode envValue
where
cfgForValue.hasFlowPath(source, envKey) and
envKey.getNode() = keyOfEnv() and
cfgForKey.hasFlowPath(source, envValue) and
envValue.getNode() = valueOfEnv() and
readToProcessEnv(envKey.getNode(), envValue.getNode())
select envKey.getNode(), source, envKey, "arbitrary environment variable assignment from this $@.",
source.getNode(), "user controllable source"
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>

<overview>

<p>
Assigning Value to environment variables from user-controllable data is not safe.
</p>

</overview>

<recommendation>

<p>
Restrict this operation only to privileged users or only for some not important environment variables.
</p>

</recommendation>

<example>

<p>
The following example allows unauthorized users to assign a value to a critical environment variable.
</p>

<sample src="examples/Bad_Value_Assignment.js" />

</example>

<references>
<li><a href="https://huntr.com/bounties/00ec6847-125b-43e9-9658-d3cace1751d6/">Admin account TakeOver in mintplex-labs/anything-llm</a></li>
</references>

</qhelp>
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* @name User controlled environment variable value injection
* @description assigning important environment variables from user controlled data is not secure
* @kind path-problem
* @id js/env-value-injection
* @problem.severity error
* @security-severity 7.5
* @precision medium
* @tags security
* external/cwe/cwe-089
*/

import javascript
import DataFlow::PathGraph

/** A taint tracking configuration for unsafe environment injection. */
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "envInjection" }

override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }

override predicate isSink(DataFlow::Node sink) {
sink = API::moduleImport("process").getMember("env").getAMember().asSink()
}
}

from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "this environment variable assignment is $@.",
source.getNode(), "user controllable"
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const http = require('node:http');

http.createServer((req, res) => {
const { EnvValue, EnvKey } = req.body;
process.env[EnvKey] = EnvValue; // NOT OK

res.end('env has been injected!');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const http = require('node:http');

http.createServer((req, res) => {
const { EnvValue } = req.body;
process.env["A_Critical_Env"] = EnvValue; // NOT OK

res.end('env has been injected!');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
nodes
| test.js:5:9:5:28 | { EnvValue, EnvKey } |
| test.js:5:9:5:39 | EnvKey |
| test.js:5:9:5:39 | EnvValue |
| test.js:5:11:5:18 | EnvValue |
| test.js:5:21:5:26 | EnvKey |
| test.js:5:32:5:39 | req.body |
| test.js:5:32:5:39 | req.body |
| test.js:6:15:6:20 | EnvKey |
| test.js:6:15:6:20 | EnvKey |
| test.js:6:25:6:32 | EnvValue |
| test.js:6:25:6:32 | EnvValue |
| test.js:7:15:7:20 | EnvKey |
| test.js:7:15:7:20 | EnvKey |
| test.js:7:25:7:32 | EnvValue |
| test.js:7:25:7:32 | EnvValue |
| test.js:13:9:13:28 | { EnvValue, EnvKey } |
| test.js:13:9:13:39 | EnvKey |
| test.js:13:9:13:39 | EnvValue |
| test.js:13:11:13:18 | EnvValue |
| test.js:13:21:13:26 | EnvKey |
| test.js:13:32:13:39 | req.body |
| test.js:13:32:13:39 | req.body |
| test.js:15:15:15:20 | EnvKey |
| test.js:15:15:15:20 | EnvKey |
| test.js:16:26:16:33 | EnvValue |
| test.js:16:26:16:33 | EnvValue |
edges
| test.js:5:9:5:28 | { EnvValue, EnvKey } | test.js:5:11:5:18 | EnvValue |
| test.js:5:9:5:28 | { EnvValue, EnvKey } | test.js:5:21:5:26 | EnvKey |
| test.js:5:9:5:39 | EnvKey | test.js:6:15:6:20 | EnvKey |
| test.js:5:9:5:39 | EnvKey | test.js:6:15:6:20 | EnvKey |
| test.js:5:9:5:39 | EnvKey | test.js:7:15:7:20 | EnvKey |
| test.js:5:9:5:39 | EnvKey | test.js:7:15:7:20 | EnvKey |
| test.js:5:9:5:39 | EnvValue | test.js:6:25:6:32 | EnvValue |
| test.js:5:9:5:39 | EnvValue | test.js:6:25:6:32 | EnvValue |
| test.js:5:9:5:39 | EnvValue | test.js:7:25:7:32 | EnvValue |
| test.js:5:9:5:39 | EnvValue | test.js:7:25:7:32 | EnvValue |
| test.js:5:11:5:18 | EnvValue | test.js:5:9:5:39 | EnvValue |
| test.js:5:21:5:26 | EnvKey | test.js:5:9:5:39 | EnvKey |
| test.js:5:32:5:39 | req.body | test.js:5:9:5:28 | { EnvValue, EnvKey } |
| test.js:5:32:5:39 | req.body | test.js:5:9:5:28 | { EnvValue, EnvKey } |
| test.js:13:9:13:28 | { EnvValue, EnvKey } | test.js:13:11:13:18 | EnvValue |
| test.js:13:9:13:28 | { EnvValue, EnvKey } | test.js:13:21:13:26 | EnvKey |
| test.js:13:9:13:39 | EnvKey | test.js:15:15:15:20 | EnvKey |
| test.js:13:9:13:39 | EnvKey | test.js:15:15:15:20 | EnvKey |
| test.js:13:9:13:39 | EnvValue | test.js:16:26:16:33 | EnvValue |
| test.js:13:9:13:39 | EnvValue | test.js:16:26:16:33 | EnvValue |
| test.js:13:11:13:18 | EnvValue | test.js:13:9:13:39 | EnvValue |
| test.js:13:21:13:26 | EnvKey | test.js:13:9:13:39 | EnvKey |
| test.js:13:32:13:39 | req.body | test.js:13:9:13:28 | { EnvValue, EnvKey } |
| test.js:13:32:13:39 | req.body | test.js:13:9:13:28 | { EnvValue, EnvKey } |
#select
| test.js:6:15:6:20 | EnvKey | test.js:5:32:5:39 | req.body | test.js:6:15:6:20 | EnvKey | arbitrary environment variable assignment from this $@. | test.js:5:32:5:39 | req.body | user controllable source |
| test.js:7:15:7:20 | EnvKey | test.js:5:32:5:39 | req.body | test.js:7:15:7:20 | EnvKey | arbitrary environment variable assignment from this $@. | test.js:5:32:5:39 | req.body | user controllable source |
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
experimental/Security/CWE-099/EnvValueAndKeyInjection.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const http = require('node:http');


http.createServer((req, res) => {
const { EnvValue, EnvKey } = req.body;
process.env[EnvKey] = EnvValue; // NOT OK
process.env[EnvKey] = EnvValue; // NOT OK

res.end('env has been injected!');
});

http.createServer((req, res) => {
const { EnvValue, EnvKey } = req.body;

process.env[EnvKey] = "constant" // OK
process.env.constant = EnvValue // OK

res.end('env has been injected!');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
nodes
| test.js:4:9:4:20 | { EnvValue } |
| test.js:4:9:4:31 | EnvValue |
| test.js:4:11:4:18 | EnvValue |
| test.js:4:24:4:31 | req.body |
| test.js:4:24:4:31 | req.body |
| test.js:5:35:5:42 | EnvValue |
| test.js:5:35:5:42 | EnvValue |
| test.js:6:23:6:30 | EnvValue |
| test.js:6:23:6:30 | EnvValue |
| test.js:7:22:7:29 | EnvValue |
| test.js:7:22:7:29 | EnvValue |
edges
| test.js:4:9:4:20 | { EnvValue } | test.js:4:11:4:18 | EnvValue |
| test.js:4:9:4:31 | EnvValue | test.js:5:35:5:42 | EnvValue |
| test.js:4:9:4:31 | EnvValue | test.js:5:35:5:42 | EnvValue |
| test.js:4:9:4:31 | EnvValue | test.js:6:23:6:30 | EnvValue |
| test.js:4:9:4:31 | EnvValue | test.js:6:23:6:30 | EnvValue |
| test.js:4:9:4:31 | EnvValue | test.js:7:22:7:29 | EnvValue |
| test.js:4:9:4:31 | EnvValue | test.js:7:22:7:29 | EnvValue |
| test.js:4:11:4:18 | EnvValue | test.js:4:9:4:31 | EnvValue |
| test.js:4:24:4:31 | req.body | test.js:4:9:4:20 | { EnvValue } |
| test.js:4:24:4:31 | req.body | test.js:4:9:4:20 | { EnvValue } |
#select
| test.js:5:35:5:42 | EnvValue | test.js:4:24:4:31 | req.body | test.js:5:35:5:42 | EnvValue | this environment variable assignment is $@. | test.js:4:24:4:31 | req.body | user controllable |
| test.js:6:23:6:30 | EnvValue | test.js:4:24:4:31 | req.body | test.js:6:23:6:30 | EnvValue | this environment variable assignment is $@. | test.js:4:24:4:31 | req.body | user controllable |
| test.js:7:22:7:29 | EnvValue | test.js:4:24:4:31 | req.body | test.js:7:22:7:29 | EnvValue | this environment variable assignment is $@. | test.js:4:24:4:31 | req.body | user controllable |
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
experimental/Security/CWE-099/EnvValueInjection.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const http = require('node:http');

http.createServer((req, res) => {
const { EnvValue } = req.body;
process.env["A_Critical_Env"] = EnvValue; // NOT OK
process.env[AKey] = EnvValue; // NOT OK
process.env.AKey = EnvValue; // NOT OK

res.end('env has been injected!');
});