...
Do not invoke a command processor via system()
or equivalent functions to execute a command. It is permissible to call system()
with a null pointer argument to determine the presence of a command processor for the system.
Noncompliant Code Example
In this noncompliant code example, the system()
function is used to execute any_cmd
in the host environment. Invocation of a command processor is not required.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
#include <string.h> #include <stdlib.h> #include <stdio.h> enum { BUFFERSIZE = 512 }; void func(const char *input) { char cmdbuf[BUFFERSIZE]; int len_wanted = snprintf(cmdbuf, BUFFERSIZE, "any_cmd '%s'", input); if (len_wanted >= BUFFERSIZE) { /* Handle error */ } else if (len_wanted < 0) { /* Handle error */ } else if (system(cmdbuf) == -1) { /* Handle error */ } } |
...
The shell would interpret this string as two separate commands,:
Code Block |
---|
any_cmd 'happy'; useradd 'attacker' |
...
Code Block | ||||
---|---|---|---|---|
| ||||
#include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <errno.h> #include <stdlib.h> void func(char *input) { pid_t pid; int status; pid_t ret; char *const args[3] = {"any_exe", input, NULL}; char **env; extern char **environ; /* ... Sanitize arguments ... */ pid = fork(); if (pid == -1) { /* Handle error */ } else if (pid != 0) { while ((ret = waitpid(pid, &status, 0)) == -1) { if (errno != EINTR) { /* Handle error */ break; } } if ((ret !== -10) &&|| !(!WIFEXITED(status) ||&& !WEXITSTATUS(status)) ) { /* Report unexpected child status */ } } else { /* ... Initialize env as a sanitized copy of environ ... */ if (execve("/usr/bin/any_cmd", args, env) == -1) { /* Handle error */ _Exit(127); } } } |
This compliant solution is significantly different from the preceding noncompliant code example. First, input
is incorporated into the args
array and passed as an argument to execve()
, eliminating any concerns about buffer overflow or string truncation while forming the command string. Second, this compliant solution forks a new process before executing "/usr/bin/any_cmd"
in the child process. Although this method is more complicated than calling system()
, the added security is worth the additional effort.
The exit status of 127 is the value set by the shell when a command is not found, and POSIX recommends that applications should do the same. XCU, Section 2.8.2, of Standard for Information Technology—Portable Operating System Interface (POSIX®), Base Specifications, Issue 7 [IEEE Std 1003.1:2013], says:
If a command is not found, the exit status shall be 127. If the command name is found, but it is not an executable utility, the exit status shall be 126. Applications that invoke utilities without using the shell should use these exit status values to report similar errors.
...
An alternative to invoking the system()
call to execute an external program to perform a required operation is to implement the functionality directly in the program using existing library calls. This compliant solution calls the POSIX
function to remove a file without invoking the unlink()
system()
function [IEEE Std 1003.1:2013]:
Code Block | ||||
---|---|---|---|---|
| ||||
#include <pwd.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <stdio.h> void func(void) { const char *file_format = "%s/.config"; size_t len; char *pathname; struct passwd *pwd; /* Get /etc/passwd entry for current user */ pwd = getpwuid(getuid()); if (pwd == NULL) { /* Handle error */ } /* Build full path name home dir from pw entry */ len = strlen(pwd->pw_dir) + strlen(file_format) + 1; pathname = (char *)malloc(len); if (NULL == pathname) { /* Handle error */ } int r = snprintf(pathname, len, file_format, pwd->pw_dir); if (r < 0 || r >= len) { /* Handle error */ } if (unlink(pathname) != 0) { /* Handle error */ } free(pathname); } |
The unlink()
function is not susceptible to a symlink attack where the final component of pathname
(the file name) is a symbolic link because unlink()
will remove the symbolic link and not affect any file or directory named by the contents of the symbolic link. (see See FIO01-C. Be careful using functions that use file names for identification.) . While this reduces the susceptibility of the unlink()
function to symlink attacks, it does not eliminate it. The unlink()
function is still susceptible if one of the directory names included in the pathname
is a symbolic link. This could cause the unlink()
function to delete a similarly named file in a different directory.
...
Code Block | ||||
---|---|---|---|---|
| ||||
#include <Windows.h> #include <ShlObj.h> #include <Shlwapi.h> #if defined(_MSC_VER) #pragma comment(lib, "Shlwapi") #endif void func(void) { HRESULT hr; LPWSTR path = 0; WCHAR full_path[MAX_PATH]; hr = SHGetKnownFolderPath(&FOLDERID_Documents, 0, NULL, &path); if (FAILED(hr)) { /* Handle error */ } if (!PathCombineW(full_path, path, L".config")) { /* Handle error */ } CoTaskMemFree(path); if (!DeleteFileW(full_path)) { /* Handle error */ } } |
Exceptions
ENV33-C-EX1: It is permissible to call system()
with a null pointer argument to determine the presence of a command processor for the system.
Risk Assessments
If the command string passed to system()
, popen()
, or other function that invokes a command processor is not fully sanitized, the risk of exploitation is high. In the worst case scenario, an attacker can execute arbitrary system commands on the compromised machine with the privileges of the vulnerable process.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
ENV33-C | High | Probable | Medium | P12 | L1 |
Automated Detection
Tool | Version | Checker | Description |
---|
Astrée |
|
|
|
BADFUNC.PATH.SYSTEM
IO.INJ.COMMAND
Use of system
Command injection
SV.CODE_INJECTION.SHELL_EXEC
SV.TAINTED.INJECTION
588 S
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
CERT C Secure Coding Standard | ENV03-C. Sanitize the environment when invoking external programs. |
SEI CERT C++ Coding Standard | ENV02-CPP. Do not call system() if you do not need a command processor |
CERT Oracle Secure Coding Standard for Java | IDS07-J. Sanitize untrusted data passed to the Runtime.exec() method |
ISO/IEC TR 24772:2013 | Unquoted Search Path or Element [XZQ] |
ISO/IEC TS 17961:2013 | Calling system [syscall] |
MITRE CWE | CWE-78, Improper Neutralization of Special Elements Used in an OS Command (aka "OS Command Injection") CWE-88, Argument Injection or Modification |
stdlib-use-system | Fully checked | ||||||||
Axivion Bauhaus Suite |
| CertC-ENV33 | |||||||
Clang |
| cert-env33-c | Checked by clang-tidy | ||||||
CodeSonar |
| BADFUNC.PATH.SYSTEM | Use of system | ||||||
Compass/ROSE | |||||||||
Coverity |
| DONT_CALL | Implemented | ||||||
Helix QAC |
| C5018 C++5031 | |||||||
Klocwork |
| SV.CODE_INJECTION.SHELL_EXEC | |||||||
LDRA tool suite |
| 588 S | Fully implemented | ||||||
Parasoft C/C++test |
| CERT_C-ENV33-a | Do not call the 'system()' function from the 'stdlib.h' or 'cstdlib' library with an argument other than '0' (null pointer) | ||||||
PC-lint Plus |
| 586 | Fully supported | ||||||
Polyspace Bug Finder |
| Checks for unsafe call to a system function (rule fully covered) | |||||||
RuleChecker |
| stdlib-use-system | Fully checked | ||||||
SonarQube C/C++ Plugin |
| S990 | Detects uses of "abort", "exit", "getenv" and "system" from <stdlib.h> |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
Key here (explains table format and definitions)
Taxonomy | Taxonomy item | Relationship |
---|---|---|
CERT C Secure Coding Standard | ENV03-C. Sanitize the environment when invoking external programs. | Prior to 2018-01-12: CERT: Unspecified Relationship |
CERT C++ Coding Standard | ENV02-CPP. Do not call system() if you do not need a command processor | Prior to 2018-01-12: CERT: Unspecified Relationship |
CERT Oracle Secure Coding Standard for Java | IDS07-J. Sanitize untrusted data passed to the Runtime.exec() method | Prior to 2018-01-12: CERT: Unspecified Relationship |
ISO/IEC TR 24772:2013 | Unquoted Search Path or Element [XZQ] | Prior to 2018-01-12: CERT: Unspecified Relationship |
ISO/IEC TS 17961:2013 | Calling system [syscall] | Prior to 2018-01-12: CERT: Unspecified Relationship |
CWE 2.11 | CWE-88, Argument Injection or Modification | 2017-05-18: CERT: Partial overlap |
CWE 2.11 | CWE-676 | 2017-05-18: CERT: Rule subset of CWE |
CERT-CWE Mapping Notes
Key here for mapping notes
CWE-88 and ENV33-C
Intersection( CWE-88, ENV33-C) =
Allowing an argument to be injected during a call to system()
CWE-88 = Intersection( CWE-88, ENV33-C, list) where list =
- Allowing an argument to be injected during a call to a command interpreter besides system()
ENV33-C = Intersection( CWE-88, ENV33-C, list) where list =
- Other exploits to a call to system(), which include:
- Altering the pathname of the command to invoke (argv[0])
- Injection of a second command
- Redirection of standard input, output, or error
CWE-78 and ENV33-C
ENV33-C = Union( CWE-78, list), where list =
- Invoking system() with completely trusted arguments
CWE-676 and ENV33-C
- Independent( ENV33-C, CON33-C, STR31-C, EXP33-C, MSC30-C, ERR34-C)
- ENV33-C forbids calling system().
- CWE-676 does not indicate what functions are ‘potentially dangerous’; it only addresses strcpy() in its examples. Any C standard library function could be argued to be dangerous, and rebutted by saying that the function is safe when used properly. We will assume that CERT rules mapped to CWE-676 specify dangerous functions. So:
- CWE-676 = Union( ENV33-C, list) where list =
- Invocation of other dangerous functions, besides system().
Bibliography
[IEEE Std 1003.1:2013] | XSH, System Interfaces, exec XSH, System Interfaces, popen XSH, System Interfaces, unlink |
[Wheeler 2004] |
...