Skip to content

Replace dead link in qhelp files - #22880

Open
geoffw0 wants to merge 1 commit into
github:mainfrom
geoffw0:deadlink2
Open

geoffw0 wants to merge 1 commit into
github:mainfrom
geoffw0:deadlink2

Conversation

@geoffw0

@geoffw0 geoffw0 commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Replace a dead link that's repeated in quite a number of .qhelp files.

Copilot AI max review requested due to automatic review settings October 23, 2026 17:59
@geoffw0
geoffw0 requested a review from a team as a code owner October 23, 2026 17:59
@geoffw0
geoffw0 requested review from a team as code owners October 23, 2026 17:59
@geoffw0 geoffw0 added the no-change-note-required This PR does not need a change note label Oct 23, 2026
@geoffw0
geoffw0 requested review from a team as code owners October 23, 2026 17:59
@github-actions

Copy link
Copy Markdown
Contributor

QHelp previews:

cpp/ql/src/Security/CWE/CWE-078/ExecTainted.qhelp

Uncontrolled data used in OS command

The code passes user input as part of a call to system or popen without escaping special elements. It generates a command line using sprintf, with the user-supplied data directly passed as a formatting argument. This leaves the code vulnerable to attack by command injection.

Recommendation

Use a library routine to escape characters in the user-supplied string before passing it to a command shell.

Example

The following example runs an external command in two ways. The first way uses sprintf to build a command directly out of a user-supplied argument. As such, it is vulnerable to command injection. The second way quotes the user-provided value before embedding it in the command; assuming the encodeShellString utility is correct, this code should be safe against command injection.

int main(int argc, char** argv) {
  char *userName = argv[2];
  
  {
    // BAD: a string from the user is injected directly into
    // a command line.
    char command1[1000] = {0};
    sprintf(command1, "userinfo -v \"%s\"", userName);
    system(command1);
  }

  {
    // GOOD: the user string is encoded by a library routine.
    char userNameQuoted[1000] = {0};
    encodeShellString(userNameQuoted, 1000, userName); 
    char command2[1000] = {0};
    sprintf(command2, "userinfo -v %s", userNameQuoted);
    system(command2);
  }
}

References

cpp/ql/src/experimental/Security/CWE/CWE-078/WordexpTainted.qhelp

Uncontrolled data used in wordexp command

The code passes user input to wordexp. This leaves the code vulnerable to attack by command injection, because wordexp performs command substitution. Command substitution is a feature that replaces $(command) or `command` with the output of the given command, allowing the user to run arbitrary code on the system.

Recommendation

When calling wordexp, pass the WRDE_NOCMD flag to prevent command substitution.

Example

The following example passes a user-supplied file path to wordexp in two ways. The first way uses wordexp with no specified flags. As such, it is vulnerable to command injection. The second way uses wordexp with the WRDE_NOCMD flag. As such, no command substitution is performed, making this safe from command injection.

int main(int argc, char** argv) {
  char *filePath = argv[2];

  {
    // BAD: the user-controlled string is injected
    // directly into `wordexp` which performs command substitution

    wordexp_t we;
    wordexp(filePath, &we, 0);
  }

  {
    // GOOD: command substitution is disabled

    wordexp_t we;
    wordexp(filePath, &we, WRDE_NOCMD);
  }
}

References

csharp/ql/src/Security Features/CWE-078/CommandInjection.qhelp

Uncontrolled command line

Code that passes user input directly to System.Diagnostic.Process.Start, or some other library routine that executes a command, allows the user to execute malicious code.

Recommendation

If possible, use hard-coded string literals to specify the command to run or library to load. Instead of passing the user input directly to the process or library function, examine the user input and then choose among hard-coded string literals.

If the applicable libraries or commands cannot be determined at compile time, then add code to verify that the user input string is safe before using it.

Example

The following example shows code that takes a shell script that can be changed maliciously by a user, and passes it straight to System.Diagnostic.Process.Start without examining it first.

using System;
using System.Web;
using System.Diagnostics;

public class CommandInjectionHandler : IHttpHandler
{
    public void ProcessRequest(HttpContext ctx)
    {
        string param = ctx.Request.QueryString["param"];
        Process.Start("process.exe", "/c " + param);
    }
}

References

go/ql/src/Security/CWE-078/CommandInjection.qhelp

Command built from user-controlled sources

If a system command invocation is built from user-provided data without sufficient sanitization, a malicious user may be able to run commands to exfiltrate data or compromise the system.

Recommendation

Whenever possible, use hard-coded string literals for commands and avoid shell string interpreters like sh -c.

If given arguments as a single string, avoid simply splitting the string on whitespace. Arguments may contain quoted whitespace, causing them to split into multiple arguments.

If this is not possible, sanitize user input to avoid characters like spaces and various kinds of quotes that can alter the meaning of the command.

Example

In the following example, assume the function handler is an HTTP request handler in a web application, whose parameter req contains the request object:

package main

import (
	"net/http"
	"os/exec"
)

