Developer

checksuid script

The setuid bit in Linux can cause a wealth of security issues in the system. With the help of Perl, Vincent Danen's latest Script Teez aids you in discovering which applications on your system have the setuid root bit set.


In this Daily Feature, we're going to take another look at the power of Perl. Perl is probably one of the oldest and most-loved scripting languages around, and it's available for virtually every platform. Not so long ago, I had to look into a security issue with a popular Linux library called ncurses. The problem was that setuid root applications (programs that run as root regardless of which user executes the program) linked against ncurses. The tool to use to find out if a program is dynamically linked against ncurses is one called ldd. That program prints a list of all libraries dynamically linked against an executable file.

Of course, the problem existed only with setuid root applications, so I needed to find those first. Unfortunately, there are a high number of setuid applications, and for me to manually track down each one and run ldd against it would have taken hours. Instead of spending my time doing things manually, I chose to write a Perl script to do it all for me and print the results.

What I needed to do was simple, yet time-consuming. I needed to find all setuid applications and then determine whether they were linked against ncurses, but I only wanted to see those applications that fit my search criteria. I didn't need a screen full of setuid applications. So a little bit of Perl programming solved the problem. Let me share with you the Perl script I wrote, which I called checksuid, and then explain what it does. While most people won’t need to use the script as written, it illustrates the power of Perl and, with some changes, can be used for an infinite number of purposes.

The checksuid script looks like this.
#!/usr/bin/perl

# check suid programs to see if linked to ncurses

print "Gathering list of suid programs...
This may take a few minutes...\n";
@suid = `find / _uid 0 _perm +4000`;
chop(@suid);

print "Finished gathering... processing...\n";

foreach $bin (@suid) {
$prob = 0;
print "Checking suid root file: $bin\n";
$temp = `ldd $bin|grep libform`;
if ($temp) {
print " $bin linked against libform\n";
$prob = 1;
}
$temp = `ldd $bin|grep libmenu`;
if ($temp) {
print " $bin linked against libmenu\n";
$prob = 1;
}
$temp = `ldd $bin|grep libncurses`;
if ($temp) {
print " $bin linked against libncurses\n";
$prob = 1;
}
$temp = `ldd $bin|grep libpanel`;
if ($temp) {
print " $bin linked against libpanel\n";
$prob = 1;
}
if ($prob == 1) {
$problem .= "$bin ";
}
}

print "Files linked against ncurses that are suid root:\n$problem";
exit(0);
What it means
The first thing we do in this script is provide the location of our Perl program, which in most cases (under Linux) should be /usr/bin/perl. After that, we begin the search. Take a look at this portion of the script:
print "Gathering list of suid programs...
This may take a few minutes...\n";
@suid = `find / _uid 0 _perm +4000`;
chop(@suid);


Our first step is to print a message to the screen, which lets us know what is happening. When it says it may take a few minutes, it means just that. We’re searching every single file on the system, and it does take time.

The next step is to run the find program. What we are telling find to do here is to search every file under the root (/) file system with a permission of 4xxx, where 4 is the numeric representation of the setuid bit. This means that files with a permission of 4700 or 4770 or 4777 (owner-execute, owner/group-execute, and all-execute) are found. We also tell it that we want it to match against a certain user ID. Since we’re only interested in files that are setuid root, we use uid 0, which is always root. All the files that find returns are then placed into the array @suid. On the next line, we run the chop() command on the array, which removes the last character of every element in the array. Since the returned filename will contain the \n character (or newline character), we want to remove these from every element (returned file) in the array. After running chop() on the array, we’re ready to use it.

Our next step is to create a foreach loop. The foreach loop will go through each element of the array, one at a time. When we run
foreach $bin (@suid) {

we’re assigning the current element in the @suid array to the variable $bin. Every time the loop is run, $bin is the name of the next file to check. Because @suid contains only files that are owned by root and suid, we can then run the ldd program against the variable $bin because we know it fits our criteria.

Now we’re looking only for files that are linked against ncurses. ncurses provides four libraries: libform, libmenu, libncurses, and libpanel. Because of this, we need to check for an instance of each library. But before doing so, we need to initialize a new variable that basically reports whether or not we have a problem file. We'll call this variable $prob and initialize it with a value of 0, meaning "no problem." After we've done this, we want to check each file with ldd and interpret the results:
 $temp = `ldd $bin|grep libform`;
 if ($temp) {
 print " $bin linked against libform\n";
 $prob = 1;
 }


At this point, we’re checking for the library libform. We make a new variable called $temp in which we execute an external command. In this case, we’re running ldd on the variable $bin, which is the name of the file to check. Since ldd will print every library the binary is dynamically linked against, we want to do a simple check to see if the library we’re interested in is in the list. Thus, we pipe the output of the ldd command as input to the grep command, which in turn looks for the word libform. Because we’re using grep in this fashion, $temp will be assigned a value only if grep returns true, meaning it found the library name we’re searching for.

We then run the if command on the $temp variable. If it exists, then we’ve found the library we’re looking for. So at this point we print a message to the screen indicating the file we checked and what library it is linked against. We then assign the $prob variable a value of 1, meaning that $prob is true.

You see in the program that this command is repeated for each library we’re interested in, so we include it four times. Once we’ve finished checking, we then need to check to see if $prob is true. If we don’t find any libraries we’re interested in, then $prob will be false (0). If even one library shows up, $prob will be true (1).

Since we also want to make a summary report once the script has finished, we want to keep track of which files are problematic. We do this using the following if statement:
 if ($prob == 1) {
 $problem .= "$bin ";
 }


What we do here is simple. If $prob is true, then we assign the value of $bin, which is the name of the file we’re checking, to the variable $problem. We use the .= assignment operator so that the name of the file is added to the list along with any other previous value of $problem. If this is the first problem file, then $problem will contain the name of the file. If this is the second, then $problem will contain the names of the first and second files, separated by a space.

The foreach loop will continue to loop until our list of files is exhausted. Then the foreach loop will exit, and we can print our summary:
print "Files linked against ncurses that are suid root:\n$problem";

This will print to the screen the list of files that are problematic, which we stored in the $problem variable.

If you were to run this program as is, the output of the script would look something like what’s shown below. It isn't the entire output—just some interesting parts:
Gathering list of suid programs. This may take a few minutes.
Finished gathering... processing...
Checking suid root file: /bin/su
Checking suid root file: /bin/ping
Checking suid root file: /bin/mount
...
Checking suid root file: /usr/bin/xativ
 /usr/bin/xativ linked against libncurses
...
Checking suid root file: /usr/bin/vmware
Checking suid root file: /usr/bin/xatitv
 /usr/bin/xatitv linked against libncurses
...
Checking suid root file: /usr/bin/xatitvc
 /usr/bin/xatitvc linked against libncurses
Checking suid root file: /usr/bin/cdrecord
...
Checking suid root file: /sbin/linuxconf
 /sbin/linuxconf linked against libncurses
Files linked against ncurses that are suid root:
/usr/bin/xativ /usr/bin/xatitv /usr/bin/xatitvc /sbin/linuxconf


Conclusion
This script in its current form may not be overly useful to anyone other than someone performing security audits or programming functions. It can easily be tailored to search for other types of files that contain other content, however. It's the premise of the script that’s important: You can spend a few minutes to write a simple Perl script to perform a function that could otherwise take hours.
The authors and editors have taken care in preparation of the content contained herein but make no expressed or implied warranty of any kind and assume no responsibility for errors or omissions. No liability is assumed for any damages. Always have a verified backup before making any changes.

About Vincent Danen

Vincent Danen works on the Red Hat Security Response Team and lives in Canada. He has been writing about and developing on Linux for over 10 years and is a veteran Mac user.

Editor's Picks