Expression Engine 3.4.2: Code Reuse Attack

Expression Engine

Expression Engine is a popular general purpose content management system that is used by thousands of individuals, organizations, and companies around the world. The open-source version has about 250,000 lines of code and is a medium-sized web application. In this post, we will examine a code reuse vulnerability that leads to remote code execution. This vulnerability type allows an attacker to partly control the applications logic and to chain existing code fragements.

RIPS Analysis

The analysis with RIPS took about 4 minutes. Overall, the code of Expression Engine seems to be very robust. Still our analysis results point out some vulnerabilities. RIPS detected mainly possibilities for a malicious user to embed HTML and JavaScript code via the administration interface into the appearance of Expression Engine. Since static analysis cannot reason about the application’s logic, RIPS does not differentiate between cases where a user is able to inject JavaScript code by design and cases where this is a security vulnerability.

The truncated analysis results are available in our RIPS demo application. Please note that we limited the results to the issues described in this post in order to ensure a fix is available.

See RIPS report

Case Study

Example 1: PHP Object Injection

The most serious issue reported is a PHP object injection vulnerability. As the name suggests, this vulnerability allows an attacker to inject objects of arbitrary class type. The issue resides in the administration control panel but it can be triggered through cross-site request forgery (CSRF). An attacker can create a malicious website and trick an administrator into visiting the page which then fires the attack against his administration panel in the background. The following code contains the PHP object injection vulnerability.


