|
|
@@ -0,0 +1,153 @@
|
|
|
+<?php
|
|
|
+/**
|
|
|
+ * Zend Framework
|
|
|
+ *
|
|
|
+ * LICENSE
|
|
|
+ *
|
|
|
+ * This source file is subject to the new BSD license that is bundled
|
|
|
+ * with this package in the file LICENSE.txt.
|
|
|
+ * It is also available through the world-wide-web at this URL:
|
|
|
+ * http://framework.zend.com/license/new-bsd
|
|
|
+ * If you did not receive a copy of the license and are unable to
|
|
|
+ * obtain it through the world-wide-web, please send an email
|
|
|
+ * to license@zend.com so we can send you a copy immediately.
|
|
|
+ *
|
|
|
+ * @category Zend
|
|
|
+ * @package Zend_Xml
|
|
|
+ * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
|
|
|
+ * @license http://framework.zend.com/license/new-bsd New BSD License
|
|
|
+ * @version $Id$
|
|
|
+ */
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * @category Zend
|
|
|
+ * @package Zend_Xml_SecurityScan
|
|
|
+ * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
|
|
|
+ * @license http://framework.zend.com/license/new-bsd New BSD License
|
|
|
+ */
|
|
|
+class Zend_Xml_Security
|
|
|
+{
|
|
|
+ const ENTITY_DETECT = 'Detected use of ENTITY in XML, disabled to prevent XXE/XEE attacks';
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Heuristic scan to detect entity in XML
|
|
|
+ *
|
|
|
+ * @param string $xml
|
|
|
+ * @throws Zend_Xml_Exception
|
|
|
+ */
|
|
|
+ protected static function heuristicScan($xml)
|
|
|
+ {
|
|
|
+ if (strpos($xml, '<!ENTITY') !== false) {
|
|
|
+ require_once 'Exception.php';
|
|
|
+ throw new Zend_Xml_Exception(self::ENTITY_DETECT);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Scan XML string for potential XXE and XEE attacks
|
|
|
+ *
|
|
|
+ * @param string $xml
|
|
|
+ * @param DomDocument $dom
|
|
|
+ * @throws Zend_Xml_Exception
|
|
|
+ * @return SimpleXMLElement|DomDocument|boolean
|
|
|
+ */
|
|
|
+ public static function scan($xml, DOMDocument $dom = null)
|
|
|
+ {
|
|
|
+ // If running with PHP-FPM we perform an heuristic scan
|
|
|
+ // We cannot use libxml_disable_entity_loader because of this bug
|
|
|
+ // @see https://bugs.php.net/bug.php?id=64938
|
|
|
+ if (self::isPhpFpm()) {
|
|
|
+ self::heuristicScan($xml);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (null === $dom) {
|
|
|
+ $simpleXml = true;
|
|
|
+ $dom = new DOMDocument();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!self::isPhpFpm()) {
|
|
|
+ $loadEntities = libxml_disable_entity_loader(true);
|
|
|
+ $useInternalXmlErrors = libxml_use_internal_errors(true);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Load XML with network access disabled (LIBXML_NONET)
|
|
|
+ // error disabled with @ for PHP-FPM scenario
|
|
|
+ set_error_handler(function ($errno, $errstr) {
|
|
|
+ if (substr_count($errstr, 'DOMDocument::loadXML()') > 0) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }, E_WARNING);
|
|
|
+ $result = $dom->loadXml($xml, LIBXML_NONET);
|
|
|
+ restore_error_handler();
|
|
|
+
|
|
|
+ if (!$result) {
|
|
|
+ // Entity load to previous setting
|
|
|
+ if (!self::isPhpFpm()) {
|
|
|
+ libxml_disable_entity_loader($loadEntities);
|
|
|
+ libxml_use_internal_errors($useInternalXmlErrors);
|
|
|
+ }
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Entity load to previous setting
|
|
|
+ if (!self::isPhpFpm()) {
|
|
|
+ libxml_disable_entity_loader($loadEntities);
|
|
|
+ libxml_use_internal_errors($useInternalXmlErrors);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isset($simpleXml)) {
|
|
|
+ $result = simplexml_import_dom($dom);
|
|
|
+ if (!$result instanceof SimpleXMLElement) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return $result;
|
|
|
+ }
|
|
|
+ return $dom;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Scan XML file for potential XXE/XEE attacks
|
|
|
+ *
|
|
|
+ * @param string $file
|
|
|
+ * @param DOMDocument $dom
|
|
|
+ * @throws Zend_Xml_Exception
|
|
|
+ * @return SimpleXMLElement|DomDocument
|
|
|
+ */
|
|
|
+ public static function scanFile($file, DOMDocument $dom = null)
|
|
|
+ {
|
|
|
+ if (!file_exists($file)) {
|
|
|
+ require_once 'Exception.php';
|
|
|
+ throw new Zend_Xml_Exception(
|
|
|
+ "The file $file specified doesn't exist"
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return self::scan(file_get_contents($file), $dom);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Return true if PHP is running with PHP-FPM
|
|
|
+ *
|
|
|
+ * @return boolean
|
|
|
+ */
|
|
|
+ public static function isPhpFpm()
|
|
|
+ {
|
|
|
+ if (substr(php_sapi_name(), 0, 3) === 'fpm') {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+}
|