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:
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
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"'
The output would be:
//... whois result
Pretty dangerous isn’t ?
Executing external executables, safely
Solution 1: Use PHP inbuilt
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
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
pnctlPECL 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
forksyscall, 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
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
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 (
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.
In coming days, I will write about message queues and other interesting components that I am busy building these days.
Originally published at amitosh.in on June 7, 2017.