Cover V09, I05
Article
Figure 1
Figure 2
Sidebar 1
Sidebar 2

may2000.tar


Setting Root SUIDed Programs at Work

Didier Racheneur

The SUID feature lets you alter a program's execution environment. This article describes SUID and provides some example SUID programs -- where there is power, there is danger. The article also include some security tips for using SUID programs without putting the network at risk.

What is a uid? What is the uid bit? The nine permission bits (located in the inode) are shown below:

owner:                                     other (world):
execute -------------------                -----------read
write ------------------  |                |  --------write
read ---------------   |  |                |  |  -----execute
                    |  |  |                |  |  |
                    0  1  0    1  0  0     1  0  1
group:                         |  |  |

read ---------------------------  |  |
write -----------------------------  |
execute ------------------------------
In octal, this is expressed as: 245 or -w-r--r-x. There are three more bits (from left):

  • The set-user-ID bit (on execution): SUID
  • The set-group-ID bit (on execution): SGID
  • The sticky bit

to give:

                1   0   0   0 1 0  1 0 0  1 0 1
                |   |   |
SUID ------------   |   |
SGID ----------------   |
sticky bit --------------
To complete the two bytes, we still have at left three bits to show the file type (from <sys/inode.h>):

IFIFO           0010000        /* fifo */
IFCHR           0020000        /* character special */
IFDIR           0040000        /* directory */
...
The sticky bit holds the text image on the swap disk (the program loads into memory faster the next time it is executed, because the swap area is handled as a contiguous file). With virtual memory, the utility of the sticky bit has disappeared. With a directory, the sticky bit (t) restricts the removal and renaming of files in the directory (interesting on /tmp for example). To rename or drop a file, you must either be the owner of the file or of the directory or be root (the superuser).

Every process has the four following IDs associated with it:

  • Real user ID
  • Real group ID } Who we really are
  • Effective user ID
  • Effective group ID and } Used for file access permission checks supplementary group IDs

See the “Supplementary Groups” sidebar for more information. The real user ID and real group ID come from /etc/passwd as we log in. The effective user ID, effective group ID, and supplementary group ID determine file access permissions. The effective user and group IDs are, by default, equal to the real IDs.

In other words, by default, effective uid = real uid and effective gid = real gid. But it is also possible to say: “when this file is executed, set the effective user ID of the process to be the owner of the file”. For example, let's compile the simple program:

myprog.c:

main()
{
system("id");
}
You can change the owner of the file to John, but unless you set the suid bit, the file will still execute under the real and effective uid of the current user “henry”:

$ id
uid=327(henry) gid=22(tennis)

$ cc -s -o myprog myprog.c        # -s to stripe (smaller output)
$ chmod 111 myprog
$ chown john:sys myprog
$ ll myprog                       # ll = ls -l

---x--x--x   1 john         sys         12339 Nov 25 09:11 myprog
Launched by user “henry” (who belongs to the group “tennis”); it has a real and effective uid “henry” and a real and effective gid “tennis”.

$ ./myprog
uid=327(henry) gid=22(tennis)
When the real uid is the same as the effective one, it is not displayed. The same is true for the real and effective gid. If we set the SUID bit (as root or john -- only the owner or root can change the permissions):

# chmod u+s myprog                      # or chmod 4111 myprog

---s--x--x   1 john         sys        12339 Nov 25 09:11 myprog
The same user (henry) launching the program will be: real user=henry, effective user=john (= the owner of the file) for the duration of the program.

$ id
uid=327(henry) gid=22(tennis)

$ ./myprog
uid=327(henry) gid=22(tennis) euid=330(john)
If we execute a SUID file, our effective uid is automatically set to the uid of the file's owner: if the file is owned by root, we are root (effective uid=0) for the duration of the program.

Setting the Real UID to Root

The following facts lead to some interesting possibilities. If the SUID permission is set, the effective uid becomes the file's owner (likewise, the effective gid becomes the file's group if the SGID bit is set). Also, the C function setuid(newid) allows us to change the real and effective uid to newid. But to successfully use setuid(0), we must first have the effective uid of root (obtained through the permissions+ownership of the program file as explained above).

The result of these facts is that, if the effective uid=0 (=> SUID bit set + owner=root), then setuid(0) lets you change not just the effective uid but also the real uid to root. Otherwise, only the effective uid is changed. It is important to keep the following rules in mind when using the setuid(newid) function (see man pages setuid(2) and Figure 1):

  • If the process has the effective uid=0, the function setuid(newid) sets the real uid, the effective uid and the saved uid to newid.
  • If the effective uid != 0, but newid is equal to the real uid or to the saved uid, setuid(newid) only sets the effective uid to newid. The real and saved uid remain unchanged.
  • In all other cases, errno is set to EPERM (=1) and an error is returned (-1).
  • For the group ID (gid), the rules are similar.

