...
Environment issues are particularly dangerous with setuid/setgid programs or other elevated privileges, because an attacker can completely control the environment variables.
Non-Compliant Code Example (POSIX) (ls)
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 processor in the host environment to be executed.
...
When the default shell does not ignore the incoming value of the IFS environment value, although IFS does not affect the command portion of this string, /bin/ls, it does determine how the argument is built after calling date. If an attacker sets IFS to "." the intended directory will not be found.
Compliant Solution (POSIX) (ls)
| Wiki Markup |
|---|
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 the string returned by calling {{confstr()}} with the first argument {{\_CS_PATH}}. 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|AA. C References#Open Group 04]\]. |
...
Note that C99 standard states that "The set of environment names and the method for altering the environment list are implementation-defined."
...
Compliant
...
Solution (
...
Sanitization)
This non-compliant code invokes the C99 system() function to remove the .config file in the users home directory.
| Code Block | ||
|---|---|---|
| ||
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).
| Code Block | ||
|---|---|---|
| ||
#include <sys/types.h>
#include <pwd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/* ... */
uid_t uid;
struct passwd *pwd;
uid = getuid();
if ((pwd = getpwuid(uid)) == NULL) {
/* handle error */
endpwent();
return 1;
}
/* build system cmd using home dir from pw entry */
const char* cmd_format = "rm %s/.config";
const size_t len = strlen(pwd) + strlen(cmd_format);
char* cmd = (char*) malloc(len+1);
snprintf( cmd, len, cmd_format, pwd->pw_dir);
system(cmd);
endpwent();
free(cmd);
|
Compliant Solution
| Wiki Markup |
|---|
If you explicitly know which environment variables you want to keep, the function below adapted from \[[Viega 03|AA. C References#Viega 03]\] will remove everything else. |
| Wiki Markup |
|---|
If you explicitly know which environment variables you want to keep, the function below adapted from \[[Viega 03|AA. C References#Viega 03]\] will remove everything else. |
| Code Block | ||
|---|---|---|
| ||
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
extern char **environ;
/* These arrays are both null-terminated. */
static char *spc_restricted_environ[ ] = {
"IFS= \t\n",
0, /* leave room for PATH=... */
0
};
static char *spc_preserve_environ[ ] = {
"TZ",
0
};
void spc_sanitize_environment(int preservec, const char **preservev) {
int i;
char **new_environ, *path, *ptr;
const char *value, *var;
size_t path_size, arr_size = 1, arr_ptr = 0, len, new_size = 0;
if ((path_size = confstr(_CS_PATH, NULL, 0)) > 0) {
path = (char *)malloc(path_size + sizeof("PATH=") - 1);
if (path == NULL) {
/* handle error */
}
strcpy(path, "PATH=");
if (confstr(_CS_PATH, path + sizeof("PATH=") - 1, path_size) > 0) {
spc_restricted_environ[1] = path;
}
else {
free(path);
}
}
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)) == NULL) 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)) == NULL) 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)) == NULL) 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; | ||
| Code Block | ||
| ||
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> extern char **environ; /* These arrays are both null-terminated. */ static char *spc_restricted_environ[ ] = { "IFS= \t\n", 0, /* leave room for PATH=... */ 0 }; static char *spc_preserve_environ[ ] = { "TZ", 0 }; void spc_sanitize_environment(int preservec, const char **preservev) { int i; char **new_environ, *path, *ptr; const char *value, *var; size_t path_size, arr_size = 1, arr_ptr = 0, len, new_size = 0; if ((path_size = confstr(_CS_PATH, NULL, 0)) > 0) { path = (char *)malloc(path_size + sizeof("PATH=") - 1); if (path == NULL) { /* handle error */ } strcpy(path, "PATH="); if (confstr(_CS_PATH, path + sizeof("PATH=") - 1, path_size) > 0) { spc_restricted_environ[1] = path; } else { free(path); } } for (i = 0; (var = spc_restrictedpreserve_environ[i]) != 0; i++) { new_size +if ((value = strlengetenv(var)) == +NULL) 1continue; new_environ[arr_sizeptr++] = ptr; len = }strlen(var); for (i = 0; (var = spc_preserve_environ[i]) != 0; i++) { if ((value = getenv(var)) == NULL) continuememcpy(ptr, var, len); *(ptr + len + 1) = '='; memcpy(ptr + len + 2, value, strlen(value) + 1); new_sizeptr += strlen(var)len + strlen(value) + 2; /* include the '=' */ arr_size++; } if (preservec && preservev) { for (i = 0; i < preservec && (var = preservev[i]) != 0; i++) { if ((value = getenv(var)) == NULL) continue; new_size +environ[arr_ptr++] = ptr; len = strlen(var); + strlen(value)memcpy(ptr, var, len); *(ptr + 2;len /*+ include1) the= '=' */; arr_size++; } } new_size += (arr_size * sizeof(char *)); if ((new_environ = (char **)malloc(new_size)) == NULL) 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)) == NULL) 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)) == NULL) 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 '=' */ } } /* new_environ can now be used as the envp argument to execle or execve */ } |
...
memcpy(ptr + len + 2, value, strlen(value) + 1);
ptr += len + strlen(value) + 2; /* include the '=' */
}
}
/* new_environ can now be used as the envp argument to execle or execve */
}
|
| Wiki Markup |
|---|
POSIX.1-2008 \[[Austin Group 08|AA. C References#Austin Group 08]\] defines a new {{\_CS_V7_ENV}} argument to {{confstr()}} to retrieve a list of environment variable settings required for a default conforming environment. A space-separated list of variable=value pairs is returned, with variable names guaranteed not to contain = signs, and variable=value pairs guaranteed not to contain spaces. Used together with the {{\_CS_PATH}} request illustrated above, this completely describes the minimum environment variable settings required to obtain a clean conforming environment. On systems conforming to the POSIX.1-2008 standard, this should be used to create a sanitized environment. |
Non-Compliant Code Example (POSIX) rm
This non-compliant code invokes the C99 system() function to remove the .config file in the users home directory.
| Code Block | ||
|---|---|---|
| ||
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) rm
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).
| Code Block | ||
|---|---|---|
| ||
#include <sys/types.h>
#include <pwd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/* ... */
uid_t uid;
struct passwd *pwd;
/* Sanitize environment, from previous compliant example */
spc_sanitize_environment(0, NULL);
/* Get /etc/passwd entry for current user */
uid = getuid();
if ((pwd = getpwuid(uid)) == NULL) {
/* handle error */
endpwent();
return 1;
}
/* Ensure home dir has no ', so we can quote it */
if (strchr( pwd->pw_dir, '\'') != NULL) {
/* handle ' in home dir */
}
/* build system cmd using home dir from pw entry */
const char* cmd_format = "rm '%s'/foo";
const size_t len = strlen(pwd->pw_dir) + strlen(cmd_format);
char* cmd = (char*) malloc(len+1);
snprintf( cmd, len, cmd_format, pwd->pw_dir);
if (system(cmd) != 0) {
/* handle error in rm command */
}
endpwent();
free(cmd);
|
Compliant Solution (POSIX) unlink
An easier way to remove a file is to use unlink(2). This compliant solution uses unlink(2) instead of system(3), so it is simpler.
| Code Block | ||
|---|---|---|
| ||
#include <pwd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/* ... */
uid_t uid;
struct passwd *pwd;
/* Get /etc/passwd entry for current user */
uid = getuid();
if ((pwd = getpwuid(uid)) == NULL) {
/* handle error */
endpwent();
return 1;
}
/* build full pathname home dir from pw entry */
const char* file_format = "%s/foo";
const size_t len = strlen(pwd->pw_dir) + strlen(file_format);
char* file = (char*) malloc(len+1);
snprintf( file, len, file_format, pwd->pw_dir);
if (unlink(file) != 0) {
/* handle error in unlink */
}
endpwent();
free(cmd);
|
Risk Assessment
Invoking an external program in an attacker-controlled environment is dangerous.
...