2

Suppose we are looking at the following scenario:

File saymyname.c (includes omitted)

int main(int argc, char** argv){
    system("whoami");   
}

Build and set permission bits:

cake@lie> gcc saymyname.c -o saymyname
cake@lie> sudo chown root:root saymyname
cake@lie> sudo chmod u+s saymyname
cake@lie> ./saymyname
cake

Every resource under the sun tells me that setting the s permission on the user-column should make the program execute with the owner's privileges, not the calling user's. Why does system("whoami"); return cake?

Modifying the program to set the UID manually like so:

int main(int argc, char** argv){
    setuid(geteuid());
    system("whoami");   
}

Yields the expected result

cake@lie> ./saymyname
root

Some resources claim that the SUID and GUID bits are often ignored. Is this why the observed behavior occurs? If so, is there a way to make it behave as if it was executed by root without setuid(.)?

nitowa
  • 1,079
  • 2
  • 9
  • 21
  • 2
    One of the better resources is Chen, Wagner and Dean's [Setuid Demystified](http://www.cs.umd.edu/~jkatz/TEACHING/comp_sec_F04/downloads/setuid.pdf). – jww Dec 05 '18 at 02:27
  • No chance with id either. Permissions `-rwsr-xr-x 1 root cake 17K Dec 5 04:05 saymyname`, `id` output: `uid=1000(cake) gid=1001(cake) groups=1001(cake),998(wheel),1000(autologin)`. Getting more puzzled by the minute now. – nitowa Dec 05 '18 at 03:08
  • `system` runs a shell. The shell might be dropping privileges. Try `execlp("id","id",(char *)0)` –  Dec 05 '18 at 03:08
  • No luck on `execlp` either. Same result as plain `system()` – nitowa Dec 05 '18 at 03:10
  • I can't reproduce that, meaning it will never print `root`. Tip: use `strace -f ./saymyname` to see the system calls being made and their results. – hek2mgl Dec 05 '18 at 03:13
  • It prints root if I run it with sudo for that matter. Feels like it's time to namedrop the kernel and shell: `Kernel: x86_64 Linux 4.19.4-arch1-1-ARCH` `Shell: zsh 5.6.2` – nitowa Dec 05 '18 at 03:16
  • Of course it prints `root` if you run it with sudo :) – hek2mgl Dec 05 '18 at 03:17
  • Is it coreutils `id`? If not, are you sure it would print euid if it found euid different from ruid? –  Dec 05 '18 at 03:20
  • It technically doesn't `never print root` if there is a way to make it print root ;). Back on topic; here's what `strace` has to say https://pastebin.com/ebSDCzf0 . Other than some rather unexpected journey through my $PATH there isn't much I can decipher from it. And it's the plain old `/usr/bin/id` that came with the distro for all I know. – nitowa Dec 05 '18 at 03:25
  • 1
    `strace` cancels setuid, because setuid programs don't necessarily want all their internals exposed to their callers. –  Dec 05 '18 at 03:27
  • @WumpusQ.Wumbley That's good to know. Following the man page, `sudo strace -u cake -f ./saymyname` can be used here – hek2mgl Dec 05 '18 at 05:52

2 Answers2

4

Looks like bash, which is executed by system(), drops the privileges. In my tests, replacing the symbolic link /bin/sh pointing to dash (instead of bash) made it work as expected.

Also with bash,

execl("/bin/bash", "bash", "-c", "whoami", NULL);

gives cake, whereas

execl("/usr/bin/whoami", "whoami", NULL);

gives root.

Georg
  • 94
  • 4
  • Spot on. As the man pages point out, `system()` calls `/bin/sh` (just like exec* family when the file isn't immediately found) which, in my case, is indeed `/bin/bash`, which does drop privileges. Before I mark your answer as correct: Do you know a way to prevent it from doing that, or is it just "a security feature"? After all, running it with `sudo` successfully passes on the privileges to `bash`, which is what I would expect the SETUID'd program to do too. – nitowa Dec 05 '18 at 04:19
  • That's because when you run the program with sudo, the uid and euid are 0. It's like running `sudo whoami` – hek2mgl Dec 05 '18 at 05:24
  • In practice, I also wouldn't use the setuid bit, but edit /etc/sudoers file instead. – Georg Dec 06 '18 at 00:13
2

Georg's answer is technically correct 1, but it is worth to mention that the system(3) man page explicitly states that the use of system() in setuid programs is not recommended:

Do not use system() from a program with set-user-ID or set-group-ID privileges, because strange values for some environment variables might be used to subvert system integrity. Use the exec(3) family of functions instead, but not execlp(3) or execvp(3). system() will not, in fact, work properly from programs with set-user-ID or set-group-ID privileges on systems on which /bin/sh is bash version [>=]2, since bash 2 drops privileges on startup. (Debian uses a modified bash which does not do this when invoked as sh.)

This is especially relevant in your example as you are calling whoami without the full path. Imagine the following scenario (as unprivileged user):

> whoami cat << 'EOF'
#!/bin/not-bash :)
echo "I'm root! let's clean up some trash ..."
# rm -rf /
EOF
chmod +x whoami

PATH="${PWD}" ./saymyname

That means, instead of changing the system shell (or using Debian), the code should just use exec(), like this:

int main(int argc, char** argv){
    execl("/usr/bin/whoami", "whoami", NULL);   
}

1 newer versions of dash on Ubuntu will also drop privileges

hek2mgl
  • 152,036
  • 28
  • 249
  • 266
  • Hah. Let this be a warning to everyone to just RTFM, I guess. – nitowa Dec 05 '18 at 09:34
  • I must say that I have learned a lot in this thread. (Or at least I'm now aware that I know nothing :) ) Especially the pdf linked by jww in the first comment below your question is very interesting to read (and extremely well done) – hek2mgl Dec 05 '18 at 11:17