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?


# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 10 (buster)"
NAME="Debian GNU/Linux"
VERSION="10 (buster)"


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
  • @StephenKitt - will you elaborate? I only know of real uid and effective uid. Furthermore, how can I check the ruid? And how can I turn my command into e.g. a bash root shell?
    – Shuzheng
    Commented Mar 24, 2020 at 15:33
  • awk works as intended. Try this, awk 1 /etc/shadow and if it honours its setuid bit it will display the contents of the otherwise protected shadow file. Commented Mar 24, 2020 at 15:35
  • id shows you the effective UID, "id -r" the real one. Commented Mar 24, 2020 at 15:37

1 Answer 1

$ 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
$ 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")'
$ env - /tmp/perl -e 'system("{ id -nu; }")'

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]


[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
debian10$ su - other_user -c 'perl /dev/fd/7' 7<<<'print "OK\n"'
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
  • So, the only way to have e.g. awk execute commands as root is to replace the system's shell with another one that doesn't reset the euid? I guess, that's good for security, but not from a developer's point of perspective.
    – Shuzheng
    Commented Mar 25, 2020 at 9:47
  • Are you using /dev/fd/0 as an argument to perl, since the piped input is available on that file descriptor?
    – Shuzheng
    Commented Mar 25, 2020 at 9:51
  • Because OpenBSD's perl will not accept an -e script or reading the script from stdin when running in setuid mode. See the edit history; the prev version used the less obtuse env - /tmp/perl -e 'system("...")'. I haven't investigated yet if this is a feature in new versions of perl, or it's an OpenBSD addition. env - /tmp/perl <(echo 'system("id -nu")') would work too, and not mess with perl's stdin, but not all shells support <(...) process substitution.
    – user313992
    Commented Mar 25, 2020 at 10:34
  • But isn't /dev/fd/0 stdin, which you say perl cannot read from setuid mode?
    – Shuzheng
    Commented Mar 25, 2020 at 13:31
  • 1
    e‎x‎a‎c‎t‎l‎y ‎
    – user313992
    Commented Mar 26, 2020 at 16:16

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .