Setting User Privileges


 
Network Programming with Perl
By Lincoln  D.  Stein
Slots : 1
Table of Contents
Chapter  14.   Bulletproofing Servers

    Content

Setting User Privileges

It is sometimes necessary to run a network application with root (superuser) privileges. Common reasons for doing this include the following:

  • To open a socket on a privileged port , you want the application to bind a well-known port in the reserved 1 “1023 range, for example the HTTP (Web) port number 80. On UNIX systems the application must be running as root to dothis.

  • To open a log or PID file , you want to create a log or PID file in a privileged location, such as /var/run . The application must be running as root to create the file and open it for writing.

Even though a particular network application must start as root in order to open privileged ports or files, it generally isn't desirable to remain running as root. Because of their accessibility to the outside, network servers are extremely vulnerable to exploitation by untrusted individuals. Even minor bugs , if exploited in the proper way, can lead to security breaches. For example, the server can be fooled into executing system commands on the untrusted user's behalf or inadvertently passing information about the system back to the remote user.

The severity of these breaches increases dramatically if the server is running as root. Now the remote user can exploit the server to run system commands with root privileges or to read and write files that the nonprivileged user would not ordinarily have access to, such as the system password file.

In general, it is a good idea to relinquish root privileges as soon as possible, and at the very least before processing any incoming data. Once the socket or file in question is opened, the application can relinquish its privileges, becoming an ordinary user. However, the socket or filehandle opened during initialization will continue to be functional.

Changing the User and Group IDs

Perl provides four special variables that control the user and group IDs of the current process:

  • < The numeric real user ID (UID) of this process

  • $( The numeric real group ID (GID) of this process

  • $> The numeric effective user ID (EUID) of this process

  • $) The numeric effective group ID (EGID) of this process

By changing the effective user ID stored in the $> (effective user ID) variable, a program running with root privileges can temporarily change its identity to that of a different user, perform some operations under this assumed identity, and later change back to root. Changing both the real UID stored in $< and the effective UID stored in $> makes the effects permanent. Once a program has relinquished its root privileges by changing both $< and $>, it cannot regain root status. This is preferred from a security standpoint, because it prevents intruders from exploiting bugs in the program to gain root status.

Programs that run with the permission of an unprivileged user cannot, in general, change the value of either $< or $>. The exception to this is when the script file has its setuid bit set: The program runs with the EUID of the user that owns the script file and the real UID of the user that launched it. In this case, the program is allowed to swap the effective and real UIDs with this type of assignment:

 ($<,$>) = ($>,$<); 

This allows setuid programs to switch back and forth between the real UIDs and EUIDs. A setuid program may relinquish its ability to switch between real UIDs and EUIDs by doing a simple assignment of its EUID to its real UID. Then the program is no longer allowed to change its EUID:

 $< = $>; 

The previous discussion of swapping the real and effective UIDs is valid only for UNIX variants that support the setreuid() C library call. In addition, the setuid bit is only effective when Perl has been configured to recognize and honor it.

There is a similar distinction between real and effective group IDs. The root user is free to change the effective group ID to anything it pleases. Anything it does thereafter will take place with the privileges of the effective GID. An unprivileged user cannot, in general, change the effective group ID. However, setgid programs, which take on the effective group of their group ownership by virtue of the setgid permission bit being set, can swap their real and effective group IDs.

Most modern UNIX systems also support the idea of supplementary groups, which are groups to which the user has privileges, but which are not the user's primary group. On such systems, when you retrieve the value of $( or $) , you get a space-delimited string of numeric GIDs. The first GID is the user's real or effective primary group, and the remainder are the supplementary groups.

Changing group IDs in Perl can be slightly tricky. To change the process's real primary group, assign a single number (not a list) to the $( variable. To change the effective group ID, assign a single number to $) . To change the list of supplementary groups as well, assign a space-separated list of group IDs to $) . The first number will become the new effective GID, and the remainder, if any, will become the supplementary groups. You may force the list of supplementary groups to an empty list by repeating the effective GID twice, as in:

 $) = '501 501'; 

Running the Psychotherapist Server as Root

