
Many programs and libraries, including the shared library loader on both Unix and Windows systems, depend on environment variable settings. Because environment variables are inherited from the parent process when a program is executed, an attacker can easily sabotage variables, causing a program to behave in an unexpected and insecure manner [[Viega 03]].
Attackers can manipulate environmental variables to trick an executable into running a spoofed version of a shared library or executable. Most modern systems, for example, uses dynamic libraries and most executables are dynamically linked (that is, use dynamic libraries). If an attacker can run arbitary code with the privileges of a spoofed process by installing a spoofed version of a shared library and influencing the mechanism for dynamic linking by setting the LD_PRELOAD
environmental variable (or another LD_*
environmental variable). An interesting example of this vulnerability involving the RFC 1408/RFC 1572 Telnet Environment Option is documented in CERT Advisory CA-1995-14, "Telnetd Environment Vulnerability" [CA-1995-14]. The Telnet Environment Option extension to telnet supports the transfer of environment variables from one system to another, allowing an attacker to transfer environment variables that influence the login program called by the telnet daemon and bypass the normal login and authentication scheme to become root on that system.
Certain variables can cause insecure program behavior if they are missing from the environment or improperly set. As a result, the environment cannot be fully purged. Instead, variables that should exist should be set to safe values or treated as untrusted data and examined closely before being used.
For example, the IFS
variable (which stands for "internal field separator") is used by the sh
and bash
shells to determine which characters separate command line arguments. Because the shell is invoked by the C99 system()
function and the POSIX popen()
function, setting IFS
to unusual values can subvert apparently-safe calls.
Environment issues are particularly dangerous with setuid/setgid programs or other elevated priviledges, because an attacker can completely control the environment variables.
Non-Compliant Code Example (POSIX)
This non-compliant code invokes the C99 system()
function to execute the /bin/ls
program. The C99 system()
function passes a string to the command processer in the host environment to be executed.
system("/bin/ls");
Using the default setting of the IFS
environmental variable, this string is interpreted as a single token. If an attacker sets the IFS
environmental variable to "/" the meaning of the system call changes dramatically. In this case, the shell interprets the string as two tokens: bin
and ls
. An attacker could exploit this by creating a program called bin
in the path (which could also be manipulated by the attacker).
Compliant Solution (POSIX)
Sanitize the environment by setting required variables to safe values and removing extraneous environment variables. Set IFS
to its default of " \t\n" (the first character is a space character). Set the PATH
environment variable to _PATH_STDPATH
defined in paths.h
. Preserve the TZ
environment variable (if present) which denotes the time zone (see the Open Group Base Specifications Issue 6 specifies for the format for this variable [[Open Group 04]].
One way to clear the environment is to use the clearenv()
function. The function clearenv()
has an odd history; it was supposed to be defined in POSIX.1, but never made it into the standard. However, it is defined in POSIX.9 (the Fortran 77 bindings to POSIX), so there is a quasi-official status for it [[Wheeler 03]].
The other technique is to directly manipulate the environment through the environ
variable. According to the Open Group Base Specifications Issue 6 [[Open Group 04]]:
The value of an environment variable is a string of characters. For a C-language program, an array of strings called the environment shall be made available when a process begins. The array is pointed to by the external variable environ, which is defined as:
extern char **environ;
These strings have the form name=value; names shall not contain the character '='.
Note that C99 standard states that "The set of environment names and the method for altering the environment list are implementation-defined."
Non-Compliant Code Example (POSIX)
This non-compliant code invokes the C99 system()
function to remove the .config
file in the users home directory.
system("rm ~/.config");
Given that the vulnerable program has sufficient permissions, an attacker can manipulate the value of HOME
so that this program can remove any file named .config
anywhere on the system.
Compliant Solution (POSIX)
This compliant solution calls the getuid()
to determine who the user is, followed by the getpwuid()
to get the user's password file record (which contains the user's home directory).
uid_t uid; struct passwd *pwd; uid = getuid( ); if (!(pwd = getpwuid(uid))) { endpwent(); return 1; } endpwent();
Compliant Solution
If you explicitly know which environment variables you want to keep, the function below from [[Viega 03]] will remove everything else.
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <paths.h> extern char **environ; /* These arrays are both NULL-terminated. */ static char *spc_restricted_environ[ ] = { "IFS= \t\n", "PATH=" _PATH_STDPATH, 0 }; static char *spc_preserve_environ[ ] = { "TZ", 0 }; void spc_sanitize_environment(int preservec, char **preservev) { int i; char **new_environ, *ptr, *value, *var; size_t arr_size = 1, arr_ptr = 0, len, new_size = 0; for (i = 0; (var = spc_restricted_environ[i]) != 0; i++) { new_size += strlen(var) + 1; arr_size++; } for (i = 0; (var = spc_preserve_environ[i]) != 0; i++) { if (!(value = getenv(var))) continue; new_size += strlen(var) + strlen(value) + 2; /* include the '=' */ arr_size++; } if (preservec && preservev) { for (i = 0; i < preservec && (var = preservev[i]) != 0; i++) { if (!(value = getenv(var))) continue; new_size += strlen(var) + strlen(value) + 2; /* include the '=' */ arr_size++; } } new_size += (arr_size * sizeof(char *)); if (!(new_environ = (char **)malloc(new_size))) abort( ); new_environ[arr_size - 1] = 0; ptr = (char *)new_environ + (arr_size * sizeof(char *)); for (i = 0; (var = spc_restricted_environ[i]) != 0; i++) { new_environ[arr_ptr++] = ptr; len = strlen(var); memcpy(ptr, var, len + 1); ptr += len + 1; } for (i = 0; (var = spc_preserve_environ[i]) != 0; i++) { if (!(value = getenv(var))) continue; new_environ[arr_ptr++] = ptr; len = strlen(var); memcpy(ptr, var, len); *(ptr + len + 1) = '='; memcpy(ptr + len + 2, value, strlen(value) + 1); ptr += len + strlen(value) + 2; /* include the '=' */ } if (preservec && preservev) { for (i = 0; i < preservec && (var = preservev[i]) != 0; i++) { if (!(value = getenv(var))) continue; new_environ[arr_ptr++] = ptr; len = strlen(var); memcpy(ptr, var, len); *(ptr + len + 1) = '='; memcpy(ptr + len + 2, value, strlen(value) + 1); ptr += len + strlen(value) + 2; /* include the '=' */ } } environ = new_environ; }
Risk Assessment
Invoking an external program in an attacker-controlled environment is dangerous.
Recommendation |
Severity |
Likelihood |
Remediation Cost |
Priority |
Level |
---|---|---|---|---|---|
ENV03-A |
2 (medium) |
2 (probable) |
2 (medium) |
P8 |
L2 |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
References
[[Dowd 06]] Chapter 10, "UNIX II: Processes"
[[ISO/IEC 9899-1999]] Section 7.20.4, "Communication with the environment"
[[Open Group 04]] Chapter 8, "Environment Variables"
[[Viega 03]] Section 1.1, "Sanitizing the Environment"
[[Wheeler 03]] Section 5.2, "Environment Variables"