Calling a script from a setuid root C program - script does not run as root
Asked Answered
S

5

31

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!

Seamus answered 17/2, 2009 at 10:18 Comment(1)
For the reasoning behind the answers below, see man system , and #1051870Scotsman
P
49

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.

Penates answered 17/2, 2009 at 10:19 Comment(3)
Is it safer to save the original UID and reset it at the end of the program?Leadership
If your program does other things after the script finishes, that do not need this, then yes. If all you do is return, then it doesn't really matter. In the typical case a script wrapper will use exec and not system(...), so anyway the wrapper will not run after the script ends anymore.Penates
@TomAlsberg Do you know the source code/documentation to confirm the Bash's behavior for this 'auto reset uid'?Analisaanalise
L
7

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

Leadership answered 17/2, 2009 at 14:18 Comment(4)
You are right, of course. Linux handles restricting suid scripts by checking the executable's mode in the exec call, and this restriction is an additional safety measure of some interpreters. Not sure why I got this confusion when I wrote this. I corrected my answer. Thanks!Penates
And of course I missed that in the OP's code he is not actually exec'ing a script with a #! interpreter specification at all, but calling bash directly, so the kernel's restriction would not apply in this case anyway - but the kernel does have this restriction for #! scripts with the suid file mode.Penates
Does not work (anymore). On one hand, man system says Do not use system() from a program with set-user-ID or set-group-ID privileges, because .... On the other hand, this works in Ubuntu 14.04 but does not in Ubuntu 16.04.Diseur
@Diseur as I mentioned, this is going to be shell dependent. /bin/bash in ubuntu is actually linked to dash. It looks like this was fixed between 14.04 and 16.04, likely in bugs.launchpad.net/ubuntu/+source/dash/+bug/1215660. Other shells on other distributions/versions are going to have different behaviors.Leadership
S
3

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 
$ 
Senegambia answered 6/5, 2010 at 8:37 Comment(1)
Do not use system() from a program with set-user-ID or set-group-ID privileges, because strange values for some environment variables might be used to subvert system integrity. Use the exec(3) family of functions instead, but not execlp(3) or execvp(3). system() will not, in fact, work properly from programs with set-user-ID or set- group-ID privileges on systems on which /bin/sh is bash version 2, since bash 2 drops privileges on startup. (Debian uses a modified bash which does not do this when invoked as sh.)Downtrodden
F
1

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.

Fearnought answered 3/1, 2012 at 11:36 Comment(0)
S
0

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.

Suber answered 3/11, 2011 at 23:42 Comment(1)
man sudo: “Running shell scripts via sudo can expose the same kernel bugs that make setuid shell scripts unsafe on some operating systems...” A standard wrapper to make suid scripts is probably best, but do you know one? Sudo is unsafe here, because it relies on the kernel to pass the script via /dev/fd/...!Horton

© 2022 - 2024 — McMap. All rights reserved.