2

I'm writing a PHP library that will need to reach out to the system and access a command line program that doesn't have a PHP interface (or PHP library). As such, I was wondering what is the best (and the safest way) to access the system to retrieve output from a CLI program? I've taken a look at both system() and exec(), but still not sure which is the best to use in a situation like this.

The library will get a string of user-passed text, and transmit it to the command line, retrieving back another string of text. Obviously, with passing user-provided data to the CLI, I will be doing a verification to ensure that no executable data can be passed.

coryb
  • 238
  • 3
  • 14
  • Yes, no, maybe? If not affirmative, clarification on the results you expect? – L0j1k Nov 10 '12 at 03:50
  • Could you please explain your decision to use the answer by Alix? I am curious to know how your research/programming has developed, and why you chose this method. Thank you. – L0j1k Nov 17 '12 at 07:09
  • @L0j1k: I'm not following... – Alix Axel Nov 17 '12 at 21:06
  • Strange. Yesterday it showed that coryb had accepted the answer by Alix, and I was curious what led him to his solution (because I'm interested in what finally he chooses here). – L0j1k Nov 18 '12 at 01:02
  • I am still looking through each solution, and trying them out to see which fits my particular situation. I'll update here when I figure out which I'll go with. – coryb Nov 18 '12 at 02:38
  • Cool, I really don't care about the points, I am just curious which method ends up working best. – L0j1k Nov 18 '12 at 02:40
  • 1
    @L0j1k: Sorry to notify you too, but you seemed to be interested as well. I posted a more in-depth rationale in my answer/suggestion. – Alix Axel Nov 18 '12 at 16:18
  • 1
    No apologies needed! I am interested in the result of this research and hope that coryb posts his final solution as a comment of whichever method he uses, or even posts his own answer. I'm no rep whore, I'm just very interested in him sharing whatever wisdom he learns from this problem and solutions. :) – L0j1k Nov 19 '12 at 02:23
  • I have actually decided to go with temp files and using escapeshellcmd + exec since I'm controlling what command is being run in the CLI, plus the input text is being written to a file first. I figured this would be the safest way to run the `liblouisxml`(or `xml2brl`) CLI command in PHP. – coryb Dec 13 '12 at 01:17

2 Answers2

2

I would suggest shell_exec() together with escapeshellcmd() and escapeshellarg().


To clarify (I was on the go when I first posted this answer): The right way to secure a shell command is:

$exe = 'cat';
$args = array('/etc/passwd');

$args = array_map('escapeshellarg', $args);

$escaped = escapeshellcmd($exe . ' ' . implode(' ', $args));

Here's a legitimate demo (and a nefarious demo as well) of the above code.

The above is just a dummy example, of course. But the main idea is that you apply escapeshellarg() to each argument and then call escapeshellcmd() on the whole command string (including the path to the executable and the previously escaped arguments). This is critical in arbitrary commands.

Note: By secure, I mean making it impossible to perform shell injection attacks by escaping characters that have special meaning like >, <, &&, | and more (see the Wikipedia link) while at the same time properly quoting spaces and other characters that may also have special interpretations by the shell.

With that aside, if you're already white-listing all the commands allowed, you already have the best possible security and you don't need the above functions (althought it doesn't hurt to use them anyway).


Regarding the actual calling function, they all pretty much do the same thing with a few quirks. Personally, I prefer shell_exec() since its return value is more versatile (from this page):

  • exec(): returns the last line of output from the command and flushes nothing.
  • shell_exec(): returns the entire output from the command and flushes nothing.
  • system(): returns the last line of output from the command and tries to flush the output buffer after each line of the output as it goes.
  • passthru(): returns nothing and passes the resulting output without interference to the browser, especially useful when the output is in binary format.

Except from the system() exit return code, you can mimic the behavior of all the other functions with the return value of shell_exec(). However, the inverse it's either harder to do, or just not possible.

I hope this clears things up for you.

Community
  • 1
  • 1
Alix Axel
  • 151,645
  • 95
  • 393
  • 500
  • Why would you recommend these? What about these functions are better than just using the exec() or system() functions? – coryb Nov 11 '12 at 03:48
  • @coryb: See http://stackoverflow.com/q/1881582/89771 and http://stackoverflow.com/a/10828773/89771. – Alix Axel Nov 11 '12 at 14:50
  • In which cases is it __necessary__ to use `escapeshellcmd` on the whole command, when arguments have already been escaped with `escapeshellarg`? – Alf Eaton Sep 17 '13 at 17:05
  • @AlfEaton: When the command is *arbitrary* (i.e.: came from the user input - still dangerous if you don't whitelist it) or contains uncommon characters. – Alix Axel Sep 18 '13 at 03:32
  • @AlixAxel in that case, it would only be necessary to escape the command name ($exe in this answer), rather than the whole command? – Alf Eaton Sep 18 '13 at 14:17
  • @AlfEaton: I'm not a authoritative source on this subject, but from what I gathered from the manual and by my own experimentation, it goes like this: `escapeshellarg` is the only secure method of escaping command **arguments**, you should use it whenever arguments come into play. `escapeshellcmd` will also try to escape arguments (although, not as reliably/securely; mostly because it's hard to know if `-a /output path` is `-a "/output path"` or `-a "/output" "path"`) and will also escape the **binary** in a different way (i.e.: no quoting). – Alix Axel Sep 18 '13 at 15:06
  • Essentially, is this sufficient to be secure?: `$escaped = escapeshellcmd($exe) . ' ' . implode(' ', array_map('escapeshellarg', $args));` – Alf Eaton Sep 19 '13 at 15:19
  • @AlfEaton: Should be, but PHP manual says: `$escaped = escapeshellcmd($exe . ' ' . implode(' ', array_map('escapeshellarg', $args)));` – Alix Axel Sep 20 '13 at 06:30
1

Ideally, you would use passthru() from a pre-defined list of possible inputs (so that if user input == 'operation_a' you can { passthru('operation_a'); } without worrying about sanitizing input). Otherwise, use passthru() with some serious sanitation of input. passthru() allows you to capture the output of the command and pass the whole lump back to the browser. This function is particularly useful if you are expecting binary output (like from image generation, &c.).

L0j1k
  • 12,255
  • 7
  • 53
  • 65
  • Thanks for that info I'll look into using that. I'm using the `xml2brl` program included with the `LibLouis` framework. What I'm doing is passing plain ASCII text to the program, and getting back Braille ASCII-formatted text. I'm going to replicate all of the options available in the program, except executable from the new PHP library I'm building. Is there a way to sandbox the input so that should something go wrong, it wouldn't harm the system? – coryb Nov 10 '12 at 03:59
  • You could virtualize the platform you're running the command on (think Linux under Virtualbox running the PHP page that executes the command), but at the end of the day, you just do not want to be the lowest-hanging fruit compared to your neighbors. – L0j1k Nov 10 '12 at 04:02