What does execve() do? - c++

What exactly does execve() do? I've tried looking at the documentation (http://linux.die.net/man/2/execve) but given that I'm very new to linux and this sort of programming it doesn't make a lot of sense. What I want to do is be able to execute this command:
nc -l -p someport -e /bin/sh
Can I do something like the following (where someport is a number such as 4444)
char *command[2];
command[0] = "nc -l -p someport -e /bin/sh"
execve(command[0], name, NULL);

execve asks the operating system to start executing a different program in the current process.
Chances are pretty decent that you want execvp or execlp instead -- you haven't mentioned anything about wanting to provide the environment for the child, but from the looks of things you probably do want the path searched to find the executable you're using.

Correct usage is
extern char * const environ[];
char * const command[] = {"nc", "-l", "-p", "porthere", "-e", "/bin/sh", NULL};
execve("/usr/bin/nc", command, environ);
You must use a full pathname, not a short name such as "nc" (more precisely: no PATH search is done, the pathname must be an actual existing file), and you must split arguments into separate strings beforehand. You also need to propagate the environment somehow, either via the extern environ mentioned in the above snippet or as obtained from the third parameter of main(); the latter is slightly more standards-blessed but may be more painful to pass around as needed.

Related

Are ALL system() calls a security risk in c++?

A post in this (Are system() calls evil?) thread says:
Your program's privileges are inherited by its spawned programs. If your application ever runs as a privileged user, all someone has to do is put their own program with the name of the thing you shell out too, and then can execute arbitrary code (this implies you should never run a program that uses system as root or setuid root).
But system("PAUSE") and system("CLS") shell to the OS, so how could a hacker possibly intervene if it ONLY shells to a specific secure location on the hard-drive?
Does explicitly flush—by using fflush or _flushall—or closing any stream before calling system eliminate all risk?
The system function passes command to the command interpreter, which executes the string as an operating-system command. system uses the COMSPEC and PATH environment variables to locate the command-interpreter file CMD.exe. If command is NULL, the function just checks whether the command interpreter exists.
You must explicitly flush—by using fflush or _flushall—or close any stream before you call system.
https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/system-wsystem
In case, there are any doubts here's the actual snippet from the MS' implementation (very simple and straightforward):
// omitted for brevity
argv[1] = _T("/c");
argv[2] = (_TSCHAR *) command;
argv[3] = NULL;
/* If there is a COMSPEC defined, try spawning the shell */
/* Do not try to spawn the null string */
if (argv[0])
{
// calls spawnve on value of COMSPEC vairable, if present
// omitted for brevity
}
/* No COMSPEC so set argv[0] to what COMSPEC should be. */
argv[0] = _T("cmd.exe");
/* Let the _spawnvpe routine do the path search and spawn. */
retval = (int)_tspawnvpe(_P_WAIT,argv[0],argv,NULL);
// clean-up part omitted
As to concerns of what _tspawnvpe may actually be doing, the answer is: nothing magical. The exact invocation sequence for spawnvpe and friends goes as following (as anybody with licensed version of MSVC can easily learn by inspecting the spanwnvpe.c source file):
Do some sanity checks on parameters
Try to invoke _tspawnve on the passed file name. spawnve will succeed if the file name represents an absolute path to an executable or a valid path relative to the current working directory. No further checks are done - so yes, if a file named cmd.exe exists in current directory it will be invoked first in the context of system() call discussed.
In a loop: obtain the next path element using `_getpath()
Append the file name to the path element
Pass the resulted path to spwanvpe, check if it was successful
That's it. No special tricks/checks involved.
The original question references POSIX not windows. Here there is no COMSPEC (there is SHELL but system() deliberately does not use it); however /bin/sh is completely, utterly vulnerable.
Suppose /opt/vuln/program does system("/bin/ls"); Looks completely harmless, right? Nope!
$ PATH=. IFS='/ ' /opt/vuln/program
This runs the program called bin in the current directory. Oops. Defending against this kind of thing is so difficult it should be left to the extreme experts, like the guys who wrote sudo. Sanitizing environment is extremely hard.
So you might be thinking what is that system() api for. I don't actually know why it was created, but if you wanted to do a feature like ftp has where !command is executed locally in the shell you could do ... else if (terminalline[0] == '!') system(terminalline+1); else ... Since it's going to be completely insecure anyway there's no point in making it secure. Of course a truly modern use case wouldn't do it that way because system() doesn't look at $SHELL but oh well.

Show environment variable LD_LIBRARY_PATH

I have simple console application that prints environment variables:
int main(int argc, char **argv, char **envp)
{
printf("Scanning for LD_LIB_PATH\n");
for (char **env = envp; *env != 0; env++)
{
char *thisEnv = *env;
std::string s= *env;
if (s.find("LD_LIBRARY_PATH")!=std::string::npos)
{
printf("!!!!!!! %s\n", thisEnv);
}
printf("%s\n", thisEnv);
}
}
Before run executable I run script that sets LD_LIBRARY_PATH
export LD_LIBRARY_PATH=~/aaa/bbb/Debug:~/ccc/ddd/Debug
echo "searching:"
export | grep LD_LIBRARY
echo "done"
Script run fine with output:
exporting
searching:
declare -x LD_LIBRARY_PATH="/home/vicp/aaa/bbb/Debug:/home/vico/ccc/ddd/Debug"
done
I run executable and it finds many variables but no environment variable LD_LIB_PATH. Why?
UPD
As recommended I script . ./script.sh
Then double check with command:
export |grep LD_LIBRARY_PATH
Got output:
declare -x LD_LIBRARY_PATH="/home/vicp/aaa/bbb/Debug:/home/vico/ccc/ddd/Debug"
But still don't see LD_LIBRARY_PATH in my programm.
Depending on how you run the script, the env variable will only be added to the environment of the subshell running the script.
If you run the script like this:
$ ./script.sh
This will spawn a new shell wherein the script is run. The parent shell, i.e. the one you started the script from, will not be affected by what the script does. Changes to the environment will therefore not be visible (changing the working directory or similar will also not work).
If the script is intended to modify the environment of the current shell, the script needs to be sourced using the . or source commands:
$ . ./script.sh
$ source ./script.sh
From help source in bash:
Execute commands from a file in the current shell.
Then there seems to be a problem with the code:
As a commenter stated previousy, there are a lot of exclamation points in the success case printf. I presume these were meant to highlight the found variable name. Since they are outside of the opening quote of the format string, they are interpreted as logical not operators.
The result is that the format string literal (a pointer) is negated, resulting in false due to the number of operators. This usually maps to 0, which would mean the format string becomes a null pointer. What happens when handing a null pointer to printf as the format string depends, as far as I can tell, on the implementation. If it doesn't crash, it most certainly will not print anything however.
So the code may actually work, but there is no visible output due to that bug. To fix it, move the exclamation points into the format string, i.e. after the opening quote.
Look at the line printf(!!!!!!!"%s\n", thisEnv);
Change to printf("!!!! %s\n", thisEnv);
It has nothing to do with your C/C++ application.
try the following:
$ ./script.sh
$ echo $LD_LIBRARY_PATH
And you'll see that LD_LIBRARY_PATH is not set
When you launch your script, bash creates a new process with its environment inherited from the original bash process. In that newly created process, you set the process environment variable LD_LIBRARY_PATH=xxxx
When finalized your script.sh exits, and its environment dies with it.
Meaning the LD_LIBRARY_PATH is not set in your original shell.
As mentioned here above you need to run your script in the current shell
Either with . or with source.
I tested with your C/C++ and it works

how to add a space character to trigger var args c++ execlp()

I'm writing this program on Ubuntu.
If I type this command into a shell
groups root sys bin
it outputs
root : root
sys : sys
bin : bin
However I'm writing a c++ program that calls groups with execlp using
execlp("groups", "groups", args.c_str(), NULL);
where args = "root sys bin". I just get a :No such user error since groups' is obviously just looking at that entire string as argv[0] which is the equivalent of running
groups "root sys bin"
How do i create the proper variable argument for execlp to run groups on each user, one at a time?
One option is to ask /bin/sh to deal with the input the way it normally would. Of course in addition to dealing with spaces, this would also deal with characters like $, #, ~, *, etc., which may or may not be what you want.
execl("/bin/sh", "sh", "-c", ("groups " + args).c_str(), nullptr);
Obviously, don't use this way if the data is user-entered and might contain nasty strings like:
root ;rm *
Otherwise, execl type functions won't work unless you know the number of command-line arguments at compile time. Assuming your args string could have varying numbers of arguments, you'll need execv type functions instead.
std::string args = "root sys bin";
std::vector<std::string> arg_vec;
std::istringstream arg_iss(args);
std::copy(std::istream_iterator<std::string>(arg_iss),
std::istream_iterator<std::string>(),
std::back_inserter(arg_vec));
char groups_exec[] = "groups";
std::vector<char*> arg_ptr_vec{ groups_exec };
std::for_each(arg_vec.begin(), arg_vec.end(),
[&](std::string& arg){ arg_ptr_vec.push_back(&arg[0]); } );
arg_ptr_vec.push_back(nullptr);
execvp("groups", arg_ptr_vec.data());
The args parameter to execlp is defined as "char *const argv[]", so I think you could do something like
const char *myArgs[3] = {"root, "sys", "bin"};
and then replace args.c_str() with myArgs.
I should admit to having zero experience of writing software for Ubuntu - this is what I would try next were I trying to get execlp to work.
EDIT: This is wrong - I had got mixed up and was looking at execv(), bobah and sleepy42 seem to have got it.

C++: How to escape user input for safe system calls?

On a Linux platform, I have C++ code that goes like this:
// ...
std::string myDir;
myDir = argv[1]; // myDir is initialized using user input from the command line.
std::string command;
command = "mkdir " + myDir;
if (system(command.c_str()) != 0) {
return 1;
}
// continue....
Is passing user input to a system() call safe at all?
Should the user input be escaped / sanitized?
How?
How could the above code be exploited for malicious purposes?
Thanks.
Just don't use system. Prefer execl.
execl ("/bin/mkdir", "mkdir", myDir, (char *)0);
That way, myDir is always passed as a single argument to mkdir, and the shell isn't involved. Note that you need to fork if you use this method.
But if this is not just an example, you should use the mkdir C function:
mkdir(myDir, someMode);
Using system() call with command line parameters without sanitizing the input can be highly insecure.
The potential security threat could be a user passing the following as directory name
somedir ; rm -rf /
To prevent this , use a mixture of the following
use getopt to ensure your input is
sanitized
sanitize the input
use execl instead of system to execute
the command
The best option would be to use all three
Further to Matthew's answer, don't spawn a shell process unless you absolutely need it. If you use a fork/execl combination, individual parameters will never be parsed so don't need to be escaped. Beware of null characters however which will still prematurely terminate the parameter (this is not a security problem in some cases).
I assume mkdir is just an example, as mkdir can trivially be called from C++ much more easily than these subprocess suggestions.
Reviving this ancient question as I ran into the same problem and the top answers, based on fork() + execl(), weren't working for me. (They create a separate process, whereas I wanted to use async to launch the command in a thread and have the system call stay in-process to share state more easily.) So I'll give an alternative solution.
It's not usually safe to pass user input as-is, especially if the utility is designed to be sudo'd; in order to sanitize it, instead of composing the string to be executed yourself, use environment variables, which the shell has built-in escape mechanisms for.
For your example:
// ...
std::string myDir;
myDir = argv[1]; // myDir is initialized using user input from the command line.
setenv("MY_DIR", myDir, 1);
if (system("mkdir \"${MY_DIR}\"") != 0) {
return 1;
}
// continue....

Executing a command from C++, What is expected in argv[0]?

I am using execv() to run commands from /bin/ such as 'ls', 'pwd', 'echo' from my c++ program, and I am wondering what value I should provide in argv[0];
const char * path = getPath();
char ** argv = getArgs();
execv(path,argv);
argv[0] is supposed to be the program name. It's passed to the program's main function. Some programs differentiate their behavior depending on what string argv[0] is. For example the GNU bash shell will disable some of its features if called using sh instead of bash. Best give it the same value that you pass to path.
In linux, argv[0] is the process name displayed by the top utility (which it probably gets from reading entries in /proc/)
argv[0] should be the full path of the command that you want to run.
I know that this is not the answer you're looking for but is there a specific reason why you're doing this? The reason I ask is that most if not all of the actions people normally run with either system() or execv() are available in libraries on either Windows or Unix and are safer, faster and less likely to suffer from circumstantial errors. By that I mean, for example, when the PATH changes and suddenly your code stops working.
If you're passing in a string, either in whole or in part, and running it then you also leave yourself open to a user gaining access to the system by entering a command that could be damaging. E.g. imagine you've implemented a file search using find /home -name and your user types in:
"%" -exec rm {} \;
Ouch!