5

I have a script somewhere in my home directory. I need to give another user or a group permissions such that when they execute that specific script, it executes as though I am logged in and has all permissions that my ID has. I do not want to use sudo or su and go through setting them as sudoers or entering passwords.

Krishna
  • 53

2 Answers2

9

With sudo you can get very granular with your permissions. If you want to give a user permission to only run your script and nothing else you can add this line to your /etc/sudoers:

user ALL=(yourusername) NOPASSWD: /path/to/your/script

Then, as the other use, you would run:

sudo -u yourusername /path/to/your/script
h3rrmiller
  • 13,235
  • I may always not have root access and I will need to do this as a normal user. I would prefer to try with file permissions. – Krishna Jun 02 '15 at 20:52
  • You will need root to edit the sudoers file. There is simply no other way to accomplish what you are trying to do. – h3rrmiller Jun 02 '15 at 20:53
  • This works. Thanks. However I am surprised that there is no way an ordinary user can share a script with another user to be run as their ID without having to work with a unix admin and have the sudoers file edited. – Krishna Jun 03 '15 at 02:17
  • i tried this, but when executing sudo -u yourusername /path/to/your/script, the shell asks me for the password of yourusername. Is that normal? (of course I replace yourusername and /path/to/your/script to my case) – David Portabella Sep 02 '19 at 07:49
1

Using sudo is the easiest way. But it does require the cooperation of the system administrator to set up.

If you want to do this without special privileges, you can use a setuid executable, but you need to be careful to not allow the caller to do more than what you intended.

Most Unix variants forbid setuid shell scripts; for example, on Linux, the kernel always ignores the setuid bit on a script. So you need a wrapper in native code.

Take care that the wrapper removes everything that could allow the user invoking it to run code. For example, whitelist environment variables; anything that isn't known to be safe (e.g. TERM) must go.

Here's a proposal for a setuid wrapper that only retains the TERM environment variable. Beware that I haven't reviewed it or really tested it; feedback welcome.

/* Compilation command:
   c99 -DTARGET='"/absolute/path/to/script"' -o setuid-wrapper setuid-wrapper.c
 */

#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

void die(const char *argv0, const char *obj, const char *msg) {
    if (msg == NULL) msg = strerror(errno);
    fprintf(stderr, "%s: %s: %s\n", argv0, obj, msg);
    exit(127);
}

int env_want(const char *entry) {
    size_t n;
    for (n = 0; entry[n]; n++) {
        if (entry[n] == '=') {
            break;
        } else if (!(isalnum(entry[n]) || entry[n] == '_')) {
            return 0;
        }
    }
    if ((n == 7 && !strncmp(entry, "DISPLAY", n)) ||
        (n == 10 && !strncmp(entry, "XAUTHORITY", n))) {
        return 1;
    }
    if ((n == 4 && !strncmp(entry, "LANG", n)) ||
        (n >= 3 && !strncmp(entry, "LC_", 3)) ||
        (n == 2 && !strncmp(entry, "TZ", n))) {
        return !strpbrk(entry, "/%");
    }
    return 0;
}

int main(int argc, char *argv[], char **environ) {
    size_t i, j;
    const char *program_name = argv[0];
    if (program_name == NULL) program_name = "setuid-wrapper";
    /* Drop privileges */
    if (setgid(getegid())) die(program_name, "setgid", NULL);
    /*if (setgroups(0, NULL)) die(program_name, "setgroups", NULL);*/
    if (setuid(geteuid())) die(program_name, "setuid", NULL);
    /* Sanitize the environment */
    for (i = j = 0; environ[i]; i++) {
        if (env_want(environ[i])) {
            environ[j] = environ[i];
            j++;
        }
    }
    environ[j] = NULL;
    /* Execute the command */
    execle(TARGET, TARGET, NULL, environ);
    die(program_name, TARGET, NULL);
}

Note that the target program will run with the supplementary groups of the caller, because you can't drop supplementary groups without being root.

An alternative approach would be to run a server (e.g. a web server) and invoke your script from that server. There are plenty of tiny HTTP servers that support CGI scripts. The major advantage of this approach is that it's easier to secure, because the server is running in a context chosen by the target (the party in whose security context the code runs), not by the client.

A variant of this approach would be to trigger the running of the script via a FUSE filesystem. For example, mount a scriptfs directory containing your script with the allow_other mount option. This requires the user_allow_other option to be set in /etc/fuse.conf.