The phf CGI script constructs a partial command line consisting of the ph command and appropriate arguments, and completes the command line based on the input from the user. The intent is to execute a ph query on behalf of the user. Annotated code from the vulnerable phf CGI script is shown below. Labels have been added for easy reference. if (!atleastonequery) printf("<B>You did not enter a query!</B>%c",LF); else { 1: strcpy(commandstr, "/usr/local/bin/ph -m "); if (strlen(serverstr)) { 2: strcat(commandstr, " -s "); /* RM 2/22/94 oops */ 3: escape_shell_cmd(serverstr); 4: strcat(commandstr, serverstr); strcat(commandstr, " "); } 5: escape_shell_cmd(typestr); 6: strcat(commandstr, typestr); if (atleastonereturn) { 7: escape_shell_cmd(returnstr); strcat(commandstr, returnstr); }
printf("The command is %s%c", commandstr, LF); for (x = 0; x < strlen(commandstr); x++) { printf("%c", (int) commandstr[x]); } printf("<PRE>%c", LF);
8: phfp = popen(commandstr,"r"); send_fd(phfp, stdout);
printf("</PRE>%c", LF); }
On line 1, the code builds a string "commandstr," consisting of the first part of a ph query. Input from the user is appended to "commandstr" on lines 2, 4, and 6. The particular text that gets appended depends on the type of query being executed.
A routine called escape_shell_cmd, called on lines 3, 5, and 7 tries to guard against shell command separators and shell history meta-characters by eliminating ampersands (&), semicolons (;), carets (^), exclamation points (!), and other characters from the input. However, the escape_shell_cmd code failed to guard against a newline character, which is also a valid command separator. If the intruder embeds a newline character in the string passed to the CGI script phf, the "popen" call on line 8 will interpret the string as two (or more) separate commands, separated by newlines.
The routine escape_shell_cmd is contained in a file named "util.c" that was distributed with web servers as part of a suite of CGI example scripts. escape_shell_cmd passes a command line to the operating system via the system call popen(3). The original escape_shell_cmd source from util.c is included below:
void escape_shell_cmd(char *cmd) { register int x,y,l;
l=strlen(cmd); for(x=0;cmd[x];x++) { if(ind("&;`'\"|*?~<>^()[]{}$\\",cmd[x]) != -1){ for(y=l+1;y>x;y--) cmd[y] = cmd[y-1]; l++; /* length has been increased */ cmd[x] = '\\'; x++; /* skip the character */ } } }
This routine attempts to limit the kinds of characters that can be passed to popen(3) in the CGI script phf. However, it fails to account for a newline character.
The popen(3) call is described by the man page as follows:
FILE *popen(const char *command, const char *type);
popen() creates a pipe between the calling program and the command to be executed. The arguments to popen() are pointers to null-terminated strings. "command" consists of a shell command line.
In essence, popen provides the calling program the output of "command." One example of a command you could pass to popen is
cat /etc/passwd
In this case, popen would return the output of the "cat /etc/passwd" file to the calling program. You can also pass more complex shell commands to popen, such as
cat /etc/passwd & rm *
The ampersand character (&) puts the preceding command in the background and executes the rest of the command in the foreground. As another example, you can execute a sequence of commands by separating them with semicolons (;). For example,
ls ; rm * ; touch filename
This runs the commands sequentially.
Because escape_shell_cmd failed to guard against the newline character, the intruder is able to cause a newline character to get passed to popen. This will appear to be two separate commands, both of which will be executed.
An annotated, updated version of the escape_shell_cmd code is shown below:
void escape_shell_cmd(char *cmd) { register int x,y,l;
l=strlen(cmd); for(x=0;cmd[x];x++) { 1: if(ind("&;`'\"|*?~<>^()[]{}$\\\x0A",cmd[x]) != -1){ for(y=l+1;y>x;y--) cmd[y] = cmd[y-1]; l++; /* length has been increased */ cmd[x] = '\\'; x++; /* skip the character */ } } }
This code, distributed with later versions of util.c, checks for the newline character on line 1. The newline character is signified by "x0A".
The first known public discussion of this vulnerability was Monday, February 5, 1996 in a posting by Jennifer Myers to BugTraq. This message is available at
http://www.securityfocus.com/frames/?content=/templates/archive.pike%3Flist%3D1%26msg%3D199602060250.UAA11818@marigold.eecs.nwu.edu
In general, we recommend against using the approach of filtering out bad characters. Instead, we recommend permitting only those characters that you are certain you can handle correctly. For more information on this recommendation, please see
http://www.cert.org/tech_tips/cgi_metacharacters.html |