func handler(req *http.Request) {
	imageName := req.URL.Query()["imageName"][0]
	outputPath := "/tmp/output.svg"
	cmd := exec.Command("sh", "-c", fmt.Sprintf("imagetool %s > %s", imageName, outputPath))
	cmd.Run()
	// ...
}

The handler extracts the image file name from the request and uses the image name to construct a shell command that is executed using `sh -c`, which can lead to command injection.

It's better to avoid shell commands by using the exec.Command function directly, as shown in the following example:

package main

import (
	"log"
	"net/http"
	"os"
	"os/exec"
)

func handler(req *http.Request) {
	imageName := req.URL.Query()["imageName"][0]
	outputPath := "/tmp/output.svg"

	// Create the output file
	outfile, err := os.Create(outputPath)
	if err != nil {
		log.Fatal(err)
	}
	defer outfile.Close()

	// Prepare the command
	cmd := exec.Command("imagetool", imageName)

	// Set the output to our file
	cmd.Stdout = outfile

	cmd.Run()
}

Alternatively, a regular expression can be used to ensure that the image name is safe to use in a shell command:

package main

import (
	"log"
	"net/http"
	"os/exec"
	"regexp"
)

func handler(req *http.Request) {
	imageName := req.URL.Query()["imageName"][0]
	outputPath := "/tmp/output.svg"

	// Validate the imageName with a regular expression
	validImageName := regexp.MustCompile(`^[a-zA-Z0-9_\-\.]+$`)
	if !validImageName.MatchString(imageName) {
		log.Fatal("Invalid image name")
		return
	}

	cmd := exec.Command("sh", "-c", fmt.Sprintf("imagetool %s > %s", imageName, outputPath))
	cmd.Run()
}

Some commands, like git, can indirectly execute commands if an attacker specifies the flags given to the command.

To mitigate this risk, either add a -- argument to ensure subsequent arguments are not interpreted as flags, or verify that the argument does not start with "--".

package main

import (
	"log"
	"net/http"
	"os/exec"
	"strings"
)

func handler(req *http.Request) {
	repoURL := req.URL.Query()["repoURL"][0]
	outputPath := "/tmp/repo"

	// Sanitize the repoURL to ensure it does not start with "--"
	if strings.HasPrefix(repoURL, "--") {
		log.Fatal("Invalid repository URL")
	} else {
		cmd := exec.Command("git", "clone", repoURL, outputPath)
		err := cmd.Run()
		if err != nil {
			log.Fatal(err)
		}
	}

	// Or: add "--" to ensure that the repoURL is not interpreted as a flag
	cmd := exec.Command("git", "clone", "--", repoURL, outputPath)
	err := cmd.Run()
	if err != nil {
		log.Fatal(err)
	}
}

References

go/ql/src/Security/CWE-078/StoredCommand.qhelp

Command built from stored data

If a system command invocation is built from stored data without sufficient sanitization, and that data is stored from a user input, a malicious user may be able to run commands to exfiltrate data or compromise the system.

Recommendation

If possible, use hard-coded string literals to specify the command to run. Instead of interpreting stored input directly as command names, examine the input and then choose among hard-coded string literals.

If this is not possible, then add sanitization code to verify that the user input is safe before using it.

Example

In the following example, the function run runs a command directly from the result of a query:

package main

import (
	"database/sql"
	"os/exec"
)

var db *sql.DB

func run(query string) {
	rows, _ := db.Query(query)
	var cmdName string
	rows.Scan(&cmdName)
	cmd := exec.Command(cmdName)
	cmd.Run()
}

The function extracts the name of a system command from the database query, and then runs it without any further checks, which can cause a command-injection vulnerability. A possible solution is to ensure that commands are checked against a whitelist:

package main

import (
	"database/sql"
	"os/exec"
)

var db *sql.DB

func run(query string) {
	rows, _ := db.Query(query)
	var cmdName string
	rows.Scan(&cmdName)
	if cmdName == "mybinary1" || cmdName == "mybinary2" {
		cmd := exec.Command(cmdName)
	}
	cmd.Run()
}

References

go/ql/src/Security/CWE-089/StringBreak.qhelp

Potentially unsafe quoting

Code that constructs a quoted string literal containing user-provided data needs to ensure that this data does not itself contain a quote. Otherwise the embedded data could (accidentally or intentionally) terminate the string literal early and thereby change the structure of the overall string, with potentially severe consequences. If, for example, the string is later used as part of an operating-system command or database query, an attacker may be able to craft input data that injects a malicious command.

Recommendation

Sanitize the embedded data appropriately to ensure quotes are escaped, or use an API that does not rely on manually constructing quoted substrings. Make sure to use the appropriate escaping mechanism, for example, double quoting for SQL strings or backslash escaping for shell commands. When using backslash escaping, the backslash character itself must also be escaped.

Example

In the following example, assume that version is an object from an untrusted source. The code snippet first uses json.Marshal to serialize this object into a string, and then embeds it into a SQL query built using the Squirrel library.

package main

import (
	"encoding/json"
	"fmt"
	sq "github.com/Masterminds/squirrel"
)

