Calling a script from a setuid root C program - script does not run as root
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!
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.
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.