CGI and Perl

The Safe Module

The Opcode module released with Perl5.003 has a bug that can allow the script to access the shell through the Glob operation. It's extremely important that you apply the security patch to the Opcode module if you're using a version older than Version 1.02. Or obtain the latest version from Tim Bunce's CPAN directory.
In general, within the Opcode module, each operation you want to mask can be specified by its canonical name, as specified in Opcode.h from the Perl source. Most of the methods from the Opcode module take a list of operators as parameters. The list's elements can consist of an individual opname, which specifies an individual operator like open, or an optag, preceded by a colon :, which refers to a group of operators. The Opcode module specifies several optags, for convenience and correctness, that can be used to allow or mask out groups of operations. The methods will also accept negated opnames or optags, prepended with a !, which will remove the specified opname or optag from the list of ops masked up to that point. The Safe module sets up a compartment by default that allows the operations corresponding to the optag :default, which consists (as of this writing) of the following optags:

:base_core The core operations required for Perl to start up, compile a script, and run. This set lets you do the very basic things with a script, like assignments, addition, subtraction, multiplication, keys, values, split, and others.
:base_mem These add the capability to repeat, concatenate, and join, as well as create anonymous data structures. They're not part of the :base_core opset because they can be used to implement a resource attack on a machine, possibly consuming all of its memory.
:base_loop These ops allow the script to use loops. They can be used to chew up all of the CPU time on a system.
:base_io These ops enable filehandle input. They are considered safe only when the filehandle exists before the compartment is created and is shared into the compartment. STDIN and STDOUT are shared into every Safe compartment by default, but that's it. Ops include read, readdir, print, seek, and others.
:base_orig These ops are still under consideration but are included in the safe compartment at this time. Ops like sprintf, crypt, select, getppid, gmtime, and others are among the hodge-podge included under this optag. This group is especially subject to change.

Note that the Opcode module instructs you not to rely on the definition of the :default tag, or any other tag, for that matter. Always check any new release to be sure that the operations you expect to be included or excluded truly are or aren't.
All other operations are disallowed within the default Safe compartment. The only variables that are shared into the default Safe compartment include $_, @_, and %_, along with the filehandles STDIN and STDOUT. They are shared into the default compartment upon invocation of the new() method for Safe, along with all the symbols that exist in the main:: namespace at the time the compartment was created. If you wish to enhance the capabilities of a given Safe compartment, you can do so with the methods provided within the Safe module. The other optags defined in the Opcode module include the following:

:base_math Math ops that can generate a floating point exception, like atan2, sin, cos, exp, log, and sqrt. If you allow this group, you should also set up a $SIG{FPE} handler.
:filesys_read This group includes the stat, lstat, and readlink operators. In general, access to information about the filesystem, even just the information from stat(), can provide insight on potential holes in security and should be allowed with caution.
:sys_db The large set of get* calls to do lookups into the system database files, like passwd or hosts. gethostbynam(), getpwuid(), and so on, belong to this group. Access to the system databases is generally a no-no but may be required to do anything truly useful. Use with caution.
:browse A handy tag name for a reasonable set of ops beyond the :default optag. Includes the :default, :sys_db, and :filesys_read tags.


The rest of this list of optags can be considered to be extremely dangerous to allow within a truly Safe compartment. Don't allow these ops in your scripts to be executed within the Safe compartment without understanding exactly what you're doing and whom you're doing it to. Caveat scriptor.

:filesys_open These ops include the open(), close(), sysopen(), opendir(), and other operations, which allow direct access to files in the system with a Perl program.
:filesys_write Includes link, unlink, symlink, truncate, mkdir, rmdir, chmod, and chown operations, among others.
:subprocess Backticks, system, fork, wait, and glob are included within this group. Possibly the most dangerous opcodes from a security standpoint.
:ownprocess exec, kill, exit.
:others msgctl and other SysV shared memory routines.
:still_to_be_decided chdir, flock, ioctl, socket, getpeername, bind, and the rest of the Berkeley networking calls, as well as sleep, alarm, sort, pack, and caller. This optag is quite unstable and should not be relied upon. There are ops here that may be retagged or removed in the future for various reasons.