As a practical example of using root privileges in a network daemon, let's rewrite the psychotherapist server to perform several operations that require root access:

  1. Instead of creating its PID file in a world-writable directory, it will write its process ID into a file located in /var/run , which on most systems is only writable by the root user.

  2. By default, the server will now try to open a socket bound to port 1002, which is in the privileged range.

  3. After opening the socket and the PID file, the server will set its EUID and GID to those of an unprivileged user, nobody and nogroup by default.

  4. After accepting an incoming connection and forking, but before processing any incoming data, the server will permanently relinquish its root privileges by setting its real UID to the effective UID.

Our design entails the addition of a new subroutine to Daemon, and a few minor changes elsewhere. Figure 14.4 shows the new code.

Figure 14.4. Changes to Daemon to support changing user privileges

graphics/14fig04.gif

Lines 1 “12: init_server() subroutine We modify init_server() so that it now takes three optional arguments: the name of the PID file, user name , and the group names to run as. We create the PID file, initialize logging, and go into the background as before. If the caller has provided both user and group names , we call the new change_privileges() subroutine. We then return the new PID as before. We also change the PIDPATH constant to write the PID file into the privileged /var/run directory rather than world-writable /usr/tmp .

Lines 13 “20: change_privileges() subroutine This subroutine accepts user and group names (not numbers ) and attempts to change our effective privileges to match them. We begin by calling getpwnam() and getgrnam() to get the numeric UID and GID for the provided user and group names. If either of these calls fails, we die with an error message (these errors will appear in the System log, thanks to the init_log() subroutine).

We first change the real and effective group IDs by setting $ ( and $ ). The list of supplementary groups is set to empty using the idiom described earlier, preventing the server from inheriting supplementary groups from the user that launched it. We then change the effective UID of the process by assigning the specified UID to $> >.

It is important to change the group membership before changing the effective UID, because a process is allowed to change group membership only when it is running with root privileges. Also notice that we do not change the real UID here. This allows the process to regain root privileges if it needs to do so.

Line 21 “24: END {} block The END{} block is responsible for unlinking the PID file. However, the PID file was created when the server was running as root. To unlink the file, we need to regain those privileges, which we do by setting the effective UID to the value of the real UID.

If it happens that the server is not launched as root, these various privilege-changing manipulations will fail. We do not check for these failures explicitly, because the other operations that require root access, such as opening the privileged port, will abort server startup first.

To take advantage of the new code in Daemon, the main server script must be modified very slightly. Three changes are required.

  1. New USER and GROUP constants At the top of the file, we change the PORT constant to 1002 and the PIDFILE constant to a file located in /var/run . We then define two new constants, USER and GROUP , which contain names of the user and group that the server will run as. These must correspond to valid entries in your /etc/passwd and /etc/group files ”change them as necessary for your system.

     use constant PORT      => 1002; use constant PIDFILE   => '/var/run/eliza_root.pid'; use constant USER      => 'nobody'; use constant GROUP     => 'nogroup'; 
  2. Pass USER and GROUP to init_server() After opening the listening socket, we call init_server() using its new three-argument form, passing it the PID filename and the values of the USER and GROUP constants.

     my $pid = init_server(PIDFILE,USER,GROUP); 
  3. Children set real UID to effective UID before processing connections This is the most important modification. After accepting an incoming connection and forking, but before reading any data from the connected socket, the child process sets the real UID to the effective UID, thereby permanently relinquishing its ability to regain root privileges.

     while (my $connection = $listen_socket->accept) {   my $host = $connection->peerhost;   log_die("Can't fork: $!") unless defined (my $child = fork());   if ($child == 0) {     $listen_socket->close;     $< = $>; # set real UID to effective UID     log_notice("Accepting a connection from $host\n");     interact($connection);     ... 

If we try to launch the modified server as an unprivileged user, it fails with an error message when it is unable to open the reserved port. If we log in as the superuser and then launch the server, it successfully opens the port and create the PID file (which will be owned by the root user and group). If we run the ps command after launching the server, we see that the main server and its children run as nobody :

 nobody 2279 1.0 6.6 5320 4172 S 10:07 0:00 /usr/bin/perl eliza_root.pl nobody 2284 0.5 6.7 5368 4212 S 10:07 0:00 /usr/bin/perl eliza_root.pl nobody 2297 1.0 6.7 5372 4220 S 10:08 0:00 /usr/bin/perl eliza_root.pl 

The risk of the server's inadvertently damaging your system while running as root is now restricted to those files, directories, and commands that the nobody user has access to.


   
Top


Network Programming with Perl
Network Programming with Perl
ISBN: 0201615711
EAN: 2147483647
Year: 2000
Pages: 173

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net