|
|
@@ -28,8 +28,24 @@
|
|
|
*/
|
|
|
class Zend_Xml_Security
|
|
|
{
|
|
|
+ const ENTITY_DETECT = 'Detected use of ENTITY in XML, disabled to prevent XXE/XEE attacks';
|
|
|
+
|
|
|
/**
|
|
|
- * Scan XML string for potential XXE and 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
|
|
|
@@ -38,40 +54,63 @@ class Zend_Xml_Security
|
|
|
*/
|
|
|
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);
|
|
|
+ }
|
|
|
|
|
|
- // Disable entity load
|
|
|
- $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 (!$dom->loadXml($xml)) {
|
|
|
+ if (!$result) {
|
|
|
// Entity load to previous setting
|
|
|
- libxml_disable_entity_loader($loadEntities);
|
|
|
- libxml_use_internal_errors($useInternalXmlErrors);
|
|
|
+ if (!self::isPhpFpm()) {
|
|
|
+ libxml_disable_entity_loader($loadEntities);
|
|
|
+ libxml_use_internal_errors($useInternalXmlErrors);
|
|
|
+ }
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- // Scan for potential XEE attacks using Entity
|
|
|
- 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(
|
|
|
- 'Detected use of ENTITY_NODE in XML, disabled to prevent XEE attacks'
|
|
|
- );
|
|
|
+ // 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
|
|
|
- libxml_disable_entity_loader($loadEntities);
|
|
|
- libxml_use_internal_errors($useInternalXmlErrors);
|
|
|
+ if (!self::isPhpFpm()) {
|
|
|
+ libxml_disable_entity_loader($loadEntities);
|
|
|
+ libxml_use_internal_errors($useInternalXmlErrors);
|
|
|
+ }
|
|
|
|
|
|
if (isset($simpleXml)) {
|
|
|
- $result = simplexml_import_dom($dom);
|
|
|
+ $result = simplexml_import_dom($dom);
|
|
|
if (!$result instanceof SimpleXMLElement) {
|
|
|
return false;
|
|
|
}
|
|
|
@@ -98,4 +137,17 @@ class Zend_Xml_Security
|
|
|
}
|
|
|
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;
|
|
|
+ }
|
|
|
}
|