pax_global_header00006660000000000000000000000064126547361200014520gustar00rootroot0000000000000052 comment=7b64507bc35d841c9c5802d67f6f87ef8e1a58c9 ZendXml-release-1.0.2/000077500000000000000000000000001265473612000145175ustar00rootroot00000000000000ZendXml-release-1.0.2/.gitignore000066400000000000000000000000631265473612000165060ustar00rootroot00000000000000composer.lock vendor .buildpath .project .settings ZendXml-release-1.0.2/.travis.yml000066400000000000000000000013761265473612000166370ustar00rootroot00000000000000sudo: false language: php branches: except: - /^release-.*$/ - /^ghgfk-.*$/ cache: directories: - $HOME/.composer/cache matrix: allow_failures: - php: hhvm matrix: fast_finish: true include: - php: 5.3 - php: 5.4 - php: 5.5 env: - EXECUTE_CS_CHECK=true - php: 5.6 - php: 7 - php: hhvm allow_failures: - php: hhvm before_install: - composer self-update install: - travis_retry composer install --no-interaction --ignore-platform-reqs script: - ./vendor/bin/phpunit -c ./tests - if [[ $EXECUTE_CS_CHECK == 'true' ]]; then ./vendor/bin/phpcs --standard=PSR2 --ignore=tests/Bootstrap.php library tests ; fi notifications: irc: "irc.freenode.org#zftalk.dev" email: false ZendXml-release-1.0.2/CHANGELOG.md000066400000000000000000000010051265473612000163240ustar00rootroot00000000000000# Changelog All notable changes to this project will be documented in this file, in reverse chronological order by release. ## 1.0.2 - 2016-02-04 ### Added - Nothing. ### Deprecated - Nothing. ### Removed - Nothing. ### Fixed - [#11](https://github.com/zendframework/ZendXml/pull/11) updates the dependencies to PHP `^5.3.3 || ^7.0` and PHPUnit `^3.7 || ^4.0`, ensuring better compatibility with other components, and with PHP 7. The test matrix was also expanded to add PHP 7 as a required platform. ZendXml-release-1.0.2/LICENSE.md000066400000000000000000000027421265473612000161300ustar00rootroot00000000000000Copyright (c) 2014-2015, Zend Technologies USA, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of Zend Technologies USA, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ZendXml-release-1.0.2/README.md000066400000000000000000000032351265473612000160010ustar00rootroot00000000000000ZendXml ======= An utility component for XML usage and best practices in PHP Installation ------------ You can install using: ``` curl -s https://getcomposer.org/installer | php php composer.phar install ``` Notice that this library doesn't have any external dependencies, the usage of composer is for autoloading and standard purpose. ZendXml\Security ---------------- This is a security component to prevent [XML eXternal Entity](https://www.owasp.org/index.php/XML_External_Entity_%28XXE%29_Processing) (XXE) and [XML Entity Expansion](http://projects.webappsec.org/w/page/13247002/XML%20Entity%20Expansion) (XEE) attacks on XML documents. The XXE attack is prevented disabling the load of external entities in the libxml library used by PHP, using the function [libxml_disable_entity_loader](http://www.php.net/manual/en/function.libxml-disable-entity-loader.php). The XEE attack is prevented looking inside the XML document for ENTITY usage. If the XML document uses ENTITY the library throw an Exception. We have two static methods to scan and load XML document from a string (scan) and from a file (scanFile). You can decide to get a SimpleXMLElement or DOMDocument as result, using the following use cases: ```php use ZendXml\Security as XmlSecurity; $xml = << test XML; // SimpleXML use case $simplexml = XmlSecurity::scan($xml); printf ("SimpleXMLElement: %s\n", ($simplexml instanceof \SimpleXMLElement) ? 'yes' : 'no'); // DOMDocument use case $dom = new \DOMDocument('1.0'); $dom = XmlSecurity::scan($xml, $dom); printf ("DOMDocument: %s\n", ($dom instanceof \DOMDocument) ? 'yes' : 'no'); ``` ZendXml-release-1.0.2/composer.json000066400000000000000000000016211265473612000172410ustar00rootroot00000000000000{ "name": "zendframework/zendxml", "description": "Utility library for XML usage, best practices, and security in PHP", "type": "library", "license": "BSD-3-Clause", "keywords": [ "zf2", "xml", "security" ], "homepage": "http://packages.zendframework.com/", "autoload": { "psr-0": { "ZendXml\\": "library/" } }, "autoload-dev": { "psr-4": { "ZendTest\\Xml\\": "tests/ZendXmlTest/" } }, "repositories": [ { "type": "composer", "url": "http://packages.zendframework.com/" } ], "require": { "php": "^5.3.3 || ^7.0" }, "extra": { "branch-alias": { "dev-master": "1.0-dev" } }, "require-dev": { "phpunit/phpunit": "^3.7 || ^4.0", "squizlabs/php_codesniffer": "^1.5" } } ZendXml-release-1.0.2/library/000077500000000000000000000000001265473612000161635ustar00rootroot00000000000000ZendXml-release-1.0.2/library/ZendXml/000077500000000000000000000000001265473612000175445ustar00rootroot00000000000000ZendXml-release-1.0.2/library/ZendXml/Exception/000077500000000000000000000000001265473612000215025ustar00rootroot00000000000000ZendXml-release-1.0.2/library/ZendXml/Exception/ExceptionInterface.php000066400000000000000000000005651265473612000260000ustar00rootroot00000000000000 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) { throw new Exception\RuntimeException(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 Exception\InvalidArgumentException * @return SimpleXMLElement|DomDocument */ public static function scanFile($file, DOMDocument $dom = null) { if (!file_exists($file)) { throw new Exception\InvalidArgumentException( "The file $file specified doesn't exist" ); } return self::scan(file_get_contents($file), $dom); } /** * Return true if PHP is running with PHP-FPM * * This method is mainly used to determine whether or not heuristic checks * (vs libxml checks) should be made, due to threading issues in libxml; * under php-fpm, threading becomes a concern. * * However, PHP versions 5.5.22+ and 5.6.6+ contain a patch to the * libxml support in PHP that makes the libxml checks viable; in such * versions, this method will return false to enforce those checks, which * are more strict and accurate than the heuristic checks. * * @return boolean */ public static function isPhpFpm() { $isVulnerableVersion = ( version_compare(PHP_VERSION, '5.5.22', 'lt') || ( version_compare(PHP_VERSION, '5.6', 'gte') && version_compare(PHP_VERSION, '5.6.6', 'lt') ) ); if (substr(php_sapi_name(), 0, 3) === 'fpm' && $isVulnerableVersion) { return true; } return false; } /** * Determine and return the string(s) to use for the $generator) { $prefix = $generator('<' . '?xml'); if (0 === strncmp($xml, $prefix, strlen($prefix))) { return $encoding; } } // Fallback return 'UTF-8'; } /** * Attempt to detect the specified XML encoding. * * Using the file's encoding, determines if an "encoding" attribute is * present and well-formed in the XML declaration; if so, it returns a * list with both the ASCII representation of that declaration and the * original file encoding. * * If not, a list containing only the provided file encoding is returned. * * @param string $xml * @param string $fileEncoding * @return string[] Potential XML encodings */ protected static function detectXmlEncoding($xml, $fileEncoding) { $encodingMap = self::getAsciiEncodingMap(); $generator = $encodingMap[$fileEncoding]; $encAttr = $generator('encoding="'); $quote = $generator('"'); $close = $generator('>'); $closePos = strpos($xml, $close); if (false === $closePos) { return array($fileEncoding); } $encPos = strpos($xml, $encAttr); if (false === $encPos || $encPos > $closePos ) { return array($fileEncoding); } $encPos += strlen($encAttr); $quotePos = strpos($xml, $quote, $encPos); if (false === $quotePos) { return array($fileEncoding); } $encoding = self::substr($xml, $encPos, $quotePos); return array( // Following line works because we're only supporting 8-bit safe encodings at this time. str_replace('\0', '', $encoding), // detected encoding $fileEncoding, // file encoding ); } /** * Return a list of BOM maps. * * Returns a list of common encoding -> BOM maps, along with the character * length to compare against. * * @link https://en.wikipedia.org/wiki/Byte_order_mark * @return array */ protected static function getBomMap() { return array( array( 'encoding' => 'UTF-32BE', 'bom' => pack('CCCC', 0x00, 0x00, 0xfe, 0xff), 'length' => 4, ), array( 'encoding' => 'UTF-32LE', 'bom' => pack('CCCC', 0xff, 0xfe, 0x00, 0x00), 'length' => 4, ), array( 'encoding' => 'GB-18030', 'bom' => pack('CCCC', 0x84, 0x31, 0x95, 0x33), 'length' => 4, ), array( 'encoding' => 'UTF-16BE', 'bom' => pack('CC', 0xfe, 0xff), 'length' => 2, ), array( 'encoding' => 'UTF-16LE', 'bom' => pack('CC', 0xff, 0xfe), 'length' => 2, ), array( 'encoding' => 'UTF-8', 'bom' => pack('CCC', 0xef, 0xbb, 0xbf), 'length' => 3, ), ); } /** * Return a map of encoding => generator pairs. * * Returns a map of encoding => generator pairs, where the generator is a * callable that accepts a string and returns the appropriate byte order * sequence of that string for the encoding. * * @return array */ protected static function getAsciiEncodingMap() { return array( 'UTF-32BE' => function ($ascii) { return preg_replace('/(.)/', "\0\0\0\\1", $ascii); }, 'UTF-32LE' => function ($ascii) { return preg_replace('/(.)/', "\\1\0\0\0", $ascii); }, 'UTF-32odd1' => function ($ascii) { return preg_replace('/(.)/', "\0\\1\0\0", $ascii); }, 'UTF-32odd2' => function ($ascii) { return preg_replace('/(.)/', "\0\0\\1\0", $ascii); }, 'UTF-16BE' => function ($ascii) { return preg_replace('/(.)/', "\0\\1", $ascii); }, 'UTF-16LE' => function ($ascii) { return preg_replace('/(.)/', "\\1\0", $ascii); }, 'UTF-8' => function ($ascii) { return $ascii; }, 'GB-18030' => function ($ascii) { return $ascii; }, ); } /** * Binary-safe substr. * * substr() is not binary-safe; this method loops by character to ensure * multi-byte characters are aggregated correctly. * * @param string $string * @param int $start * @param int $end * @return string */ protected static function substr($string, $start, $end) { $substr = ''; for ($i = $start; $i < $end; $i += 1) { $substr .= $string[$i]; } return $substr; } } ZendXml-release-1.0.2/tests/000077500000000000000000000000001265473612000156615ustar00rootroot00000000000000ZendXml-release-1.0.2/tests/Bootstrap.php000066400000000000000000000056201265473612000203520ustar00rootroot00000000000000addDirectoryToWhitelist($zfCoreLibrary . '/' . $lastArg); } elseif (is_file($zfCoreTests . '/' . $lastArg)) { $codeCoverageFilter->addDirectoryToWhitelist(dirname($zfCoreLibrary . '/' . $lastArg)); } else { $codeCoverageFilter->addDirectoryToWhitelist($zfCoreLibrary); } /* * Omit from code coverage reports the contents of the tests directory */ $codeCoverageFilter->addDirectoryToBlacklist($zfCoreTests, ''); $codeCoverageFilter->addDirectoryToBlacklist(PEAR_INSTALL_DIR, ''); $codeCoverageFilter->addDirectoryToBlacklist(PHP_LIBDIR, ''); unset($codeCoverageFilter); } /* * Unset global variables that are no longer needed. */ unset($phpUnitVersion); ZendXml-release-1.0.2/tests/ZendXmlTest/000077500000000000000000000000001265473612000201025ustar00rootroot00000000000000ZendXml-release-1.0.2/tests/ZendXmlTest/MultibyteTest.php000066400000000000000000000074261265473612000234420ustar00rootroot00000000000000 array('UTF-16LE', pack('CC', 0xff, 0xfe), 3), 'UTF-16BE' => array('UTF-16BE', pack('CC', 0xfe, 0xff), 3), 'UTF-32LE' => array('UTF-32LE', pack('CCCC', 0xff, 0xfe, 0x00, 0x00), 4), 'UTF-32BE' => array('UTF-32BE', pack('CCCC', 0x00, 0x00, 0xfe, 0xff), 4), ); } public function getXmlWithXXE() { return << ]> retrieved: &pocdata; XML; } /** * Invoke ZendXml\Security::heuristicScan with the provided XML. * * @param string $xml * @return void * @throws Exception\RuntimeException */ public function invokeHeuristicScan($xml) { $r = new ReflectionMethod('ZendXml\Security', 'heuristicScan'); $r->setAccessible(true); return $r->invoke(null, $xml); } /** * @dataProvider multibyteEncodings * @group heuristicDetection */ public function testDetectsMultibyteXXEVectorsUnderFPMWithEncodedStringMissingBOM($encoding, $bom, $bomLength) { $xml = $this->getXmlWithXXE(); $xml = str_replace('{ENCODING}', $encoding, $xml); $xml = iconv('UTF-8', $encoding, $xml); $this->assertNotSame(0, strncmp($xml, $bom, $bomLength)); $this->setExpectedException('ZendXml\Exception\RuntimeException', 'ENTITY'); $this->invokeHeuristicScan($xml); } /** * @dataProvider multibyteEncodings */ public function testDetectsMultibyteXXEVectorsUnderFPMWithEncodedStringUsingBOM($encoding, $bom) { $xml = $this->getXmlWithXXE(); $xml = str_replace('{ENCODING}', $encoding, $xml); $orig = iconv('UTF-8', $encoding, $xml); $xml = $bom . $orig; $this->setExpectedException('ZendXml\Exception\RuntimeException', 'ENTITY'); $this->invokeHeuristicScan($xml); } public function getXmlWithoutXXE() { return << retrieved: &pocdata; XML; } /** * @dataProvider multibyteEncodings */ public function testDoesNotFlagValidMultibyteXmlAsInvalidUnderFPM($encoding) { $xml = $this->getXmlWithoutXXE(); $xml = str_replace('{ENCODING}', $encoding, $xml); $xml = iconv('UTF-8', $encoding, $xml); try { $result = $this->invokeHeuristicScan($xml); $this->assertNull($result); } catch (\Exception $e) { $this->fail('Security scan raised exception when it should not have'); } } /** * @dataProvider multibyteEncodings * @group mixedEncoding */ public function testDetectsXXEWhenXMLDocumentEncodingDiffersFromFileEncoding($encoding, $bom) { $xml = $this->getXmlWithXXE(); $xml = str_replace('{ENCODING}', 'UTF-8', $xml); $xml = iconv('UTF-8', $encoding, $xml); $xml = $bom . $xml; $this->setExpectedException('ZendXml\Exception\RuntimeException', 'ENTITY'); $this->invokeHeuristicScan($xml); } } ZendXml-release-1.0.2/tests/ZendXmlTest/SecurityTest.php000066400000000000000000000064461265473612000232740ustar00rootroot00000000000000 ]> This result is &harmless; XML; $this->setExpectedException('ZendXml\Exception\RuntimeException'); $result = XmlSecurity::scan($xml); } public function testScanForXXE() { $file = tempnam(sys_get_temp_dir(), 'ZendXml_Security'); file_put_contents($file, 'This is a remote content!'); $xml = << ]> &foo; XML; try { $result = XmlSecurity::scan($xml); } catch (Exception\RuntimeException $e) { unlink($file); return; } $this->fail('An expected exception has not been raised.'); } public function testScanSimpleXmlResult() { $result = XmlSecurity::scan($this->getXml()); $this->assertTrue($result instanceof SimpleXMLElement); $this->assertEquals($result->result, 'test'); } public function testScanDom() { $dom = new DOMDocument('1.0'); $result = XmlSecurity::scan($this->getXml(), $dom); $this->assertTrue($result instanceof DOMDocument); $node = $result->getElementsByTagName('result')->item(0); $this->assertEquals($node->nodeValue, 'test'); } public function testScanInvalidXml() { $xml = <<test XML; $result = XmlSecurity::scan($xml); $this->assertFalse($result); } public function testScanInvalidXmlDom() { $xml = <<test XML; $dom = new DOMDocument('1.0'); $result = XmlSecurity::scan($xml, $dom); $this->assertFalse($result); } public function testScanFile() { $file = tempnam(sys_get_temp_dir(), 'ZendXml_Security'); file_put_contents($file, $this->getXml()); $result = XmlSecurity::scanFile($file); $this->assertTrue($result instanceof SimpleXMLElement); $this->assertEquals($result->result, 'test'); unlink($file); } public function testScanXmlWithDTD() { $xml = << ]> test XML; $dom = new DOMDocument('1.0'); $result = XmlSecurity::scan($xml, $dom); $this->assertTrue($result instanceof DOMDocument); $this->assertTrue($result->validate()); } protected function getXml() { return << test XML; } } ZendXml-release-1.0.2/tests/phpunit.xml.dist000077500000000000000000000010701265473612000210350ustar00rootroot00000000000000 ./ZendXmlTest ./ZendXmlTest/TestAsset ./ZendXmlTest ../vendor