Exploit-DB #37765
Description: Zend Framework 2.4.2 - PHP FPM XML eXternal Entity Injection
Type: webapps
Platform: multiple
Date: 2015-08-13
Author: Dawid Golunski

Exploit:
=============================================
- Release date: 12.08.2015
- Discovered by: Dawid Golunski
- Severity: High
- CVE-ID: CVE-2015-5161
=============================================

 
I. VULNERABILITY
-------------------------

Zend Framework <= 2.4.2     XML eXternal Entity Injection (XXE) on PHP FPM
Zend Framework <= 1.12.13

 
II. BACKGROUND
-------------------------

- Zend Framework 

From http://framework.zend.com/about/ website:

"Zend Framework 2 is an open source framework for developing web applications 
and services using PHP 5.3+. Zend Framework 2 uses 100% object-oriented code and 
utilises most of the new features of PHP 5.3, namely namespaces, late static 
binding, lambda functions and closures.

Zend Framework 2 evolved from Zend Framework 1, a successful PHP framework with
over 15 million downloads."


- PHP FPM

http://php.net/manual/en/install.fpm.php

"FPM (FastCGI Process Manager) is an alternative PHP FastCGI implementation with
 some additional features (mostly) useful for heavy-loaded sites."

Starting from release 5.3.3 in early 2010, PHP merged the php-fpm fastCGI 
process manager into its codebase. However PHP-FPM was available earlier as a 
separate project (http://php-fpm.org/).

 
III. INTRODUCTION
-------------------------

The XML standard defines a concept of external entites. 
XXE (XML eXternal Entity) attack is an attack on an application that parses XML 
input from untrusted sources using incorrectly configured XML parser. 
The application may be forced to open arbitrary files and/or network resources.
Exploiting XXE issues on PHP applications may also lead to denial of service or
in some cases (for example, when an 'expect' PHP module is installed) lead to 
command execution.

An independent security reserach of Zend Framework revealed that it is 
possible to bypass XXE security controls within the framework in case 
the PHP application using Zend XML related classes (e.g Zend_XmlRpc_Server, 
Zend_Feed, Zend_Config_Xml etc.) from Zend Framework is served via PHP FPM.
Bypassing the controls may allow XXE attacks and lead to the aforementioned 
exploitation possibilities on systems where the XML parser is set to resolve 
entities.

IV. DESCRIPTION
-------------------------
 
The security controls within the Zend Framework mitigate the XXE attack vectors
by first calling libxml_disable_entity_loader(), and then looping 
through the DOMDocument nodes testing if any is of type: XML_DOCUMENT_TYPE_NODE
If so, an exception is raised and PHP script execution is halted.

These controls have been included in the scan() function of a Zend_Xml_Security 
class located in the following paths depending on the code branch of Zend 
Framework:

ZendFramework-1.12.13/library/Zend/Xml/Security.php

ZendFramework-2.4.2/library/ZendXml/Security.php


In case of the latest version of ZendFramework-1.12.13, 
the relevant code blocks from the scan() function look as follows:


---[library/Zend/Xml/Security.php ]---

    public static function scan($xml, DOMDocument $dom = null)
    {
        if (self::isPhpFpm()) {
            self::heuristicScan($xml);
        }

        if (!self::isPhpFpm()) {
            $loadEntities = libxml_disable_entity_loader(true);
            $useInternalXmlErrors = libxml_use_internal_errors(true);
        }

        // Load XML with network access disabled (LIBXML_NONET)
        $result = $dom->loadXml($xml, LIBXML_NONET);
        restore_error_handler();

        if (!self::isPhpFpm()) {
            libxml_disable_entity_loader($loadEntities);
            libxml_use_internal_errors($useInternalXmlErrors);
        }

        if (!$result) {
            return false;
        }

        // Scan for potential XEE attacks using ENTITY, if not PHP-FPM
        if (!self::isPhpFpm()) {
            foreach ($dom->childNodes as $child) {
                if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
                    if ($child->entities->length > 0) {
                        require_once 'Exception.php';
                        throw new Zend_Xml_Exception(self::ENTITY_DETECT);
                    }
                }
            }
        }

        if (isset($simpleXml)) {
            $result = simplexml_import_dom($dom);
            if (!$result instanceof SimpleXMLElement) {
                return false;
            }
            return $result;
        }
        return $dom;


--------------------------------------


As we can see from the code, the application disables the entity loader
(via libxml_disable_entity_loader), it also disables network access 
(LIBXML_NONET), and it additionally scans provided XML for the presence of XML
entities to prevent potential entity expansion attacks.  
The code succesfully prevents most XXE attacks. 

However, as the PHP libxml_disable_entity_loader() function was reported not
thread safe (the entity loader setting could potentially get overwritten 
between hits in FPM processes), Zend Framework does not use it when the 
application is hosted in a PHP-FPM environment. Instead, another approach is 
taken to prevent the XXE attacks.

In the code above we see the check !self::isPhpFpm() which determines the type
of interface between web server and PHP (through the php_sapi_name() function). 
If the SAPI is FPM-CGI (i.e. PHP-FPM) the following heuristicScan function gets 
executed: 

---[library/Zend/Xml/Security.php ]---

    protected static function heuristicScan($xml)
    {
        if (strpos($xml, '<!ENTITY') !== false) {
            require_once 'Exception.php';
            throw new Zend_Xml_Exception(self::ENTITY_DETECT);
        }
    }

--------------------------------------

It validates provided XML by searching for any entity declaration. It throws an
exception if it finds one. 
Although this check cannot be bypassed by simpl