From 6c905411bac66caad5e220a70e3d561b8a648507 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sat, 14 Jul 2012 11:53:37 -0400 Subject: [PATCH] Fix XML decoding attack via external entities. --- lib/Cake/Test/Case/Utility/XmlTest.php | 18 ++++++++++ lib/Cake/Utility/Xml.php | 46 ++++++++++++++++++-------- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/lib/Cake/Test/Case/Utility/XmlTest.php b/lib/Cake/Test/Case/Utility/XmlTest.php index a2693713a..23517314d 100644 --- a/lib/Cake/Test/Case/Utility/XmlTest.php +++ b/lib/Cake/Test/Case/Utility/XmlTest.php @@ -1039,4 +1039,22 @@ XML; $this->assertContains('mark & mark', $result); } +/** + * Test that entity loading is disabled by default. + * + * @return void + */ + public function testNoEntityLoading() { + $file = CAKE . 'VERSION.txt'; + $xml = <<]> + + &payload; + +XML; + $result = Xml::build($xml); + $this->assertEquals('', (string)$result->xxe); + } + } diff --git a/lib/Cake/Utility/Xml.php b/lib/Cake/Utility/Xml.php index eb5c6d0f8..f8662b282 100644 --- a/lib/Cake/Utility/Xml.php +++ b/lib/Cake/Utility/Xml.php @@ -74,6 +74,8 @@ class Xml { * ### Options * * - `return` Can be 'simplexml' to return object of SimpleXMLElement or 'domdocument' to return DOMDocument. + * - `loadEntities` Defaults to false. Set to true to enable loading of ` (string)$options); } $defaults = array( - 'return' => 'simplexml' + 'return' => 'simplexml', + 'loadEntities' => false, ); $options = array_merge($defaults, $options); if (is_array($input) || is_object($input)) { return self::fromArray((array)$input, $options); } elseif (strpos($input, '<') !== false) { - if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') { - return new SimpleXMLElement($input, LIBXML_NOCDATA); - } - $dom = new DOMDocument(); - $dom->loadXML($input); - return $dom; + return self::_loadXml($input, $options); } elseif (file_exists($input) || strpos($input, 'http://') === 0 || strpos($input, 'https://') === 0) { - if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') { - return new SimpleXMLElement($input, LIBXML_NOCDATA, true); - } - $dom = new DOMDocument(); - $dom->load($input); - return $dom; + $input = file_get_contents($input); + return self::_loadXml($input, $options); } elseif (!is_string($input)) { throw new XmlException(__d('cake_dev', 'Invalid input.')); } throw new XmlException(__d('cake_dev', 'XML cannot be read.')); } +/** + * Parse the input data and create either a SimpleXmlElement object or a DOMDocument. + * + * @param string $input The input to load. + * @param array $options The options to use. See Xml::build() + * @return SimpleXmlElement|DOMDocument. + */ + protected static function _loadXml($input, $options) { + $hasDisable = function_exists('libxml_disable_entity_loader'); + $internalErrors = libxml_use_internal_errors(true); + if ($hasDisable && !$options['loadEntities']) { + libxml_disable_entity_loader(true); + } + if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') { + $xml = new SimpleXMLElement($input, LIBXML_NOCDATA); + } else { + $xml = new DOMDocument(); + $xml->loadXML($input); + } + if ($hasDisable && !$options['loadEntities']) { + libxml_disable_entity_loader(false); + } + libxml_use_internal_errors($internalErrors); + return $xml; + } + /** * Transform an array into a SimpleXMLElement *