Skip to Content [alt-c]

November 18, 2012

Security Pitfalls of setgid Programs

If someone asked you whether it was safer to write a setuid program or a setgid program, what would you say? What about a setgid-only program versus a program that was both setuid and setgid? Instinctively, I would say setgid-only: setgid grants fewer privileges, which by principle of least privilege ought to be a good thing.

Not so. Here is a story that convinced me that setgid-only programs are inherently less safe than setuid programs.

The TAs at Brown's Computer Science department keep track of student grades using a homegrown system written in Python with some shell thrown in. One common command is report STUDENTLOGIN, which lists the grades for a particular student. The grades are stored in a file readable and writable only by the course's TA group, of which the TA staff are members.

Someone decided that it would be nice for students to be able to check their own grades at any time. So a setgid wrapper program called "mygrades" was written in C. It was owned by the course TA group and exec'd the report command with the username of the real user ID (i.e. the student who invoked the program) as the first argument. The wrapper program was written very carefully and scrutinized closely. In particular, the environment was completely wiped and replaced by a safe environment. What could possibly go wrong?

As it turns out, a lot. Here is a simplified version of the report command, which is a KornShell script that determines how it was called and execs the Python code appropriately:

#!/bin/ksh # Assume that this script is installed one directory down from the course directory root (e.g. in tabin) export COURSEDIR=$(readlink -f "$(dirname "$(whence "$0")")/..") export EVALPIG_ROOT=$(readlink -f "$(dirname "$(readlink -f "$(whence "$0")")")") export PYTHONPATH=$COURSEDIR/config:$EVALPIG_ROOT exec python -O -c "__import__('$(basename "$0")').main()" "$@"

Can you spot the vulnerability? Keep in mind, the environment is completely safe. So are the arguments (including $0).

The problem is that KornShell implements command substitution (i.e. $(...)) by creating a temporary file (in /tmp), redirecting the output of the command to the temp file, and then reading it into the variable. Since mygrades is a setgid wrapper, the user ID does not change. Consequentially, the temporary files are owned by the user who invoked mygrades. The user can write to one of the temporary files and as a result, inject arbitrary data into one of the script's variables. This is bad for any script, and really bad for the script above. An attacker could effectively set an arbitrary PYTHONPATH and thus execute arbitrary Python code with the permissions of the TA group.

Of course, the temporary file exists only for an instant (it's unlinked immediately after being opened), but I easily wrote a program that monitored /tmp with inotify and opened the file as soon as it was created. It didn't work every time, but if I ran mygrades in an infinite loop my attack would succeed within a minute.

What is counter-intuitive is that if the mygrades program had been setuid, this wouldn't have been a problem. The temp files would have been owned by the effective UID of mygrades and would have been tamperproof. Linux (and any decent Unix implementation) otherwise does a very good job protecting setgid programs from tampering by the process owner: the owner of a setgid process isn't allowed to ptrace it, debug it, etc. But the kernel can do nothing to protect the files that a setgid program creates on the filesystem. For this reason, unless you are sure that a program will not create temporary files at any point during its execution, or you are sure that a tampered-with temp file will not lead to a security compromise (which is probably a bad assumption), it is safer to make a program setuid than setgid-only.

Comments

Reader Pi on 2014-03-12 at 03:26:

Except that with (just) setuid the attacker could use "umask 002" and then since the group didn't change, he'll still be able to modify the tmpfile...

The correct way to handle things for any non-god entity is to not use setuid or setgid at all, but rely on sudo instead. That is the proper scrutinizer program that you should use.

Reply

Andrew Ayer on 2014-03-12 at 03:39:

No, umask is actually not a concern because any decent temporary file implementation creates the file with restrictive permissions (0600). Besides, the setuid program could always set its own predictable umask.

Agreed that you should just use sudo if you can, especially if you have no experience writing secure set[ug]id programs. Using sudo is not always feasible though, especially since it depends on your sysadmin to set up rules. For example, using sudo would have been totally infeasible for the use case presented here, since each course needs its own grading database.

Reply

Post a Comment

Your comment will be public. To contact me privately, email me. Please keep your comment polite, on-topic, and comprehensible. Your comment may be held for moderation before being published.

(Optional; will be published)

(Optional; will not be published)

(Optional; will be published)

  • Blank lines separate paragraphs.
  • Lines starting with > are indented as block quotes.
  • Lines starting with two spaces are reproduced verbatim (good for code).
  • Text surrounded by *asterisks* is italicized.
  • Text surrounded by `back ticks` is monospaced.
  • URLs are turned into links.
  • Use the Preview button to check your formatting.