Invoking Executables from PHP, Safely

PHP is a versatile language, but often we face the need to execute an external program to accomplish a task, not easily done using PHP.

To execute external commands, PHP provides with few built-in functions: exec, shell_exec, system and proc_open. However, the former three, though easy to use, are vulnereble to shell injection, much like SQL injection, especially if the command contains some user input. To guard against shell injection, PHP provides with escapeshellcmd and escapeshellargs, but they are grossly inadequate in functionality, do not perform proper escaping for shells other than bash and are ridden with CVEs.

An example of shell injection

And if I sent a request like

// $domain = 'example.com && echo "malicious code"' 
GET /whois.php?domain=example.com%20%26%26%20echo%20%22malicious%20code%22

The output would be:

//... whois result 
malicious code

Pretty dangerous isn’t ?

Executing external executables, safely

Solution 1: Use PHP inbuilt escapeshellcmd() / escapeshellargs()

Modifying the PHP script a little.

But it had some few critical CVEs associated, and some inherent flaws, such as:

The later 2 are CVEs that impacted several popular PHP frameworks and CMSs used on the internet, directly due to the consequence of escapeshellcmd(), which means a significant portion of the internet was vulnereble.

Definitely, not recommended

Solution 2: Classic UNIX fork/exec using pnctl_fork() and pnctl_exec()

The two functions are the part of PHP pnctl extension (POSIX Extensions). They behave like the normal UNIX fork() and exec() functions. They do not go through a shell, and thus are immune to shell injection attacks.

While this is a secure and good way to execute processes, it has some drawbacks:

  • You have no straightforward way to get any output from the executed command. While, you can output to a file or socket and retrieve data, it’s bit cumbersome.
  • It depends on the pnctl PECL extension and is not available out-of-the-box and most webhosts refuse to enable it.
  • This does not work on non POSIX compilant OSes, as it depends on the availability on fork syscall, only present in POSIX compilant OSes.
  • If not properly coded, the fork/exec may lead to [fork bombs] which may deplete system resources.

Solution 3: PHP proc_open

This is by far the most flexible inbuilt function for executing executables and scripts. It also provides means to control various streams and file descriptors of the child.

Let’s reimplement our whois script using proc_open

It is clearly more flexible, but care must be taken in windows to pass the option to bypass the shell (as it uses cmd to start the process).

However, it is cumbersome and involves a great deal of low-level access to a process.

Solution 4: The Symfony Process component

Symfony is a set of reusable PHP components and a PHP framework for web projects.”

The Symfony Process component provides a high level API to execute commands in sub-processes. Even though you may be using a different framework or no framework at all (seriously, you should), you can easily use this component through composer (symfony/process).

Apart from usual convenient methods for retrieving and streaming outputs from processes, it provides an object oriented access to processes and wraps unhelpful errors with exceptions. Also, it is even more flexible than the proc_open. (Internally, it builds on the same, with secure defaults)

Here is the code:

Simple. It also has more goodies like timeout, streaming the output line by line.

Background

In my GSoC project for LibreCores.org, I had to invoke few git processses with known inputs, to extract info about commits and contributors.

In coming days, I will write about message queues and other interesting components that I am busy building these days.

Resources

PHP Manual

shell_exec, escapeshellcmd, escapeshellarg, pnctl_fork, pnctl_exec, pnctl_waitpid, proc_open, proc_close

Symfony documentation

Other resources

Originally published at amitosh.in on June 7, 2017.

Product Engineer @ Gojek. Open-source contributor. Find me at https://amitosh.in/

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store