Ovid has asked for the wisdom of the Perl Monks concerning the following question:

Since I primarily work on Win32 systems, I cannot fork. I can simulate a fork, but since I have had little call to use it, I am not aware of its limitations. However, using fork is considered a way of ensuring greater security in your code. Since I have been doing a lot of research into CGI security, I'd like to understand this issue better.

Here's a (modified for brevity) snippet of code from page 200 of the excellent book CGI Programming with Perl, Second Edition (note that the previous link is NOT amazon.com):

my $string = $q->param( 'string' ); unless ( $string ) { error( $q, "Please enter some text." ) }; unless ( $string =~ /^[\w .!?-]+$/ ) { error( $q, "Invalid character entered." ); } local *PIPE; # This code is more secure, but still dangerous... # Do NOT use this code on a live web server!! open PIPE, "/usr/local/bin/figlet '$string' |" or die "Cannot open figlet: $!"; print $q->header( "text/plain" ); print while <PIPE>; close PIPE;
Personally, I don't see the problem with this code, aside from the lack of taint checking. In fact, the text mentions that the code is secure, but that someone may come along and modify it in a way which opens a security hole. The book recommends replacing the command that opens the pipe with the following code:
my $pid = open PIPE, "-|"; die "Cannot fork $!" unless defined $pid; unless ( $pid ) { exec FIGLET, $string or die "Cannot open pipe to figlet: $!"; }
Since I have not worked with fork in the past, I am not sure exactly what happens here. Here's what I'm trying to understand:

Cheers,
Ovid

Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

Replies are listed 'Best First'.
Re (tilly) 1: CGI Security and Forking
by tilly (Archbishop) on Dec 10, 2000 at 05:10 UTC
    Well the problem with the original code is obvious. The open invokes a subshell, which means that a shell reads the code. What happens if the subshell includes something like this:
    ' `rm -rf /`
    Then the subshell would cheerfully try to produce two arguments for the child, with the second subshell being the output of...oops. :-)

    As for the second example that they give. In open they say that opening "-|" or "|-" does an implicit fork with the parent process getting the pid of the child, and the child getting 0. So if you try to do that and get an undefined pid, well something went wrong. And if you did that and got a true pid, then you are the parent. But if you did that and got 0 then you are the child and need to do something else. Like call exec to become someone else without using the shell.

    Of course all of this is rather verbose and complicated. It is far simpler to accomplish the task using IPC::Open2 or IPC::Open3. Here is (tested code) with IPC::Open3:

    use IPC::Open3; # Time passes. my $pid = open3("<&STDIN", \*PIPE, ">&STDERR", $figlet, $str) or die "Cannot run '$figlet' with argument '$str': $!"; print <PIPE>; # Whatever you want here
    I have seen IPC::Open3 work on Windows with 5.005, so this snippet is probably pretty portable.

    Oh, you may want to glance at wait and waitpid if you are concerned about the possibility of creating zombies. That happens when the child dies and is left in a state where the process exists and cannot finish expiring until it tells you how it died. But you, the thoughtless parent, are refusing to listen and so keep it on in a strange half-life... :-)

      You pointed out the danger of what occurs when someone enters something like ' `rm -rf /`. In this case, it's not possible because the character class specifically did not allow for backticks or forward slashes (amongst other things). That was part of the reason why I was trying to figure out why it was dangerous. Now I think I see the point: with the fork, it's not possible to pass the arguments directly to the shell, regardless of further modifications. The first version was only secure so long as someone did not allow the offending characters.

      Thanks for the tip on IPC::Open3. I'll check it out.

      Cheers,
      Ovid

      Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

Re: CGI Security and Forking
by AgentM (Curate) on Dec 10, 2000 at 04:46 UTC
    This is a classic case of fork() confusion- it is called once but "returns twice". Really, it returns once in your parent and once in the child. When you fork, you create a new process. execing over this program space creates a program that runs in the space where the previous fork was. This is what system does, fork and exec (with some signal stuff and security stuff which is why you shouldn't try to substitute for it). Once you fork, you can change the pipe information to pipe anywhere you want. The exec does not modify this so if you pipe STDIN of the kid to the STDOUT of the parent, you have a model system where you can pipe info to the kid even though it was designed to run from the shell. Nifty, eh? What happens here is that the unless makes sure that you are in the kid (fork returns 0), and execs over the space where the PIPE was opened to return back to the parent. The pipe is maintained over the exec and thus, information is able to be returned. In order, not be confused, keep in mind, that you can have an arbitrary number of processes in any ONE Perl script.

    I see this as more secure because you are guaranteed not to be able to open any other processes. Now that you have figlet running, nothing that you send to figlet will cause it to open any other program (I know my figlet! :-D). In the first case, you are running through the shell. In the second case, the shell is not involved. If you pass bad values to figlet, it will not be interpolated by the shell. If there are illegal instructions passed, figlet will simply die. That's it!

    AgentM Systems nor Nasca Enterprises nor Bone::Easy nor Macperl is responsible for the comments made by AgentM. Remember, you can build any logical system with NOR.