Joey The Saint has asked for the wisdom of the Perl Monks concerning the following question:

Hey folks,

I'm in a position now where I'd like to provide access to a binary on a web-server via a cgi script. I've been handed a cgi that does the job reasonably well, but I'm not convinced it's at all secure or robust, so I'd like some confirmation/advice on it.

The first problem is it wasn't written using CGI.pm and I'm not quite up to re-writing it the right way just now unless I absolutely have to. Here's what it does:

  1. Checks for the presence of the binary in an accessible location. (It used to be looking for it in /usr/local/bin! I've changed that.)
  2. It expects to be called as the GET action from a form, so it checks to make sure it got a single parameter from the form. (Since this parameter is a standard regular expression, I can't realistically filter out too much from the input stream.)
  3. Open an anonymous pipe for input like so:
  4. unless (open(PIPE, "search \"$pattern\" |")) { # barf out some error messages } # carry on processing the output from the search command

Now is it just me, or is this opening a sub-shell and thus providing a way for almost anyone to execute almost any code on my web-server with (at least) the permissions of the server's UID/GID? Is there a way I can insist that the open() command execute a restricted shell, or better yet only execute the 'search' command without a shell at all so I don't have to worry about evil escape sequences that I can't really quotemeta since they are regexps?

Thanks,

-J.

Replies are listed 'Best First'.
Re: Question about input pipes and sub-shells
by kjherron (Pilgrim) on Nov 14, 2001 at 20:49 UTC
    You could use the "-|" form of open and exec your program directly:
    my $pid = open(PIPE, '-|'); if (!defined($pid)) { die "fork failure: $!\n"; } elsif (!$pid) { # Child process exec 'search', $pattern; die "exec failure: $!\n"; } else { # parent ... }
    This guarantees there won't be copy of the shell involved, so shell metacharacters in the search string are irrelevant.
      Along the same lines... here is the relevant section from open:
      If you open a pipe on the command '-', i.e., either '|-' or '-|', then there is an implicit fork done, and the return value of open is the pid of the child within the parent process, and 0 within the child process. (Use defined($pid) to determine whether the open was successful.) The filehandle behaves normally for the parent, but i/o to that filehandle is piped from/to the STDOUT/STDIN of the child process. In the child process the filehandle isn't opened--i/o happens from/to the new STDOUT or STDIN. Typically this is used like the normal piped open when you want to exercise more control over just how the pipe command gets executed, such as when you are running setuid, and don't want to have to scan shell commands for metacharacters. The following pairs are more or less equivalent:

      open(FOO, "|tr 'a-z' 'A-Z'");
      open(FOO, "|-") || exec 'tr', 'a-z', 'A-Z';

      open(FOO, "cat -n '$file'|");
      open(FOO, "-|") || exec 'cat', '-n', $file;

      See Safe Pipe Opens for more examples of this.

      -Blake

Re: Question about input pipes and sub-shells
by suaveant (Parson) on Nov 14, 2001 at 20:11 UTC
    Yes, you should be careful about what can be called... taint mode wouldn't be a bad idea... I am not sure, but I think using \Q \E around $pattern would pretty much prevent any quotes, semi colons pipes or the like from subverting your script but putting a \ in front of non-word chars... like so...
    unless (open(PIPE, "search \Q$pattern\E |")) {
    this also removes the need for the quotes around $pattern since spaces will be escaped.

                    - Ant
                    - Some of my best work - (1 2 3)

      Thanks for the advice. I'd planned to use taint mode too, but my experience with taint-perl so far is that the biggest gains are from ensuring that you cannot pass along values that haven't been properly checked. My problem, though, is that I'm really not able to do much validation since I want to be able to pass through almost any valid POSIX regular expression to the 'search' command.

      Just so I'm clear, won't using \Q and \E mangle the contents of $pattern in such a way that if it contained:

      (tcp|udp)RxPacket

      as an example, the 'search' command would see:

      \(tcp\|udp\)RxPacket

      Which would cause it to treat it as a regular expression with no special characters in it, ie, it would be looking for a string "(tcp|udp)RxPacket" rather than "tcpRxPacket" or "udpRxPacket".

      -J.
        No, the escapes are for the shell, and it handles them. Your script gets what you want.

        of course, you could always use fork and exec, that would end your shell issues... but make the code more difficult.

                        - Ant
                        - Some of my best work - (1 2 3)