Setuid – Common Binaries Not Respecting Setuid Bit

setuidsuid

I'm hardening a Linux system and wanted to test (setuid-based) shell escapes using common binaries, like awk, vim, etc., supporting command executing.

However, all binaries I've tested except sh and bash don't respect their setuid bit.

In particular, awk continues to execute as a normal user:

$ ls -lL /usr/bin/awk
-rwsr-xr-x 1 root root 121976 Mar 23  2012 /usr/bin/awk
$ id
uid=1000(bob) gid=1000(bob) groups=1000(bob)
$ awk 'BEGIN{system("id")}'
uid=1000(bob) gid=1000(bob) groups=1000(bob)

In contrast, bash executes as root when given the -p option:

$ ls -la /bin/bash
-rwsr-xr-x 1 root root 1168776 Apr 18  2019 /bin/bash
$ /bin/bash -p
# id
uid=1000(bob) gid=1000(bob) euid=0(root) groups=1000(bob)

Is there any way to make awk, vim, less, etc. respect the setuid bit and execute the command as root?

OS:

# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 10 (buster)"
NAME="Debian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"

Update:

parallels@debian-gnu-linux-vm:~$ ls -la /proc/self/fd/0 /dev/fd/0 /dev/stdin
lrwx------ 1 parallels parallels 64 Mar 26 08:15 /dev/fd/0 -> /dev/pts/1
lrwxrwxrwx 1 root      root      15 Mar 20 19:56 /dev/stdin -> /proc/self/fd/0
lrwx------ 1 parallels parallels 64 Mar 26 08:15 /proc/self/fd/0 -> /dev/pts/1

Best Answer

$ ls -lL /usr/bin/awk
-rwsr-xr-x 1 root root 121976 Mar 23  2012 /usr/bin/awk
$ awk 'BEGIN{system("id")}'
uid=1000(bob) gid=1000(bob) groups=1000(bob)

In your example, it's not awk which is dropping privileges or not "respecting its setuid bit", but the /bin/sh command that awk uses to implement its system() function.

Just like its C counterpart, awk's system() does not parse and run the command directly, but by passing it as an argument to /bin/sh -c. If /bin/sh is bash (or the Debian version of dash, or a couple of other shells which copied this misfeature from bash), it will reset its effective uid back to the real one.

The same thing applies to print | "cmd" or "cmd" | getline in awk -- they're implemented with popen(3) which calls /bin/sh -c. Notice that it's always /bin/sh (or the system's shell, eg. /system/bin/sh on Android), not the user's login shell or that from the $SHELL environment variable. [1]

This is different in perl: perl's system, exec, open "|-", open2, open3, etc will run the command directly if they're called with multiple arguments or if the command does not contain shell metacharacters:

$ id -nu
ahq
$ ls -l /tmp/perl
-rwsr-xr-x 1 dummy_user dummy_user 3197768 Mar 24 18:13 /tmp/perl
$ env - /tmp/perl -e 'system("id -nu")'
dummy_user
$ env - /tmp/perl -e 'system("{ id -nu; }")'
ahq

This example is on Debian 10. On other systems like FreeBSD or older Debian, both commands will print the same thing, because their /bin/sh does not drop privileges. [2]


Notes:

[1] Other programs like vim and less do use the $SHELL environment variable, so they're easily "fixable" by pointing it to some wrapper. In vim you could also use :set shcf=-pc to pass the -p option to the shell used for the :! and similar commands.

[2] The perl example will also work on OpenBSD just like on FreeBSD, provided that you replace the env - /tmp/perl 'script' with the more obtuse echo 'script' | /tmp/perl /dev/fd/0.

OpenBSD's perl will reject the -e argument and refuse to read its script from the stdin when running in setuid mode (see this which is ending here -- OpenBSD supposedly has secure setuid scripts).

But that does not apply to /dev/fd/N, which perl is handling itself when given as a script name (only the /dev/fd/N form, not /dev/stdin or /proc/self/fd/N).

obsd66$ ls -l /tmp/perl
-rwsr-xr-x  1 dummy_user  dummy_user  10728 Mar 25 18:34 /tmp/perl

obsd66$ env - /tmp/perl -e 'system("{ id -nu; }")'
No -e allowed while running setuid.

obsd66$ echo 'system("{ id -nu; }")' | env - /tmp/perl
No program input from stdin allowed while running setuid.

obsd66$ echo 'system("{ id -nu; }")' | env - /tmp/perl /dev/stdin
Can't open perl script "/dev/stdin": Operation not permitted

obsd66$ echo 'system("{ id -nu; }")' | env - /tmp/perl /dev/fd/0
dummy_user
debian10$ su - other_user -c 'perl /dev/fd/7' 7<<<'print "OK\n"'
OK
debian10$ su - other_user -c 'perl /proc/self/fd/7' 7<<<'print "OK\n"'
Can't open perl script "/proc/self/fd/7": Permission denied