mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge pull request #10376 from creative-commoners/pulls/4.10/cve-2021-41559
CVE-2021-41559 Disable xml entities
This commit is contained in:
commit
410c2a8966
@ -2,12 +2,11 @@
|
|||||||
|
|
||||||
namespace SilverStripe\Core;
|
namespace SilverStripe\Core;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use SimpleXMLElement;
|
||||||
use SilverStripe\Dev\Deprecation;
|
use SilverStripe\Dev\Deprecation;
|
||||||
use SilverStripe\ORM\DB;
|
use SilverStripe\ORM\DB;
|
||||||
use SilverStripe\View\Parsers\URLSegmentFilter;
|
use SilverStripe\View\Parsers\URLSegmentFilter;
|
||||||
use InvalidArgumentException;
|
|
||||||
use SimpleXMLElement;
|
|
||||||
use Exception;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Library of conversion functions, implemented as static methods.
|
* Library of conversion functions, implemented as static methods.
|
||||||
@ -29,7 +28,6 @@ use Exception;
|
|||||||
*/
|
*/
|
||||||
class Convert
|
class Convert
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a value to be suitable for an XML attribute.
|
* Convert a value to be suitable for an XML attribute.
|
||||||
*
|
*
|
||||||
@ -296,40 +294,34 @@ class Convert
|
|||||||
* @param string $val
|
* @param string $val
|
||||||
* @param boolean $disableDoctypes Disables the use of DOCTYPE, and will trigger an error if encountered.
|
* @param boolean $disableDoctypes Disables the use of DOCTYPE, and will trigger an error if encountered.
|
||||||
* false by default.
|
* false by default.
|
||||||
* @param boolean $disableExternals Disables the loading of external entities. false by default. No-op in PHP 8.
|
* @param boolean $disableExternals Does nothing because xml entities are removed
|
||||||
|
* @deprecated 4.11.0:5.0.0
|
||||||
* @return array
|
* @return array
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public static function xml2array($val, $disableDoctypes = false, $disableExternals = false)
|
public static function xml2array($val, $disableDoctypes = false, $disableExternals = false)
|
||||||
{
|
{
|
||||||
// PHP 8 deprecates libxml_disable_entity_loader() as it is no longer needed
|
Deprecation::notice('4.10', 'Use a dedicated XML library instead');
|
||||||
if (\PHP_VERSION_ID >= 80000) {
|
|
||||||
$disableExternals = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check doctype
|
// Check doctype
|
||||||
if ($disableDoctypes && preg_match('/\<\!DOCTYPE.+]\>/', $val)) {
|
if ($disableDoctypes && strpos($val ?? '', '<!DOCTYPE') !== false) {
|
||||||
throw new InvalidArgumentException('XML Doctype parsing disabled');
|
throw new InvalidArgumentException('XML Doctype parsing disabled');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable external entity loading
|
// CVE-2021-41559 Ensure entities are removed due to their inherent security risk via
|
||||||
$oldVal = null;
|
// XXE attacks and quadratic blowup attacks, and also lack of consistent support
|
||||||
if ($disableExternals) {
|
$val = preg_replace('/(?s)<!ENTITY.*?>/', '', $val ?? '');
|
||||||
$oldVal = libxml_disable_entity_loader($disableExternals);
|
|
||||||
|
// If there's still an <!ENTITY> present, then it would be the result of a maliciously
|
||||||
|
// crafted XML document e.g. <!ENTITY><!<!ENTITY>ENTITY ext SYSTEM "http://evil.com">
|
||||||
|
if (strpos($val ?? '', '<!ENTITY') !== false) {
|
||||||
|
throw new InvalidArgumentException('Malicious XML entity detected');
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
$xml = new SimpleXMLElement($val);
|
// This will throw an exception if the XML contains references to any internal entities
|
||||||
$result = self::recursiveXMLToArray($xml);
|
// that were defined in an <!ENTITY /> before it was removed
|
||||||
} catch (Exception $ex) {
|
$xml = new SimpleXMLElement($val ?? '');
|
||||||
if ($disableExternals) {
|
return self::recursiveXMLToArray($xml);
|
||||||
libxml_disable_entity_loader($oldVal);
|
|
||||||
}
|
|
||||||
throw $ex;
|
|
||||||
}
|
|
||||||
if ($disableExternals) {
|
|
||||||
libxml_disable_entity_loader($oldVal);
|
|
||||||
}
|
|
||||||
return $result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -403,55 +403,86 @@ PHP
|
|||||||
*/
|
*/
|
||||||
public function testXML2Array()
|
public function testXML2Array()
|
||||||
{
|
{
|
||||||
// Ensure an XML file at risk of entity expansion can be avoided safely
|
|
||||||
$inputXML = <<<XML
|
$inputXML = <<<XML
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<!DOCTYPE results [<!ENTITY long "SOME_SUPER_LONG_STRING">]>
|
<!DOCTYPE results [
|
||||||
|
<!ENTITY long "SOME_SUPER_LONG_STRING">
|
||||||
|
]>
|
||||||
<results>
|
<results>
|
||||||
<result>Now include &long; lots of times to expand the in-memory size of this XML structure</result>
|
<result>My para</result>
|
||||||
<result>&long;&long;&long;</result>
|
<result>Ampersand & is retained and not double encoded</result>
|
||||||
</results>
|
</results>
|
||||||
XML
|
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 = [
|
$expected = [
|
||||||
'result' => [
|
'result' => [
|
||||||
'Now include SOME_SUPER_LONG_STRING lots of times to expand the in-memory size of this XML structure',
|
'My para',
|
||||||
[
|
'Ampersand & is retained and not double encoded'
|
||||||
'long' => [
|
|
||||||
[
|
|
||||||
'long' => 'SOME_SUPER_LONG_STRING'
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'long' => 'SOME_SUPER_LONG_STRING'
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'long' => 'SOME_SUPER_LONG_STRING'
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
$result = Convert::xml2array($inputXML, false, true);
|
$actual = Convert::xml2array($inputXML, false);
|
||||||
$this->assertEquals(
|
$this->assertEquals($expected, $actual);
|
||||||
$expected,
|
$this->expectException(InvalidArgumentException::class);
|
||||||
$result
|
$this->expectExceptionMessage('XML Doctype parsing disabled');
|
||||||
);
|
Convert::xml2array($inputXML, true);
|
||||||
$result = Convert::xml2array($inputXML, false, false);
|
}
|
||||||
$this->assertEquals(
|
|
||||||
$expected,
|
/**
|
||||||
$result
|
* Tests {@link Convert::xml2array()} if an exception the contains a reference to a removed <!ENTITY />
|
||||||
);
|
*/
|
||||||
|
public function testXML2ArrayEntityException()
|
||||||
|
{
|
||||||
|
$inputXML = <<<XML
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE results [
|
||||||
|
<!ENTITY long "SOME_SUPER_LONG_STRING">
|
||||||
|
]>
|
||||||
|
<results>
|
||||||
|
<result>Now include &long; lots of times to expand the in-memory size of this XML structure</result>
|
||||||
|
<result>&long;&long;&long;</result>
|
||||||
|
</results>
|
||||||
|
XML;
|
||||||
|
$this->expectException(Exception::class);
|
||||||
|
$this->expectExceptionMessage('String could not be parsed as XML');
|
||||||
|
Convert::xml2array($inputXML);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link Convert::xml2array()} if an exception the contains a reference to a multiple removed <!ENTITY />
|
||||||
|
*/
|
||||||
|
public function testXML2ArrayMultipleEntitiesException()
|
||||||
|
{
|
||||||
|
$inputXML = <<<XML
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE results [<!ENTITY long "SOME_SUPER_LONG_STRING"><!ENTITY short "SHORT_STRING">]>
|
||||||
|
<results>
|
||||||
|
<result>Now include &long; and &short; lots of times</result>
|
||||||
|
<result>&long;&long;&long;&short;&short;&short;</result>
|
||||||
|
</results>
|
||||||
|
XML;
|
||||||
|
$this->expectException(Exception::class);
|
||||||
|
$this->expectExceptionMessage('String could not be parsed as XML');
|
||||||
|
Convert::xml2array($inputXML);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link Convert::xml2array()} if there is a malicious <!ENTITY /> present
|
||||||
|
*/
|
||||||
|
public function testXML2ArrayMaliciousEntityException()
|
||||||
|
{
|
||||||
|
$inputXML = <<<XML
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE results [
|
||||||
|
<!ENTITY><!<!ENTITY>ENTITY ext SYSTEM "http://evil.com">
|
||||||
|
]>
|
||||||
|
<results>
|
||||||
|
<result>Evil document</result>
|
||||||
|
</results>
|
||||||
|
XML;
|
||||||
|
$this->expectException(InvalidArgumentException::class);
|
||||||
|
$this->expectExceptionMessage('Malicious XML entity detected');
|
||||||
|
Convert::xml2array($inputXML);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user