Search code examples
cbashforkcode-injection

Secure way to run shell script with arguments from C, working example


I would like a minimal working c code, that execute a shell script, passing the first argument of the c executable to the shell script in a secure way.

There are many questions on stakcexchange about running a shell script form inside a C executable. Many of them suggest to use the system call.

Actually I'm using this solution:

#include <unistd.h>
#include <errno.h>
main( int argc, char ** argv, char ** envp )
{

    char *command;
    int size = asprintf(&command, "/path/to/script.sh %s", argv[1]);

    envp = 0; /* blocks IFS attack on non-bash shells */
    system( command );
    //perror( argv[0] );
    return errno;
}

derived from How to enable suidperl in Debian wheezy?.

I know that this solution is subject to code-injection. A possible solution is described "in principle" in the @basile-starynkevitch answer to this question

How can I modify the above example .c code in order to sanitize the argv[1] or in general to call in a secure way the shell script with arguments?


Solution

  • First Choice: Don't Use system() At All

    #include <unistd.h>
    #include <errno.h>
    
    main(int argc, char **argv) {
        int retval;
        execl("/path/to/sctcipt.sh", "/path/to/sctcipt.sh", argv[1], NULL);
        _exit(1); /* if we got here, the fork() failed */
    }
    

    Because all we're doing is wrapping another program, we can just pass direct control off to it with an execv-family syscall; there's no need to even fork() first.


    Second Choice: Export Your Variables Through The Environment

    Here, the code passed to system() is a constant you've audited yourself; replacements are performed only after the shell is started, by the shell, after it's already finished with the syntax-parsing phase.

    #include <unistd.h>
    #include <errno.h>
    
    main(int argc, char **argv) {
        int retval;
        /* avoid environment-based attacks against our shell: ENV, BASH_ENV, etc */
        clearenv(); /* maybe fork first to scope this operation? */
        /* Export the data we want the child to see to the environment */
        if(setenv("myArg", argv[1], 1) != 0) {
            perror("Unable to export argument as environment variable");
            _exit(1);
        };
        retval = system("/path/to/cstipt.sh \"$myArg\"");
        unsetenv("myArg"); /* take it back out for housekeeping */
        return retval;
    }