public function create() {
    $values = array(
        'name' => ee()->input->get('name'),
        'url'  => ee('CP/URL')->decodeUrl(ee()->input->get('url'))
    $this->form($vars, $values);


public function decodeUrl($url) {

Here, the GET parameter url is passed to the sensitive sink unserialize() without any sanitization. As already explained in our Coppermine post, older installations of PHP (before 5.5.37, 5.6.x before 5.6.23, and 7.x before 7.0.8) are vulnerable to arbitrary code execution due to a security issue in the unserialize() function itself.

But even with the latest and greatest PHP version, this object injection vulnerability poses security risks because it can be used to trigger other vulnerabilities in the code base that would not be reachable otherwise. An attacker can inject an object and control its class type and its properties by modifying the serialized string that is used to re-create the object. For example, the following string invokes an object of class Link when deserialized: O:4:"Link":{}

Example 2: Chain for XSS to RCE

In order to exploit the PHP object injection vulnerability, an attacker can utilize an attack technique called property oriented programming. It allows the attacker to chain code fragments of existing classes in the code base and to jump from one method to another until a sensitive code is reached that triggers another vulnerability.

Our starting point is the magic method __toString() that is automatically called when an object is type-casted into a string. When we inject an object of the class Link, the object is concatenated into a template and the __toString() method of Link is automatically invoked. This is our initial gadget to takeover the control flow of the application. In the __toString() method, the method render() is called.


class Link {
    public function __toString() {
         return $this->render();

class Link {
    public function render() {
        $url = $this->filepicker->getUrl();

Since not only the class name, but also the properties of the injected object are within the control of an attacker, the property filepicker can be chosen arbitrarily. This enables to jump to any method named getUrl() in line 37. Placing an object of class FilePicker into this property then calls the method getUrl() of this class.


class FilePicker {
    public function getUrl() {
         $qs = array('directories' => $this->directories);
         return $this->url->make(static::CONTROLLER, $qs);

From here, we continue our journey through the url property. Again, any object of any class can be injected into this property and defer the control flow to arbitrary make() methods. An interesting make() method is found in the class InjectionBindingDecorator.


class InjectionBindingDecorator implements ServiceProvider {
    public function make() {
         $arguments = func_get_args();
         $name = array_shift($arguments);
         if (isset($this->bindings[$name])) {
             $object = $this->bindings[$name];
             if ($object instanceof Closure) {
                 array_unshift($arguments, $this);
                 return call_user_func_array($object, $arguments);
             return $object;
         array_unshift($arguments, $name);
         array_unshift($arguments, $this);
         return call_user_func_array(
             array($this->delegate, 'make'),

At first sight, it is possible to execute arbitrary functions by injecting into the first argument of call_user_func_array(). However, this is prevented in line 72 that ensures that the argument is a Closure which cannot be injected through unserialize() and, thus, line 74 cannot be reached.

Another call to call_user_func_array() is found in line 80 though. It is not possible to directly execute PHP code here, but at least we can control the class name that is taken from the property delegate. Based on this property, we can call arbitrary make() methods again, but no other interesting make() methods were found in the code base. Interestingly, when a faulty class name is injected, the error page of Expression Engine is shown and the name of the class is printed without any sanitization. This at least leads to a cross-site scripting vulnerability.

Cross-site scripting in Expression Engine can lead to PHP code evaluation though because it can be abused to enable the PHP parser for a template and then to write arbitrary code into it. For this purpose, an attacker can retrieve the secret token used for CSRF protection and then submit requests in the name of an administrator. You can see a video demonstration of this step in our FreePBX post.

Time Line

2016/09/14First contact with vendor
2016/09/14Vendor responds with fix
2016/09/20Vendor releases fixed version


The code of Expression Engine is well written and the architecture sophisticated. But as demonstrated in this post, one single vulnerability is enough to endanger your complete system. A simple code slip can set off an avalanche and lead to arbitrary code execution for attackers. The team at EllisLab takes security very seriously, promptly responded to our reporting, and published a fix only 6 days later. Kudos and thank you for the professional collaboration.

To prevent PHP object injection vulnerabilities, the deserialization of user-supplied data should be avoided in general. It is much safer to use similar functions such as json_encode()/json_decode(). It is also possible to protect the input with a keyed-hash message authentication code (HMAC) in order to prevent unauthorized modifications.

Follow us on Twitter to be notified when the next gift of our advent calendar is opened!

APAV Time Table

24 Dec 2016Johannes DahseWhat we learned from our Advent Calendar
23 Dec 2016Hendrik Buchwalde107 2.1.2: SQL Injection through Object Injection
22 Dec 2016Daniel PeerenSecurity Compliance with Static Code Analysis
21 Dec 2016Martin BednorzAbanteCart 1.2.8 - Multiple SQL Injections
20 Dec 2016Martin BednorzKliqqi From Cross-Site Request Forgery to Code Execution
19 Dec 2016Robin PeraglieosClass 3.6.1: Remote Code Execution via Image File
18 Dec 2016Daniel PeerenContinuous Integration - Jenkins at your service
17 Dec 2016Johannes DahseOpenConf 5.30 - Multi-Step Remote Command Execution
16 Dec 2016Robin PeraglieRedaxo 5.2.0: Remote Code Execution via CSRF
15 Dec 2016Dennis DeteringGuest Post: Vtiger 6.5.0 - SQL Injection
14 Dec 2016Hendrik BuchwaldThe State of Wordpress Security
13 Dec 2016Johannes DahsephpBB 2.0.23 - From Variable Tampering to SQL Injection
12 Dec 2016Martin BednorzTeampass Unauthenticated SQL Injection
11 Dec 2016Daniel PeerenRescanning Applications with RIPS
10 Dec 2016Hendrik BuchwaldNon-Exploitable Security Issues
9 Dec 2016Hendrik BuchwaldPrecurio 2.1: Remote Command Execution via Xinha Plugin
8 Dec 2016Martin BednorzPHPKit 1.6.6: Code Execution for Privileged Users
7 Dec 2016Hendrik BuchwaldSerendipity 2.0.3: From File Upload to Code Execution
6 Dec 2016Robin PeraglieRoundcube 1.2.2: Command Execution via Email
5 Dec 2016Hendrik BuchwaldExpression Engine 3.4.2: Code Reuse Attack
4 Dec 2016Johannes DahseIntroducing the RIPS analysis engine
3 Dec 2016Martin BednorzeFront 3.6.15: Steal your professors password
2 Dec 2016Martin BednorzCoppermine 1.5.42: Second-Order Command Execution
1 Dec 2016Hendrik BuchwaldFreePBX 13: From Cross-Site Scripting to Remote Command Execution
25 Nov 2016Martin BednorzAnnouncing the Advent of PHP Application Vulnerabilities

Disclaimer: The information provided here is for educational purposes only. It is your responsibility to obey all applicable local, state and federal laws. RIPS Technologies GmbH assumes no liability and is not responsible for any misuse or damages caused by direct or indirect use of the information provided.

Tags: hendrik buchwald, php, security, expression engine, apav, php object injection, property oriented programming, cross-site scripting, code injection,

Author: Hendrik Buchwald

CISO, Co-Founder

Hendrik graduated in computer science at the Ruhr-University Bochum and is a professional software engineer. In addition to designing and building complex systems, he particularly enjoys breaking and securing web applications. He is an experienced security consultant and the lead developer of the open source web application firewall Shadow Daemon.


comments powered by Disqus