From 7f983c2bae1dc78ca7217e9af364b2fb71dcefe8 Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Fri, 20 Mar 2015 17:21:59 +1300 Subject: [PATCH] BUG Fix SS-2014-017 --- core/Convert.php | 27 ++++++++++++++++---- tests/core/ConvertTest.php | 51 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 5 deletions(-) diff --git a/core/Convert.php b/core/Convert.php index 819d2abd0..89a4367a6 100644 --- a/core/Convert.php +++ b/core/Convert.php @@ -167,15 +167,32 @@ class Convert { /** * Converts an XML string to a PHP array + * See http://phpsecurity.readthedocs.org/en/latest/Injection-Attacks.html#xml-external-entity-injection * * @uses recursiveXMLToArray() - * @param string - * + * @param string $val + * @param boolean $disableDoctypes Disables the use of DOCTYPE, and will trigger an error if encountered. + * false by default. + * @param boolean $disableExternals Disables the loading of external entities. false by default. * @return array */ - public static function xml2array($val) { - $xml = new SimpleXMLElement($val); - return self::recursiveXMLToArray($xml); + public static function xml2array($val, $disableDoctypes = false, $disableExternals = false) { + // Check doctype + if($disableDoctypes && preg_match('/\<\!DOCTYPE.+]\>/', $val)) { + throw new InvalidArgumentException('XML Doctype parsing disabled'); + } + + // Disable external entity loading + if($disableExternals) $oldVal = libxml_disable_entity_loader($disableExternals); + try { + $xml = new SimpleXMLElement($val); + $result = self::recursiveXMLToArray($xml); + } catch(Exception $ex) { + if($disableExternals) libxml_disable_entity_loader($oldVal); + throw $ex; + } + if($disableExternals) libxml_disable_entity_loader($oldVal); + return $result; } /** diff --git a/tests/core/ConvertTest.php b/tests/core/ConvertTest.php index 35141940d..baffd69c2 100644 --- a/tests/core/ConvertTest.php +++ b/tests/core/ConvertTest.php @@ -238,4 +238,55 @@ class ConvertTest extends SapphireTest { Convert::raw2json($value) ); } + + public function testXML2Array() { + // Ensure an XML file at risk of entity expansion can be avoided safely + $inputXML = << +]> + + Now include &long; lots of times to expand the in-memory size of this XML structure + &long;&long;&long; + +XML + ; + try { + Convert::xml2array($inputXML, true); + } catch(Exception $ex) {} + $this->assertTrue( + isset($ex) + && $ex instanceof InvalidArgumentException + && $ex->getMessage() === 'XML Doctype parsing disabled' + ); + + // Test without doctype validation + $expected = array( + 'result' => array( + "Now include SOME_SUPER_LONG_STRING lots of times to expand the in-memory size of this XML structure", + array( + 'long' => array( + array( + 'long' => 'SOME_SUPER_LONG_STRING' + ), + array( + 'long' => 'SOME_SUPER_LONG_STRING' + ), + array( + 'long' => 'SOME_SUPER_LONG_STRING' + ) + ) + ) + ) + ); + $result = Convert::xml2array($inputXML, false, true); + $this->assertEquals( + $expected, + $result + ); + $result = Convert::xml2array($inputXML, false, false); + $this->assertEquals( + $expected, + $result + ); + } }