1

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?

  • 5
    Everything that `system()` does shells to the OS. That's what `system()` means. – Ken White Jan 03 '18 at 01:21
  • 1
    Those are internal commands of `cmd.exe` interpreter. So, first you have to establish, whether at all `cmd.exe` is invoked by the `system()` call, and if yes, whether it is willing to evaluate the internal commands with whatever command line flags passed to it by the MS' C runtime. – oakad Jan 03 '18 at 01:49
  • Thank you, I edited the question to add clarification – William FitzPatrick Jan 03 '18 at 23:31
  • Is this question intended to be Windows-specific? – Keith Thompson Jan 04 '18 at 02:04
  • No, is there a better way for me to word this to make that clarification? – William FitzPatrick Jan 04 '18 at 02:07
  • The flushing is unrelated to the other risk. Flushing the streams just means that the input and output will happen in the expected order. The risks of `system` are that it can call programs you don’t expect, potentially doing *anything at all* (controllable by a potential attacker, not because of UB) depending on what those programs do. – Daniel H Jan 04 '18 at 16:14

2 Answers2

2

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):

  1. Do some sanity checks on parameters
  2. 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.
  3. In a loop: obtain the next path element using `_getpath()
    1. Append the file name to the path element
    2. Pass the resulted path to spwanvpe, check if it was successful

That's it. No special tricks/checks involved.

oakad
  • 6,945
  • 1
  • 22
  • 31
  • 1
    This doesn't answer the question. It implies, though, that if anybody can put a file named `CMD.exe` earlier on the `PATH` than the real one is, you're calling their code. – Daniel H Jan 03 '18 at 02:01
  • (A brief test says that the current directory is checked before `C:\Windows\system32`, but that might only be when you're already in the shell. Which is still a huge problem unless you're sure what you call is internal instead of another program.) – Daniel H Jan 03 '18 at 02:04
  • Check the update - I copy pasted the relevant bit from MSVCRT source (hope they won't mind). – oakad Jan 03 '18 at 02:12
  • That really doesn't help. Even if we had all the information (how does `_tspawnvpe` search `%PATH%`?), it wouldn't be a security analysis. Does that make `system("pause");` safe? Copying source code does not actually tell you that, because people can mis-read source code or fail to think of attacks. Before this question I would have assumed it always called the *right* `cmd.exe` and it was only when the command was external that there could be issues. Even with the source in front of me I might not have thought of potential attacks based on that. – Daniel H Jan 03 '18 at 15:12
  • I will upvote you as you put sincere effort into this answer, but I cant checkmark this as it implies that there are potential safe and unsafe uses for system calls. Without giving an explicit answer. Furthermore it omits other potential risks with system calls, brought up by Daniel H. I will leave this question open until a more exhaustive answer comes – William FitzPatrick Jan 03 '18 at 23:23
  • @DanielH There's no such thing as "right" cmd.exe. There's a value of COMSPEC environment variable and there's a PATH environment variable parsed using a standard `_getpath` helper function. That's it (and the docs say the same). No amount of magical thinking on your behalf is going to change that. :-) – oakad Jan 04 '18 at 02:00
  • @oakad So does that mean system calls shelled to CMD prompt are safe? If so, I would accept your answer if you wrote something along the lines of: "if you use X commands with Y before them, then they pose no security risk" – William FitzPatrick Jan 04 '18 at 02:13
  • @oakad I would have assumed, before looking at those docs, that it was hardcoded as something like `C:\Windows\System32\cmd.exe`; I'm fairly sure the Linux equivalent uses the full path, but I might be wrong. In any case, what I meant by "the *right* `cmd.exe`" was "the one that's actually part of my system". Now my understanding is "If your current directory is writable by an untrustworthy source, they can put their own program called `cmd.exe` there, and you'll call that instead", which is a lot less safe. – Daniel H Jan 04 '18 at 04:11
2

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.

Joshua
  • 40,822
  • 8
  • 72
  • 132
  • So there is no way to call system on POSIX safely? – William FitzPatrick Jan 04 '18 at 02:38
  • @WilliamFitzPatrick: There is. Don't call it from programs that are suid or sgid-anything. – Joshua Jan 04 '18 at 03:33
  • @Joshua Or from any program that might have untrusted environment variables. Or with untrusted input strings. In general, sanitizing these things is probably harder than just crafting the right `exec` call. I doubt `sudo` has `system` calls in it at all (although now I'm curious and will check just in case). – Daniel H Jan 04 '18 at 04:14
  • Yeah, it looks like Sudo hasn't had `system` calls until April 2000, except in tests. I could have missed something, though. – Daniel H Jan 04 '18 at 04:46
  • 2
    @DanielH: You made my point. The experts who could sanitize everything so it's safe found it more expedient not to anyway. – Joshua Jan 04 '18 at 14:39
  • @Joshua Exactly; that’s why I was surprised when you implied `sudo` used it. I’m not sure it even *is* possible to sanitize without completely wiping environment variables, since `/bin/sh` could be linked to any one of several of other shells with different behaviors. Bash enters POSIX mode when called as `sh`, but I think it doesn’t do it right away. – Daniel H Jan 04 '18 at 17:32