2

I was making a Bash script with the setuid permission on, but it didn't work. So I found my solution here:

Now my script works fine and all (I rewrote it in cpp).

To satisfy my curiosity as to why pure Bash shell didn't work, I read this link: http://www.faqs.org/faqs/unix-faq/faq/part4/section-7.html (referenced by this answer: https://unix.stackexchange.com/a/2910). At that site, I came across the following:

        $ echo \#\!\/bin\/sh > /etc/setuid_script
        $ chmod 4755 /etc/setuid_script
        $ cd /tmp
        $ ln /etc/setuid_script -i
        $ PATH=.
        $ -i

I don't understand the fourth line, which reads ln /etc/setuid_script -i.

What does that command do?

I've read in the ln manual that -i is just the "interactive" flag (asking whether you want to overwrite an existing file or not). So why does ln /etc/setuid_script -i followed by PATH=. and -i make my shell execute /bin/sh -i?

jwodder
  • 448
Kristian
  • 259

2 Answers2

5

The code ln /etc/setuid_script -i is intended to create a hardlink to a file called -i in the current directory. You might need to say ln -- /etc/setuid_script -i to make this work if you are using GNU tools.

The shell can get commands to run in 3 different ways.

  1. From a string. Use sh -c "mkdir /tmp/me" with the -c flag.
  2. From a file. Use sh filename
  3. From the terminal, use sh -i or sh.

Historically when you have a shell script called foo starting with #!/bin/sh the kernel invokes it with a filename, i.e. /bin/sh foo, to tell it to use the 2nd way of reading commands. If you give it a filename of -i then the kernel invokes /bin/sh -i and you get the third way.

There are also race conditions. This was exploited thus.

  1. The exec system call is invoked to start the script.
  2. The kernel sees the file is SUID, and sets the permissions of the process accordingly.
  3. The kernel reads the first few bytes of the file to see what kind of executable it is, finds the #!/bin/sh and so sees it is a script for /bin/sh.
  4. The attacker replaces the script.
  5. The kernel replaces the current process with /bin/sh.
  6. The /bin/sh opens the filename and executes the commands.

This is a classic TOCTTOU (time of check to time of use) attack. The check in step 2 is against a different file to the one used (in the open call) in step 6.

Both these bugs are usually fixed these days.

icarus
  • 17,920
  • so the nice -20 temp & in the page is intended to execute temp script, but with very low scheduling priority (20), so that it's put off later by the scheduler; then & tell it to run in background (so the attacker can enter the mv my_script temp). Then when the shell is busy changing permission/user who execute the script, the attacker already changed the temp script with his/her own. Is this correct? – Kristian Jun 15 '21 at 01:44
  • And therefore when I use compiled binary (instead of interpreted shell script), this problem (race condition) won't occur since the binary is loaded into memory and not read line-by-line from disk. Is this correct? – Kristian Jun 15 '21 at 01:50
  • 1
    The problem is not usually the line by line reading, A mv will replace the file atomically. The issue is that the kernel starts the shell and then the shell opens the filename. With a suid binary the kernel does the open itself. Edited my answer to explain the attack more. – icarus Jun 15 '21 at 14:41
3

The document you linked to (http://www.faqs.org/faqs/unix-faq/faq/part4/section-7.html) is describing the behavior of ln on a (presumably) UNIX system, and you looked at the manual in a Linux system, or more precisely a GNU/Linux. The GNU is the relevant bit here. GNU ln does indeed have the option you mention:

   -i, --interactive
          prompt whether to remove destinations

However, this is a GNU extension and is not part of the POSIX standard where only the following option flags are defined:

-f
    Force existing destination pathnames to be removed to allow the link.
-L
    For each source_file operand that names a file of type symbolic link, create a (hard) link to the file referenced by the symbolic link.
-P
    For each source_file operand that names a file of type symbolic link, create a (hard) link to the symbolic link itself.
-s
    Create symbolic links instead of hard links. If the -s option is specified, the -L and -P options shall be silently ignored.

So, since -i isn't a valid option for ln, the command ln /etc/setuid_script -i will actually make a hardlink named -i which points to /etc/setuid_script. Next, the command PATH=. redefines the PATH variable to only search for executables in the current directory which now means that -i will execute the file named -i in the current directory which, as it is a hardlink to /etc/setuid_script, will execute the script instead. However, since this is a shell script, the actual command run will be /bin/sh -i.

terdon
  • 242,166