To change /etc/passwd, it is enough to have the effective uid of root (file access). To mount a filesystem, on the other hand, it is necessary to have the real uid of root (because the command mount checks the real uid).

If you want to be “full” root (real + effective uid = 0), you must use the following statement in a C root SUID program:

setuid(0);                      // 0 is the uid of root
or login as root or use su, but both will ask the root password.

To change the permissions of a file, you must be the owner of the file (or root depending on _POSIX_CHOWN_RESTRICTED if it exists, see man setprivgrp(2) as well). In HP-UX, it is not set by default. Therefore, to set the SUID bit of a root-owned file, you must be root first.

As root:

# chmod 111 myprog
# ll myprog

---x--x--x   1 root         sys       20038 Nov 25 09:11 myprog

# chmod 4111 myprog                   # or chmod u+s myprog
# ll myprog

---s--x--x   1 root         sys       20038 Nov 25 09:11 myprog
Shells and Scripts

Changing the SUID bit of a shell gives the effective uid. It is not the real uid, but it can easily lead to it, giving for example the possibility to change /etc/passwd (e.g., to set to 0 the uid of any regular user). The following lines shows the effect of setting the SUID bit of a shell on the effective uid:

As root:

  # cp /usr/bin/sh /home/me          # let's work on a copy
  # ll /home/me/sh                   # ll = ls -l
  -r-xr-xr-x   1 bin       bin    491520 Jun 29 14:10 /home/me/sh

  # chmod u+s /home/me/sh
  # chown root /home/me/sh
  # ll /home/me/sh                   # ll = ls -l
  -r-sr-xr-x   1 root      bin    491520 Jun 29 14:10 /home/me/sh

  # cd /home/me
  # echo id > t1.sh
  # chmod 555 t1.sh
  # su bin
  $ id
  uid=2(bin) gid=2(bin)

  $ /usr/bin/sh t1.sh                     # Posix shell
  uid=2(bin) gid=2(bin)

  $ ./sh t1.sh                            # local SUID shell
  uid=2(bin) gid=2(bin) euid=0(root) egid=3(sys)

  => Only the effective uids are changed.
Changing the SUID bit of a script gives a similar effect. The script must begin with the following as first line (otherwise the SUID bit is ignored):

#!/usr/bin/sh
or any other interpreter (use the absolute pathname; also the line may not exceed 32 characters in all). For example, (as root):

  # echo '#!/usr/bin/sh' > t1.sh
  # echo id >> t1.sh
  # chmod 4555 t1.sh
  # chown root t1.sh  # in principle, root is already the owner
  # ll t1.sh
  -r-sr-xr-x   1 root      sys          17 Jan  4 09:43 t1.sh
  # su bin
  $ id
  uid=2(bin) gid=2(bin)
  $ ./t1.sh
  uid=2(bin) gid=2(bin) euid=0(root)
Adding a Wrapper

If the real uid=0 is necessary (e.g., if you wish to use mount, su without password, remsh, etc.), you must create a C wrapper (Listing 1), compile it, and set the SUID bit of that executable file. The C wrapper can then call a script (if you don't intend to continue in C). If you call a script from within a C wrapper with the SUID bit set, you don't need to set the SUID bit of the script. (All listings for this article are available from the Sys Admin Web site: www.sysadminmag.com.)

Don't forget to test the interruptions (signals): ctrl-C, etc. and use, if necessary, the statement “trap” in the scripts (or the signal family in C). Also, don't forget that all the scripts called by a C wrapper (or which have the SUID bit set) must have the following first line to invoke the shell:

#!/usr/bin/sh
or another shell or interpreter (Perl, ksh, csh, etc.). /usr/bin/sh is the Posix shell (man sh-posix), the Bourne shell (man sh-bourne) is /usr/old/bin/sh (HP-UX 10.20 or 11.0). Here's another example (see also “Build a Backdoor” sidebar):

#!/bin/awk -f
{
system("id")
}

#!       have to be the 2 first characters of the first line.
Setting the Saved UID

_POSIX_SAVED_IDS indicates whether the implementation supports the saved SUID and the saved SGID (it is the case of HP-UX). The command getconf _SC_SAVED_IDS returns 1 if the saved SUID is supported, the C function sysconf(_SC_SAVED_IDS) returns 1 if the saved SUID supported (errno unchanged). Use:

#include <unistd.h>
_POSIX_SAVED_IDS can be checked at compile time (#ifdef _POSIX_SAVED_IDS) and sysconf(_SC_SAVED_IDS) at runtime (see Figure 1).

We can obtain the current value of the real and effective uids (getuid() and geteuid()), but we cannot obtain the current value of the saved uid. If a program completely changes its real+effective users, it can no longer obtain the original real+effective uid because changing the effective uid destroys the original SUID bit. Here's a program illustrating that:

  t0.c :

  #include <unistd.h>
  #include <stdio.h>
  int errno;

  main ()
  {
1 int ret;
2 printf("getuid=%d geteuid=%d\n",getuid(),geteuid());
3 ret=setuid(5);
4 printf("errno=%d ret=%d getuid=%d geteuid=%d\n",
        errno,ret,getuid(),geteuid());

5 ret=setuid(0);                // try to come back
6 printf("errno=%d ret=%d getuid=%d geteuid=%d\n",
        errno,ret,getuid(),geteuid());
  }
Now, let's compile and run the above program. As root:

  # cc t0.c
  # chmod 4111 a.out
  # ll a.out
  ---s--x--x   1 root        sys     20531 Jan 10 11:26 a.out
  # su bin
  $ id
  $ uid=2(bin) gid=2(bin)
  $ ./a.out
  getuid=2 geteuid=0
  errno=0 ret=0 getuid=5 geteuid=5
  errno=1 ret=-1 getuid=5 geteuid=5
getuid gives the real uid, geteuid the effective one. Because the program is SUID, the effective uid becomes the file's owner (1st line of output): real remains 2, effective=0 (=file's owner). Having effective uid=0, we have the “full power”. setuid(5) sets the real, effective, and saved uids to 5.

Now we have real, effective, and saved uid=5 (source code line 4). Because the effective uid is not 0, the “power” is limited. Only the effective uid can be changed (to the saved uid only). Source code line 5 tries to become root back. It fails because the saved uid is 5. See the uid-related functions in HP-UX 10.20 or 11.0 listed in Figure 2.

Restricting Access to su

The su command creates a shell with effective uid of another user. If you don't specify a user, the shell has an effective uid of root.

# ll /usr/bin/su
  -r-sr-xr-x   1 root    bin     24576 Nov  7  1997 /usr/bin/su
Changing the permissions, you can restrict the use of su to the users of a group, let's say “admin” (to define in /etc/group), as root:

  # cp /usr/bin/su /home/me          # let's work on a copy
  # chmod 4110  /home/me/su
  # chgrp admin /home/me/su
  # ll /home/me/su                   # ll = ls -l
  ---s--x---   1 root   admin    24576 Nov  7  1997 /home/me/su
Only the members of the group admin are now allowed to run su.

Other methods are:

1. see acl(5) Access Control Lists.

2. (un)alias in some .profile (see the article “Controlling Root” of Carolyn Conner in Sys Admin June 1997) to forbid the use of su by some users.

3. sudo.

Setting the uid with sudo

The sudo utility offers several options for setting uid that aren't available through most UNIX-based operating systems. The sudo Web site (www.courtesan.com/sudo/) explains:

“Sudo (Superuser do) allows a system administrator to give certain users (or group of users) the ability to run some (or all) commands as root or another user while logging the commands and arguments.”

No vendor supports the third-party package. Because it is a more complex product than an SUID C program of a few lines, it is potentially more dangerous and many potential customers refuse to install it. To be honest, it seems to work correctly and it is free of charge.

Setting the uid in Perl

Let's build a script (t.pl) containing the two lines:

  #!/usr/contrib/bin/perl
  system("id");
then (as root):

  # chmod 4555 t.pl
  # ll t.pl
  -r-sr-xr-x   1 root       bin           38 Nov  8  1999 t.pl

  # su bin
  $ ./t.pl
Output (under HP-UX 10.* or 11.0) the following lines:

YOU HAVEN'T DISABLED SET-ID SCRIPTS IN THE KERNEL YET!
FIX YOUR KERNEL, PUT A C WRAPPER AROUND THIS SCRIPT, \
  OR USE -u AND UNDUMP!
In some operating systems, you can disable the SUID effect on scripts. The SUID bits are then ignored. If the SUID effect is disabled, Perl detects the SUID bit and emulates setuid/setgid in a secure fashion. Some systems, however (such as, HP-UX 10.x or 11.0), do not let you disable the SUID effect; and Perl, therefore, always refuses to run SUID scripts (for security reasons). The solution is to use a C wrapper to call the Perl program (Listing 3) or use -u and undump. Perl -u generates a core dump, and undump creates a compiled executable from the core file. However, undump doesn't exist under HP-UX and should be provided by the creators of Perl (or any third party).

Becoming Root without the Password

The .rhosts feature provides a means for a user to become root without ever knowing the root password. Let's take the user “carol” existing on the host “chicago”:

  $ id
  uid=201(carol) gid=20(users)
  $ rlogin chicago -l root
She can log in as root without the root password if the file .rhosts is in the home directory of root, owned by root and not writable by others, and if the same .rhosts file contains the line:

chicago carol
From here, it is in principle, the default situation. /etc/inetd.conf contains the line (or similar):

login      stream tcp nowait root /usr/lbin/rlogind rlogind
/etc/services contains the line:

login      513/tcp         # remote login
inetd is running as daemon (HP-UX: /usr/sbin/inetd) or is configured to start up automatically on demand. To switch user without a password (su needs a password), user “john” becomes user “appli”. If a group of users (operators) need to log in as “appli” (e.g., to launch an application) without knowing the password of appli or of root, the following can be put into a script:

su [-] appli -c "<script or command>"
This is in turn called by a root SUID C program (likewise the “myscript” in Listing 2). In that case, su requests no password.

Some Security Tips

  • Run some regular checks in crontab. For example, list the SUID programs of the (mounted) disk space (SGID : -perm -2000):

  find / -perm -4000 -exec ls -ld {} \; > /me/check.$$
or for both:

  find / \( -perm -4000 -o -perm -2000 \) -exec ls -ld {} \; > \
    /me/check.$$
Either you have to look at the list every day to detect any new program (or to count them with wc -l) or, much better, you keep the list of the previous day and perform a diff, emailing you the difference (if any).

  • Write a little script to detect the 0 uids in /etc/passwd (3rd field) and the users without password. Trying to crack the passwords is also recommended but is out of the scope of this article.
  • Regularly look at /var/adm/sulog or automate the lookup (sending you the result). If any sulog contains lines like these:

  SU 11/29 06:30 + tty?? root-john          (root became john)
  SU 11/29 08:38 + 2 root-bin               (root became bin)
or if you detect lines ending with:

  ...... john-root                          (john became root)
it means that john knows the root password!

By the way, becoming root through a self-done SUID program (Listing 1) doesn't automatically create any entry in sulog! You can notice it by auditing the system calls, or by adding some logging lines (recommended) in the source code, like done in Listings 5 and 6.

Carefully log all the users who call the program especially in the beginning. If there is any problem, you can know who called the utility, at what time and eventually from what IP address or terminal.

  • Typically, only the people logged in as root can launch root processes (except using su but those are visible in sulog). If somebody logged in as “john” has some root processes running on his terminal device and is not in sulog, he may have used some hidden SUID program (or a bug in any utility) or he knows the root password.

Combining who -u and ps -ef, a regularly scheduled script can email you some result (if any).

  • copy: as root, no change. As non-root, you are the new owner and the SUID bit is dropped (security).
  • system(3C), exec(2), popen(2), and all the commands allowing you to load a shell (ed, vi, pg, mail, etc.) automatically give a prompt as root if we call them from a root SUID program. That allows a non-root user to do everything as root. For example, if, from a root SUIDed program, we call a script containing /usr/bin/pg to display some big lists, it allows the user to type !sh to load a shell and to receive a root prompt (!).

In this case, to avoid that, it can be enough to set export SHELL=”” in the script before calling the pg command. Other protection methods include using a restricted shell, chroot, etc.

  • If you use a temporary file with sensitive data, either set correctly the permissions and the owner or use an “invisible” temporary file:

  fd=open("/tmp/mytemp", O_RDWR|O_CREAT);
  unlink("/tmp/mytemp");
The directory entry is away but the associated storage will remain untill the last file descriptor referring to the file is closed.

  • Use full paths or set your PATH:

  system("PATH=/bin:/usr/bin;vi");
  same with popen() and exec()
Not setting (correctly) the PATH variable can lead to misuse. Likewise, check umask. As root, avoid to have umask=00 (/etc/profile or $HOME/.profile). system() is dangerous because some old shells don't reset IFS => possible misuse.

  • Don't pass user-specified args to system(), exec(), popen()
  • Keep the SUID (SGID) programs you write as simple as possible.
  • Never leave any SUID (SGID) program writable by others. Don't leave any compiled SUID program readable by others to avoid that someone sees the (eventual) security holes. Permission 4111 (or 2111) is the maximum acceptable.

References

Unix System Security by Patrick H. Wood and Stephen G. Kochan, Hayden Books, 1985.

Advanced Programming in the Unix Environment by W. Richard Stevens, Addison-Wesley, 1992.

Unix Systems Advanced Administration by Bruce H. Hunter & Karen Bradford Hunter, Macmillan Publishing Company, 1991.

Unix Power Tools by Jerry Peek, Tim O'Reilly and Mike Loukides, O'Reilly & Associates/Bantam Book, 1993.

UNIX System Security by David A. Curry, Addison-Wesley, 1992.

UNIX System Security by Rik Farrow, Addison Wesley, 1991.

UNIX Administration Guide for System V by Rebecca Thomas and Rik Farrow, Prentice Hall, 1989.

UNIX System V Release 4 Administration, 2nd Edition, by Fiedler and Hunter, Hayden Books, 1991.

About the Author

The author has worked as a consultant in the systems administration field (UNIX, networking, and Oracle) for more than 15 years. He can be reached at: dr000@usa.net.