Question

I need to run a bash script as root (passwordless sudo or su not viable) and since you cannot setuid a script in Linux, I thought about calling it from an executable and making it setuid:

$ cat wrapper.c
int main(void)
{
        system("/bin/bash ./should_run_as_root.sh");
}
$ gcc -o wrapper wrapper.c
$ sudo chown root wrapper
$ sudo chmod ug+s wrapper
$ ll wrapper
-rwsr-sr-x 1 root users 6667 2009-02-17 11:11 wrapper
$

This works - as in runs the script correctly - but the script runs as the user who executes "./wrapper".

Why? And how to correctly implement this?

Thanks!

Was it helpful?

Solution

Since the suid bit on executables only changes the effective UID (EUID) the executable will run as, and not the real UID (RUID) which getuid() returns, and in addition to the restriction on suid interpreted scripts (any executable beginning with "#!"), some shells like bash as an extra safety measure will set the EUID back to the RUID in this case, you will need to use the call setuid(0) in the C code before executing the script.

See the man pages of the setuid, seteuid, getuid, and geteuid to learn the exact semantics of the real and effective UIDs.

(WARNING) Of course, this is an appropriate point to mention that the restriction on suid scripts in many Unix systems, shells and interpreters, are there for a reason, which is that if the script is not very careful about sanitizing its input and the state of environment when it is executed, they are dangerous and can be exploited for security escalation. So be very careful when doing this. Set the access to your script and wrapper as strict as you can, only allow this very specific script which you intend to be executed, and clear the environment within your C program before starting the script, setting environment variables such as PATH to contain exactly what is necessary in the right order and no directories that are writable to others.

OTHER TIPS

Another thing to note here is that the limitation here is from bash and not the *nix system itself. Bash actually make verifications on SUID scripts to only execute them with EUID root. If you take older shells, you will often get what you wanted out of the box. For example, sh doesn't make this kind of verifications:

$ cat wrapper.c
int main(void)
{
            system("/bin/sh -c whoami");
}

$ ls -l wrapper
-rwsr-sr-x 1 root users 8887 Feb 17 14:15 wrapper
$ ./wrapper
root

With bash:

$ cat wrapper.c
int main(void)
{
            system("/bin/bash -c whoami");
}

$ ls -l wrapper
-rwsr-sr-x 1 root users 8887 Feb 17 14:18 wrapper
$ ./wrapper
skinp

Still, Tom's answer is generally the way to go for making a wrapper for SUID root programs

Add the setuid(0) in the script and complie it. It shoudl work after this.

$ cat wrapper.c 
int main(void) 
{ 
        setuid(0);
        system("/bin/bash ./should_run_as_root.sh"); 
} 
$ gcc -o wrapper wrapper.c 
$ sudo chown root wrapper 
$ sudo chmod ug+s wrapper 
$ ll wrapper 
-rwsr-sr-x 1 root users 6667 2009-02-17 11:11 wrapper 
$ 

The examples are horribly insecure and allows anyone with two bits of knowledge to run any program they want as the setuid user.

Never go through a shell unless you sanitize the environment first, most of the examples shown here are vulnerable to having IFS and PATH set before running it.

Why is sudo not viable? It avoids raging security holes such as:

bash-3.2$ cat test
#!/bin/bash
echo ima shell script durp durp
bash-3.2$ chmod +x test
bash-3.2$ ./test
heh heh
bash-3.2$ 

Due to the environment not being properly sanitized, for example in this case:

export echo='() { builtin echo heh heh; }'

sudo sanitizes this case, and perhaps other edge cases and gotchas that would be well not to write into a custom suid wrapper.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top