Sudo Snooper: Code, Explanation and Justification
It was a dark and stormy night, in the corner a Postfix server threatened
to buckle under the weight of the Sobig.F worm. On a mailing list not quite
in a galaxy far far away
an argument a discussion about sudo,
history files and information disclosure raged.
One of the topics that came up was the information you can glean from the process table as people use commands such as Sudo and su instead of running everything as root. Both sudo and su allow you to execute commands as other users (assuming you have the correct privileges), su does this in a sledgehammer kind of way, where you are required to have the password of the user you want to run the command as before you can get anywhere.
Sudo on the other hand does things properly. A flexible configuration file allows you to define exactly what each user or group of users can do, which machines they can do it on and which options they can do it with. Add to this a sensible logging system (syslog and an optional logfile) and a command (visudo) that both validates the config file and opens it in a sanitised environment to prevent a number of common but nasty privilege escalation tricks and you have a pretty essential tool.
If you run sudo then you need to consider the presence of the history
file, the only way to get a list of the commands a user can run under sudo
is to issue a
'sudo -l' command, which requires you to enter a
password before it will divulge any information. Any easy way around this
is to go rummaging through the users shell history for any sudo invocations
that they can find. By removing this file you force an attacker to either
guess, or step up his game.
When a command is run under sudo that the config file doesn’t permit a log entry is created and, if the sysadmin is paying attention, a red flag is raised containing the user, the attempted command and any arguments passed to it. An attacker blindly attempting to guess the permitted commands for a users is not only looking for a needle in a haystack but he’s also painting a bulls eye on himself (that sentence is toned down quite a bit from how i first wrote it!)
The script attached both here and at the bottom of this page is a proof of concept based upon some of the arguments presented that suggested a way of taking the guessing and turning it into a more methodical approach, monitoring the process table.
When a process is created using su or sudo it’s PPID is that of (typically) the invoking users shell. By looking for processes that are owned by one user and the process indicated by their PPID is owned by a different user you can gain some insight on to which users have the power to run commands under another users context.
In our example script we look for any root owned process that has a non-root owned PPID and we flag both that we have found it as well as a time stamp and what we’ve found. The script itself isn’t very complex and could be optimised if need be to run a little faster.
The script itself is just a proof of concept and has a number of points that need to be considered before you run it.
- The script only grabs the process table every minute
- All these calls to ps makes the PID's rise rapidly
- It picks up exim/postfix
- PID looping
Lets address the issues in the order presented above: On the machines this was tested on once a second was enough to grab 95% of the interesting activity, you won’t for instance see a user running ‘sudo ls -al /home/another_user’ but i’m willing to live with that.
The PID's increasing like this is semi-deliberate, as this is a proof of concept i have no real interest in making it invisible. Increasing the PID every run makes it easier for an admin to spot its running.
A number of MTA’s (rightly) run their queue processing components as a separate non-priviledged user. This gets caught in our net but its easy enough to filter out with either a grep -v or a tweak to the code.
Once a machines PID's have reached a certain value they loop and restart at 1, in order to avoid showing all the sudo’s each run (i.e. once a second) the script caches those already seen and doesn’t show them again. If the script was running for long enough and the PID's looped then it could fail to show valid sudo’s if it had already seen the PID, even though the actual command has changed. This can be worked around quite easily with any of a wrapper script, invoking via cron or even just using it only when your actually paying attention to its output. As an example Linux restarts its PID's at just over 32,000
The format of the output includes a time stamp, both the usernames involved and the entire entry from the process table, comma separated (thats what i wanted at the time ;), the formatting itself can be changed easily in the code at the end of the ‘whittle_processes’ subroutine.
Although the principles behind this script are of most use to an attacker it does present an advantage that might make it worth using as part of your toolkit, if you suspect your sudo binary has been trojaned running sudosnooper for a couple of hours and then comparing sudo’s logs to its output might present you with the information you need to prove it. If not hopefully at least something on this page (other than my appalling grammar) has made you go ‘hm’.