Any of these optags, or any of the other opnames that aren't defined in the default Safe compartment, can be permitted via the permit() method. After you've set up the compartment the way you wish, user code can be executed within it via the reval() method, or a script can be compiled and executed via the rdo() method. You should become fully acquainted with the Safe module, in its intents and workings, before using it. This also goes for the Opcode module. Their documentation is embedded into the modules themselves as POD. SafeCGIPerl Now that you understand how the Safe module works, you might think that you could create a program that would set up a Safe compartment and then execute arbitrary Perl code for a given user. After all, if only the :default opset is allowed, what can go wrong? Right? Well, theoretically, yes, but in practice, the :default optag is just too restrictive to do anything useful within a given CGI script. So just how do you set up a scheme whereby the arbitrary script can be executed as a CGI? One way is to use SafeCGIPerl. SafeCGIPerl is a package that utilizes the Safe module specifically to provide a means to run CGI scripts under a given user's UID, and provide a restricted environment while giving the user's code some additional leeway to perform useful tasks like sending mail. In order to use SafeCGIPerl, you must first obtain the package from a CPAN site (available in Malcolm Beatties' CPAN directory), and then, assuming you're running Perl5.003 or later, apply the patch to use later versions of the Safe module. Next, build the safeperl executable and install it as a root suid program into your cgi-bin directory and install the cgiperl script into /usr/local/bin. When you're finished, your users will be able to create a $HOME/cgi/bin directory in their home directory and place scripts there that will be executed by safeperl when the URL of the form


is received by httpd. The SafeCGIPerl scheme is quite comprehensive but also provides methods for performing rudimentary tasks that aren't available within the standard Safe compartment. SafeCGIPerl consists primarily of two components:

safeperl The suid-root executable that performs most of the ground work and then executes cgiperl.
cgiperl A Perl script that sets up a Safe compartment, along with some additional functionality, and then executes another (arbitrary) script running under a specified user id using the rdo() method from the Safe module.

Let's take a look at how SafeCGIPerl works.

  1. First, safeperl parses the URL to be sure that the username is valid. It does this by first escaping any unwanted characters from the string, then parsing out the username and calling getpwnam() with the string it gets. If the username isn't valid, it exits with an error. If the username is valid, it logs the request and changes to the UID specified and to that user's ~/cgi/bin directory.
  2. Once the UID and directory have been set, the safeperl executable sets up an environment using the UNIX limit() system call, which explicitly restricts some crucial system-level parameters for the process (Perl) which it's about to exec. The parameters that are restricted are CPU time, niceness, coresize, data-segment size, and resident-memory size. Doing this implicitly avoids many of the risks that can be introduced by allowing Perl ops like looping or memory-growing by specifying in advance the maximum allowable amount for these parameters.
  3. Finally, the safeperl binary execs cgiperl with the desired scriptname expected now to be in the current working directory. The script is then executed by cgiperl within a Safe compartment. This implies, of course, that the script can't do just any old thing, but it does give the user some additional capabilities, including a subroutine for sending mail and capability to read and write files within a particular directory, along with the standard operators that are part of the Safe :default optag.

In general, SafeCGIPerl is a vastly preferable alternative to allowing users to run arbitrary scripts within their own Web space via the .cgi extension. It is not, however, a foolproof solution. As always, read the documentation carefully and provide the same documentation, along with any other warnings appropriate, to your users before telling them to utilize this tool. CGIWrap While not specifically a Perl package, CGIWrap is another tool you can implement on a site-wide basis to allow your users the freedom to create and execute their own CGI programs. It does not, however, provide the opcode masking that SafeCGIPerl does, nor any kind of Safe compartment. All it does, basically, is ensure that a given user's CGI programs are run under his/her own UID. The danger to the user is still quite prominent, unless he/she is very savvy about what to do, and what not to do, with CGI programs in general. Further, if an unsavvy user allows a Trojan horse to be placed in his/her path, it can endanger the security of the entire system.