func save(id string, version interface{}) {
	versionJSON, _ := json.Marshal(version)
	sq.StatementBuilder.
		Insert("resources").
		Columns("resource_id", "version_md5").
		Values(id, sq.Expr(fmt.Sprintf("md5('%s')", versionJSON))).
		Exec()
}

Note that JSON encoding does not escape single quotes in any way, so this code is vulnerable: any single-quote character in version will prematurely close the surrounding string literal, changing the structure of the SQL expression being constructed. This could be exploited to mount a SQL injection attack.

To fix this vulnerability, use the placeholder syntax from Squirrel's structured API for building queries, which avoids the need to explicitly construct a quoted string.

package main

import (
	"encoding/json"
	sq "github.com/Masterminds/squirrel"
)

func saveGood(id string, version interface{}) {
	versionJSON, _ := json.Marshal(version)
	sq.StatementBuilder.
		Insert("resources").
		Columns("resource_id", "version_md5").
		Values(id, sq.Expr("md5(?)", versionJSON)).
		Exec()
}

In situations where a structured API is not available, make sure that you escape quotes before embedding user-provided data into a quoted string. For example, this is how you can backslash-escape single quotes using strings.ReplaceAll:

  quoted := strings.ReplaceAll(raw, `\`, `\\`)
  quoted = strings.ReplaceAll(quoted, "'", "\\'")

Note that any existing backslash characters in the string must be escaped first, so that they do not interfere with the escaping of single quotes.

In some cases, strconv.Quote is a convenient option for backslash escaping, but note that it has two limitations:

  1. It only supports double quotes, not single quotes (as in the example).
  2. It puts quotes around the entire string, so it can only be used to construct complete string literals, not parts of larger string literals.

References

java/ql/src/Security/CWE/CWE-078/ExecTainted.qhelp

Uncontrolled command line

Code that passes user input directly to Runtime.exec, or some other library routine that executes a command, allows the user to execute malicious code.

Recommendation

If possible, use hard-coded string literals to specify the command to run or library to load. Instead of passing the user input directly to the process or library function, examine the user input and then choose among hard-coded string literals.

If the applicable libraries or commands cannot be determined at compile time, then add code to verify that the user input string is safe before using it.

Example

The following example shows code that takes a shell script that can be changed maliciously by a user, and passes it straight to Runtime.exec without examining it first.

class Test {
    public static void main(String[] args) {
        String script = System.getenv("SCRIPTNAME");
        if (script != null) {
            // BAD: The script to be executed is controlled by the user.
            Runtime.getRuntime().exec(script);
        }
    }
}

References

java/ql/src/Security/CWE/CWE-078/ExecUnescaped.qhelp

Building a command line with string concatenation

Code that builds a command line by concatenating strings that have been entered by a user allows the user to execute malicious code.

Recommendation

Execute external commands using an array of strings rather than a single string. By using an array, many possible vulnerabilities in the formatting of the string are avoided.

Example

In the following example, latlonCoords contains a string that has been entered by a user but not validated by the program. This allows the user to, for example, append an ampersand (&) followed by the command for a malicious program to the end of the string. The ampersand instructs Windows to execute another program. In the block marked 'BAD', latlonCoords is passed to exec as part of a concatenated string, which allows more than one command to be executed. However, in the block marked 'GOOD', latlonCoords is passed as part of an array, which means that exec treats it only as an argument.

class Test {
    public static void main(String[] args) {
        // BAD: user input might include special characters such as ampersands
        {
            String latlonCoords = args[1];
            Runtime rt = Runtime.getRuntime();
            Process exec = rt.exec("cmd.exe /C latlon2utm.exe " + latlonCoords);
        }

        // GOOD: use an array of arguments instead of executing a string
        {
            String latlonCoords = args[1];
            Runtime rt = Runtime.getRuntime();
            Process exec = rt.exec(new String[] {
                    "c:\\path\to\latlon2utm.exe",
                    latlonCoords });
        }
    }
}

References

java/ql/src/experimental/Security/CWE/CWE-078/CommandInjectionRuntimeExec.qhelp

Command Injection into Runtime.exec() with dangerous command

Code that passes remote user input to an arugment of a call of Runtime.exec that executes a scripting executable will allow the user to execute malicious code.

Recommendation

If possible, use hard-coded string literals to specify the command or script to run, or library to load. Instead of passing the user input directly to the process or library function, examine the user input and then choose among hard-coded string literals.

If the applicable libraries or commands cannot be determined at compile time, then add code to verify that the user input string is safe before using it.

Example

The following example shows code that takes a shell script that can be changed maliciously by a user, and passes it straight to the array going into Runtime.exec without examining it first.

class Test {
    public static void main(String[] args) {
        String script = System.getenv("SCRIPTNAME");
        if (script != null) {
            // BAD: The script to be executed by /bin/sh is controlled by the user.
            Runtime.getRuntime().exec(new String[]{"/bin/sh", script});
        }
    }
}

References

java/ql/src/experimental/Security/CWE/CWE-078/CommandInjectionRuntimeExecLocal.qhelp

Command Injection into Runtime.exec() with dangerous command

Code that passes local user input to an arugment of a call of Runtime.exec that executes a scripting executable will allow the user to execute malicious code.

Recommendation

If possible, use hard-coded string literals to specify the command or script to run, or library to load. Instead of passing the user input directly to the process or library function, examine the user input and then choose among hard-coded string literals.

If the applicable libraries or commands cannot be determined at compile time, then add code to verify that the user input string is safe before using it.

Example

The following example shows code that takes a shell script that can be changed maliciously by a user, and passes it straight to the array going into Runtime.exec without examining it first.

class Test {
    public static void main(String[] args) {
        String script = System.getenv("SCRIPTNAME");
        if (script != null) {
            // BAD: The script to be executed by /bin/sh is controlled by the user.
            Runtime.getRuntime().exec(new String[]{"/bin/sh", script});
        }
    }
}

References

java/ql/src/experimental/Security/CWE/CWE-078/ExecTainted.qhelp

Uncontrolled command line (experimental sinks)

Code that passes user input directly to Runtime.exec, or some other library routine that executes a command, allows the user to execute malicious code.

Recommendation

If possible, use hard-coded string literals to specify the command to run or library to load. Instead of passing the user input directly to the process or library function, examine the user input and then choose among hard-coded string literals.

If the applicable libraries or commands cannot be determined at compile time, then add code to verify that the user input string is safe before using it.

Example

The following example shows code that takes a shell script that can be changed maliciously by a user, and passes it straight to Runtime.exec without examining it first.

class Test {
    public static void main(String[] args) {
        String script = System.getenv("SCRIPTNAME");
        if (script != null) {
            // BAD: The script to be executed is controlled by the user.
            Runtime.getRuntime().exec(script);
        }
    }
}

References

javascript/ql/src/Security/CWE-078/CommandInjection.qhelp

Uncontrolled command line

Code that passes untrusted user input directly to child_process.exec or similar APIs that execute shell commands allows the user to execute malicious code.

Recommendation

If possible, use APIs that don't run shell commands and that accept command arguments as an array of strings rather than a single concatenated string. This is both safer and more portable.

If given arguments as a single string, avoid simply splitting the string on whitespace. Arguments may contain quoted whitespace, causing them to split into multiple arguments. Use a library like shell-quote to parse the string into an array of arguments instead.

If this approach is not viable, then add code to verify that the user input string is safe before using it.

Example

The following example shows code that extracts a filename from an HTTP query parameter that may contain untrusted data, and then embeds it into a shell command to count its lines without examining it first:

var cp = require("child_process"),
    http = require('http'),
    url = require('url');

var server = http.createServer(function(req, res) {
    let file = url.parse(req.url, true).query.path;

    cp.execSync(`wc -l ${file}`); // BAD
});

A malicious user can take advantage of this code by executing arbitrary shell commands. For example, by providing a filename like foo.txt; rm -rf ., the user can first count the lines in foo.txt and subsequently delete all files in the current directory.

To avoid this catastrophic behavior, use an API such as child_process.execFileSync that does not spawn a shell by default:

var cp = require("child_process"),
    http = require('http'),
    url = require('url');

var server = http.createServer(function(req, res) {
    let file = url.parse(req.url, true).query.path;

    cp.execFileSync('wc', ['-l', file]); // GOOD
});

If you want to allow the user to specify other options to wc, you can use a library like shell-quote to parse the user input into an array of arguments without risking command injection:

var cp = require("child_process"),
    http = require('http'),
    url = require('url'),
    shellQuote = require('shell-quote');

var server = http.createServer(function(req, res) {
    let options = url.parse(req.url, true).query.options;

    cp.execFileSync('wc', shellQuote.parse(options)); // GOOD
});

Alternatively, the original example can be made safe by checking the filename against an allowlist of safe characters before using it:

var cp = require("child_process"),
    http = require('http'),
    url = require('url');

var server = http.createServer(function(req, res) {
    let file = url.parse(req.url, true).query.path;

    // only allow safe characters in file name
    if (file.match(/^[\w\.\-\/]+$/)) {
        cp.execSync(`wc -l ${file}`); // GOOD
    }
});

References

javascript/ql/src/Security/CWE-078/IndirectCommandInjection.qhelp

Indirect uncontrolled command line

Forwarding command-line arguments to child_process.exec or some other library routine that executes a system command within a shell can change the meaning of the command unexpectedly due to unescaped special characters.

When the forwarded command-line arguments come from a parent process that has not escaped the special characters in the arguments, then the parent process may indirectly be vulnerable to command-line injection since the special characters are evaluated unexpectedly.

Recommendation

If possible, use APIs that don't run shell commands and accept command arguments as an array of strings rather than a single concatenated string. This is both safer and more portable.

If given arguments as a single string, avoid simply splitting the string on whitespace. Arguments may contain quoted whitespace, causing them to split into multiple arguments. Use a library like shell-quote to parse the string into an array of arguments instead.

If this approach is not viable, then add code to verify that each forwarded command-line argument is properly escaped before using it.

Example

The following wrapper script example executes another JavaScript file in a child process and forwards some command-line arguments. This is problematic because the special characters in the command-line arguments may change the meaning of the child process invocation unexpectedly. For instance, if one of the command-line arguments is "dollar$separated$name", then the child process will substitute the two environment variables $separated and $name before invoking node.

var cp = require("child_process");

const args = process.argv.slice(2);
const script = path.join(__dirname, 'bin', 'main.js');
cp.execSync(`node ${script} ${args.join(' ')}`); // BAD

If another program uses child_process.execFile to invoke the above wrapper script with input from a remote user, then there may be a command-line injection vulnerability. This may be surprising, since a command-line invocation with child_process.execFile is generally considered safe. But in this case, the remote user input is simply forwarded to the problematic process.exec call in the wrapper script.

To guard against this, use an API that does not perform environment variable substitution, such as child_process.execFile:

var cp = require("child_process");

const args = process.argv.slice(2);
const script = path.join(__dirname, 'bin', 'main.js');
cp.execFileSync('node', [script].concat(args)); // GOOD

If you want to allow the user to specify other options to node, you can use a library like shell-quote to parse the user input into an array of arguments without risking command injection:

var cp = require("child_process"),
    shellQuote = require("shell-quote");

const args = process.argv.slice(2);
let nodeOpts = '';
if (args[0] === '--node-opts') {
    nodeOpts = args[1];
    args.splice(0, 2);
}
const script = path.join(__dirname, 'bin', 'main.js');
cp.execFileSync('node', shellQuote.parse(nodeOpts).concat(script).concat(args)); // GOOD

References

javascript/ql/src/Security/CWE-078/SecondOrderCommandInjection.qhelp

Second order command injection

Some shell commands, like git ls-remote, can execute arbitrary commands if a user provides a malicious URL that starts with --upload-pack. This can be used to execute arbitrary code on the server.

Recommendation

Sanitize user input before passing it to the shell command. For example, ensure that URLs are valid and do not contain malicious commands.

Example

The following example shows code that executes git ls-remote on a URL that can be controlled by a malicious user.

const express = require("express");
const app = express();

const cp = require("child_process");

app.get("/ls-remote", (req, res) => {
  const remote = req.query.remote;
  cp.execFile("git", ["ls-remote", remote]); // NOT OK
});

The problem has been fixed in the snippet below, where the URL is validated before being passed to the shell command.

const express = require("express");
const app = express();

const cp = require("child_process");

app.get("/ls-remote", (req, res) => {
  const remote = req.query.remote;
  if (!(remote.startsWith("git@") || remote.startsWith("https://"))) {
    throw new Error("Invalid remote: " + remote);
  }
  cp.execFile("git", ["ls-remote", remote]); // OK
});

References

javascript/ql/src/Security/CWE-078/ShellCommandInjectionFromEnvironment.qhelp

Shell command built from environment values

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.

Recommendation

If possible, use hard-coded string literals to specify the shell command to run, and provide the dynamic arguments to the shell command separately to avoid interpretation by the shell.

Alternatively, if the shell command must be constructed dynamically, then add code to ensure that special characters in environment values do not alter the shell command unexpectedly.

Example

The following example 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
}

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/username/important project/temp, then the shell command will recursively delete /home/username/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/username/; 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

javascript/ql/src/Security/CWE-078/UnsafeShellCommandConstruction.qhelp

Unsafe shell command constructed from library input

Dynamically constructing a shell command with inputs from exported functions may inadvertently change the meaning of the shell command. Clients using the exported function may use inputs containing 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.

Recommendation

If possible, provide the dynamic arguments to the shell as an array using a safe API such as child_process.execFile to avoid interpretation by the shell.

If given arguments as a single string, avoid simply splitting the string on whitespace. Arguments may contain quoted whitespace, causing them to split into multiple arguments. Use a library like shell-quote to parse the string into an array of arguments instead.

Alternatively, if the command must be interpreted by a shell (for example because it includes I/O redirections), you can use shell-quote to escape any special characters in the input before embedding it in the command.

Example

The following example shows a dynamically constructed shell command that downloads a file from a remote URL.

var cp = require("child_process");

module.exports = function download(path, callback) {
  cp.exec("wget " + path, callback);
}

The shell command will, however, fail to work as intended if the input contains spaces or other special characters interpreted in a special way by the shell.

Even worse, a client might pass in user-controlled data, not knowing that the input is interpreted as a shell command. This could allow a malicious user to provide the input http://example.org; cat /etc/passwd in order to execute the command cat /etc/passwd.

To avoid such potentially catastrophic behaviors, provide the inputs from exported functions as an argument that does not get interpreted by a shell:

var cp = require("child_process");

module.exports = function download(path, callback) {
  cp.execFile("wget", [path], callback);
}

As another example, consider the following code which is similar to the preceding example, but pipes the output of wget into wc -l to count the number of lines in the downloaded file.

var cp = require("child_process");

module.exports = function download(path, callback) {
  cp.exec("wget " + path + " | wc -l", callback);
};

In this case, using child_process.execFile is not an option because the shell is needed to interpret the pipe operator. Instead, you can use shell-quote to escape the input before embedding it in the command:

var cp = require("child_process");

module.exports = function download(path, callback) {
  cp.exec("wget " + shellQuote.quote([path]) + " | wc -l", callback);
};

References

javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp

Unnecessary use of cat process

Using the unix command cat only to read a file is an unnecessarily complex way to achieve something that can be done in a simpler and safer manner using the Node.js fs.readFile API.

The use of cat for simple file reads leads to code that is unportable, inefficient, complex, and can lead to subtle bugs or even security vulnerabilities.

Recommendation

Use fs.readFile or fs.readFileSync to read files from the file system.

Example

The following example shows code that reads a file using cat:

var child_process = require('child_process');

module.exports = function (name) {
    return child_process.execSync("cat " + name).toString();
};

The code in the example will break if the input name contains special characters (including space). Additionally, it does not work on Windows and if the input is user-controlled, a command injection attack can happen.

The fs.readFile API should be used to avoid these potential issues:

var fs = require('fs');

module.exports = function (name) {
    return fs.readFileSync(name).toString();
};

References

javascript/ql/src/experimental/heuristics/ql/src/Security/CWE-078/CommandInjection.qhelp

Uncontrolled command line with additional heuristic sources

Code that passes untrusted user input directly to child_process.exec or similar APIs that execute shell commands allows the user to execute malicious code.

Recommendation

If possible, use APIs that don't run shell commands and that accept command arguments as an array of strings rather than a single concatenated string. This is both safer and more portable.

If given arguments as a single string, avoid simply splitting the string on whitespace. Arguments may contain quoted whitespace, causing them to split into multiple arguments. Use a library like shell-quote to parse the string into an array of arguments instead.

If this approach is not viable, then add code to verify that the user input string is safe before using it.

Example

The following example shows code that extracts a filename from an HTTP query parameter that may contain untrusted data, and then embeds it into a shell command to count its lines without examining it first:

var cp = require("child_process"),
    http = require('http'),
    url = require('url');

var server = http.createServer(function(req, res) {
    let file = url.parse(req.url, true).query.path;

    cp.execSync(`wc -l ${file}`); // BAD
});

A malicious user can take advantage of this code by executing arbitrary shell commands. For example, by providing a filename like foo.txt; rm -rf ., the user can first count the lines in foo.txt and subsequently delete all files in the current directory.

To avoid this catastrophic behavior, use an API such as child_process.execFileSync that does not spawn a shell by default:

var cp = require("child_process"),
    http = require('http'),
    url = require('url');

var server = http.createServer(function(req, res) {
    let file = url.parse(req.url, true).query.path;

    cp.execFileSync('wc', ['-l', file]); // GOOD
});

If you want to allow the user to specify other options to wc, you can use a library like shell-quote to parse the user input into an array of arguments without risking command injection:

var cp = require("child_process"),
    http = require('http'),
    url = require('url'),
    shellQuote = require('shell-quote');

var server = http.createServer(function(req, res) {
    let options = url.parse(req.url, true).query.options;

    cp.execFileSync('wc', shellQuote.parse(options)); // GOOD
});

Alternatively, the original example can be made safe by checking the filename against an allowlist of safe characters before using it:

var cp = require("child_process"),
    http = require('http'),
    url = require('url');

var server = http.createServer(function(req, res) {
    let file = url.parse(req.url, true).query.path;

    // only allow safe characters in file name
    if (file.match(/^[\w\.\-\/]+$/)) {
        cp.execSync(`wc -l ${file}`); // GOOD
    }
});

References

python/ql/src/Security/CWE-078/CommandInjection.qhelp

Uncontrolled command line

Code that passes user input directly to exec, eval, or some other library routine that executes a command, allows the user to execute malicious code.

Recommendation

If possible, use hard-coded string literals to specify the command to run or the library to load. Instead of passing the user input directly to the process or library function, examine the user input and then choose among hard-coded string literals.

If the applicable libraries or commands cannot be determined at compile time, then add code to verify that the user input string is safe before using it.

Example

The following example shows two functions. The first is unsafe as it takes a shell script that can be changed by a user, and passes it straight to subprocess.call() without examining it first. The second is safe as it selects the command from a predefined allowlist.

urlpatterns = [
    # Route to command_execution
    url(r'^command-ex1$', command_execution_unsafe, name='command-execution-unsafe'),
    url(r'^command-ex2$', command_execution_safe, name='command-execution-safe')
]

COMMANDS = {
    "list" :"ls",
    "stat" : "stat"
}

def command_execution_unsafe(request):
    if request.method == 'POST':
        action = request.POST.get('action', '')
        #BAD -- No sanitizing of input
        subprocess.call(["application", action])

def command_execution_safe(request):
    if request.method == 'POST':
        action = request.POST.get('action', '')
        #GOOD -- Use an allowlist
        subprocess.call(["application", COMMANDS[action]])

References

python/ql/src/Security/CWE-078/UnsafeShellCommandConstruction.qhelp

Unsafe shell command constructed from library input

Dynamically constructing a shell command with inputs from library functions may inadvertently change the meaning of the shell command. Clients using the exported function may use inputs containing 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.

Recommendation

If possible, provide the dynamic arguments to the shell as an array to APIs such as subprocess.run to avoid interpretation by the shell.

Alternatively, if the shell command must be constructed dynamically, then add code to ensure that special characters do not alter the shell command unexpectedly.

Example

The following example shows a dynamically constructed shell command that downloads a file from a remote URL.

import os

def download(path): 
    os.system("wget " + path) # NOT OK

The shell command will, however, fail to work as intended if the input contains spaces or other special characters interpreted in a special way by the shell.

Even worse, a client might pass in user-controlled data, not knowing that the input is interpreted as a shell command. This could allow a malicious user to provide the input http://example.org; cat /etc/passwd in order to execute the command cat /etc/passwd.

To avoid such potentially catastrophic behaviors, provide the input from library functions as an argument that does not get interpreted by a shell:

import subprocess

def download(path): 
    subprocess.run(["wget", path]) # OK

References

ruby/ql/src/queries/security/cwe-078/CommandInjection.qhelp

Uncontrolled command line

Code that passes user input directly to Kernel.system, Kernel.exec, or some other library routine that executes a command, allows the user to execute malicious code.

Recommendation

If possible, use hard-coded string literals to specify the command to run or library to load. Instead of passing the user input directly to the process or library function, examine the user input and then choose among hard-coded string literals.

If the applicable libraries or commands cannot be determined at compile time, then add code to verify that the user input string is safe before using it.

Example

The following example shows code that takes a shell script that can be changed maliciously by a user, and passes it straight to Kernel.system without examining it first.

class UsersController < ActionController::Base
  def create
    command = params[:command]
    system(command) # BAD
  end
end

References

ruby/ql/src/queries/security/cwe-078/KernelOpen.qhelp

Use of Kernel.open, IO.read or similar sinks with user-controlled input

If Kernel.open is given a file name that starts with a | character, it will execute the remaining string as a shell command. If a malicious user can control the file name, they can execute arbitrary code. The same vulnerability applies to IO.read, IO.write, IO.binread, IO.binwrite, IO.foreach, IO.readlines and URI.open.

Recommendation

Use File.open instead of Kernel.open, as the former does not have this vulnerability. Similarly, use the methods from the File class instead of the IO class e.g. File.read instead of IO.read.

Instead of URI.open use URI(..).open or an HTTP Client.

Example

The following example shows code that calls Kernel.open on a user-supplied file path.

require "open-uri"

class UsersController < ActionController::Base
  def create
    filename = params[:filename]
    open(filename) # BAD

    web_page = params[:web_page]
    URI.open(web_page) # BAD - calls `Kernel.open` internally
  end
end

Instead, File.open should be used, as in the following example.

class UsersController < ActionController::Base
  def create
    filename = params[:filename]
    File.open(filename)

    web_page = params[:web_page]
    Net::HTTP.get(URI.parse(web_page))
  end
end

References

ruby/ql/src/queries/security/cwe-078/NonConstantKernelOpen.qhelp

Use of Kernel.open or IO.read or similar sinks with a non-constant value

If Kernel.open is given a file name that starts with a | character, it will execute the remaining string as a shell command. If a malicious user can control the file name, they can execute arbitrary code. The same vulnerability applies to IO.read, IO.write, IO.binread, IO.binwrite, IO.foreach, IO.readlines and URI.open.

Recommendation

Use File.open instead of Kernel.open, as the former does not have this vulnerability. Similarly, use the methods from the File class instead of the IO class e.g. File.read instead of IO.read.

Instead of URI.open use URI(..).open or an HTTP Client.

Example

The following example shows code that calls Kernel.open on a user-supplied file path.

require "open-uri"

class UsersController < ActionController::Base
  def create
    filename = params[:filename]
    open(filename) # BAD

    web_page = params[:web_page]
    URI.open(web_page) # BAD - calls `Kernel.open` internally
  end
end

Instead, File.open should be used, as in the following example.

class UsersController < ActionController::Base
  def create
    filename = params[:filename]
    File.open(filename)

    web_page = params[:web_page]
    Net::HTTP.get(URI.parse(web_page))
  end
end

References

ruby/ql/src/queries/security/cwe-078/UnsafeShellCommandConstruction.qhelp

Unsafe shell command constructed from library input

Dynamically constructing a shell command with inputs from exported functions may inadvertently change the meaning of the shell command. Clients using the exported function may use inputs containing 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.

Recommendation

If possible, avoid concatenating shell strings to APIs such as system(..) to avoid interpretation by the shell.

Instead, provide the arguments to the shell command as separate arguments to the API, such as system("echo", arg1, arg2).

Alternatively, if the shell command must be constructed dynamically, then add code to ensure that special characters do not alter the shell command unexpectedly.

Example

The following example shows a dynamically constructed shell command that downloads a file from a remote URL.

module Utils 
    def download(path)
        system("wget #{path}") # NOT OK
    end
end

The shell command will, however, fail to work as intended if the input contains spaces or other special characters interpreted in a special way by the shell.

Even worse, a client might pass in user-controlled data, not knowing that the input is interpreted as a shell command. This could allow a malicious user to provide the input http://example.org; cat /etc/passwd in order to execute the command cat /etc/passwd.

To avoid such potentially catastrophic behaviors, provide the input from exported functions as an argument that does not get interpreted by a shell:

module Utils 
    def download(path)
        # using an API that doesn't interpret the path as a shell command
        system("wget", path) # OK
    end
end

References

swift/ql/src/queries/Security/CWE-078/CommandInjection.qhelp

System command built from user-controlled sources

Constructing a system command with unsanitized user input is dangerous, since a malicious user may be able to craft input that executes arbitrary code.

Recommendation

If possible, use hard-coded string literals to specify the command to run. Instead of interpreting user input directly as command names, examine the input and then choose among hard-coded string literals.

If this is not possible, then add sanitization code to verify that the user input is safe before using it.

Example

The following example executes code from user input without sanitizing it first:

var task = Process()
task.launchPath = "/bin/bash"
task.arguments = ["-c", userControlledString] // BAD

task.launch()

If user input is used to construct a command it should be checked first. This ensures that the user cannot insert characters that have special meanings:

func validateCommand(_ command: String) -> String? {
    let allowedCommands = ["ls -l", "pwd", "echo"]
    if allowedCommands.contains(command) {
        return command
    }
    return nil
}

if let validatedString = validateCommand(userControlledString) {
    var task = Process()
    task.launchPath = "/bin/bash"
    task.arguments = ["-c", validatedString] // GOOD

    task.launch()
}

References

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The replacements are consistent, but the new URL’s reachability could not be independently confirmed and needs human verification.

No open issues 🎉

What changed in this PR

Updates a repeated OWASP reference in CodeQL query-help documentation across eight languages.

Changes:

  • Replaces the legacy Command Injection URL in 23 .qhelp files with https://community.owasp.org/attacks/Command_Injection.
  • Preserves link text, document structure, and query behavior.
File Description
swift/​ql/​src/​queries/​Security/​CWE-078/​CommandInjection.qhelp Updates the command-injection reference.
ruby/​ql/​src/​queries/​security/​cwe-078/​UnsafeShellCommandConstruction.qhelp Updates the unsafe-shell-command reference.
ruby/​ql/​src/​queries/​security/​cwe-078/​KernelOpen.inc.qhelp Updates the shared Kernel.open reference.
ruby/​ql/​src/​queries/​security/​cwe-078/​CommandInjection.qhelp Updates the command-injection reference.
python/​ql/​src/​Security/​CWE-078/​UnsafeShellCommandConstruction.qhelp Updates the unsafe-shell-command reference.
python/​ql/​src/​Security/​CWE-078/​CommandInjection.qhelp Updates the command-injection reference.
javascript/​ql/​src/​Security/​CWE-078/​UselessUseOfCat.qhelp Updates the shell-command security reference.
javascript/​ql/​src/​Security/​CWE-078/​UnsafeShellCommandConstruction.qhelp Updates the unsafe-shell-command reference.
javascript/​ql/​src/​Security/​CWE-078/​ShellCommandInjectionFromEnvironment.qhelp Updates the environment-based injection reference.
javascript/​ql/​src/​Security/​CWE-078/​SecondOrderCommandInjection.qhelp Updates the second-order injection reference.
javascript/​ql/​src/​Security/​CWE-078/​IndirectCommandInjection.qhelp Updates the indirect injection reference.
javascript/​ql/​src/​Security/​CWE-078/​CommandInjection.inc.qhelp Updates the shared command-injection reference.
java/​ql/​src/​Security/​CWE/​CWE-078/​ExecUnescaped.qhelp Updates the unescaped-execution reference.
java/​ql/​src/​Security/​CWE/​CWE-078/​ExecTainted.qhelp Updates the tainted-execution reference.
java/​ql/​src/​experimental/​Security/​CWE/​CWE-078/​ExecTainted.qhelp Updates the experimental execution reference.
java/​ql/​src/​experimental/​Security/​CWE/​CWE-078/​CommandInjectionRuntimeExecLocal.qhelp Updates the local Runtime.exec reference.
java/​ql/​src/​experimental/​Security/​CWE/​CWE-078/​CommandInjectionRuntimeExec.qhelp Updates the Runtime.exec injection reference.
go/​ql/​src/​Security/​CWE-089/​StringBreak.qhelp Updates the OWASP injection reference.
go/​ql/​src/​Security/​CWE-078/​StoredCommand.qhelp Updates the stored-command injection reference.
go/​ql/​src/​Security/​CWE-078/​CommandInjection.qhelp Updates the command-injection reference.
csharp/​ql/​src/​Security Features/​CWE-078/​CommandInjection.qhelp Updates the command-injection reference.
cpp/​ql/​src/​Security/​CWE/​CWE-078/​ExecTainted.qhelp Updates the tainted-execution reference.
cpp/​ql/​src/​experimental/​Security/​CWE/​CWE-078/​WordexpTainted.qhelp Updates the wordexp injection reference.

Review effort: Max

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants