提问者:小点点

澄清PHP版本中的XXE漏洞


我在这里发布一个问题作为最后的手段,我浏览了网页,经历了许多尝试,但没有成功。

复制XXE攻击是我试图做的,以防止它们,但我似乎无法理解PHP处理XML实体的方式。郑重声明,我在Ubuntu 12.04上使用PHP5.5.10,但我在5.4和5.3上做了一些测试,libxml2似乎是2.7.8版(似乎不包括不解析实体的默认值)。

在下面的例子中,用true或false调用libxml_disable_entity_loader()没有效果,或者我做错了什么。

$xml = <<<XML
<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY c PUBLIC "bar" "/etc/passwd">
]>
<root>
    <test>Test</test>
    <sub>&c;</sub>
</root>
XML;

libxml_disable_entity_loader(true);
$dom = new DOMDocument();
$dom->loadXML($xml);

// Prints Test.
print $dom->textContent;

但是,我可以特别地将一些参数传递给loadXML()以允许一些选项,并且当实体是本地文件时有效,而不是当它是外部URL时。

$xml = <<<XML
<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY c PUBLIC "bar" "/etc/passwd">
]>
<root>
    <test>Test</test>
    <sub>&c;</sub>
</root>
XML;

$dom = new DOMDocument();
$dom->loadXML($xml, LIBXML_NOENT | LIBXML_DTDLOAD);

// Prints Test.
print $dom->textContent;

现在,如果我们将实体更改为其他东西,如以下示例所示,实体已解析,但我无法使用参数或函数禁用它…发生了什么?!

$xml = <<<XML
<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY c "Blah blah">
]>
<root>
    <test>Test</test>
    <sub>&c;</sub>
</root>
XML;

$dom = new DOMDocument();
$dom->loadXML($xml);

// Prints Test.
print $dom->textContent;

我能找到的唯一方法是覆盖DOMDocument对象的属性。

  • 解析器外部设置为1
  • 替换实体设置为1

然后他们解决了,或者没有。

所以总结一下,我真的很想理解我显然不理解的东西。为什么那些参数和函数似乎没有影响?libxml2优先于PHP吗?

非常感谢!

参考资料:

  • https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing
  • http://au2.php.net/libxml_disable_entity_loader
  • http://au2.php.net/manual/en/libxml.constants.php
  • http://www.vsecurity.com/download/papers/XMLDTDEntityAttacks.pdf
  • http://www.mediawiki.org/wiki/XML_External_Entity_Processing
  • 如何使用PHP的各种XML库来获得类似DOM的功能并避免DoS漏洞,例如Billion Laughs或Quadratic Blowup?

共1个答案

匿名用户

保持简单…因为它应该很简单:-)

libxml_disable_entity_loader在这里做或不做任何事情取决于您的系统是否默认解析实体(我的没有)。这由libxml的LIBXML_NOENT选项控制。

没有它,文档处理器甚至可能不会尝试翻译外部实体,因此libxml_disable_entity_loader没有什么真正的影响(如果libxml默认不加载实体,这似乎是您的测试用例中的情况)。

LIBXML_NOENT添加到loadXML(),如下所示:

$dom->loadXML($xml, LIBXML_NOENT);

你会很快得到:

PHP Warning:  DOMDocument::loadXML(): I/O warning : failed to load external entity "/etc/passwd" in ...
PHP Warning:  DOMDocument::loadXML(): Failure to process entity c in Entity, line: 7 in ...
PHP Warning:  DOMDocument::loadXML(): Entity 'c' not defined in Entity, line: 7 in ...

在这种情况下,您使用LIBXML_NOENT选项启用了实体解析,这就是为什么它在/etc/passwd之后。

该示例在我的机器上运行良好,即使对于外部URL-我将ENTITY更改为外部,如下所示:

<!ENTITY c PUBLIC "bar" "https://stackoverflow.com/opensearch.xml">

但是,它甚至可能受到例如allow_url_fopenPHPINI设置的影响-将其设置为false,PHP将永远不会加载远程文件。

XML您提供的实体不是外部实体,而是内部实体(参见此处)。

您的实体:

<!ENTITY c "Blah blah">

如何定义内部实体:

<!ENTITY % name "entity_value">

因此,没有理由PHP或libxml来阻止解析此类实体。

我很快发布了一个PHPXXE测试脚本,它尝试不同的设置,并显示XXE是否成功以及在何种情况下。

唯一应该真正显示警告的线是“LIBXML_NOENT”线。

如果任何其他行加载警告,加载外部实体!您的设置默认允许加载外部实体。

你呀 <罢工> 使用不会出错 无论您/您的提供商的机器默认设置如何,都应该使用libxml_disable_entity_loader()。如果您的应用程序被迁移,它可能会立即变得容易受到攻击。

正如您发布的链接中的MediaWiki所述。

不幸的是,libxml2实现禁用的方式,当外部实体被禁用时,库会被瘫痪,否则安全的函数会在整个解析中导致异常。

$oldValue = libxml_disable_entity_loader(true);
// do whatever XML-processing related
libxml_disable_entity_loader($oldValue);

注意:libxml_disable_entity_loader()也禁止直接加载外部xml文件(不是通过实体):

<?php
$remote_xml = "https://stackoverflow.com/opensearch.xml";

$dom = new DOMDocument();

if ($dom->load($remote_xml) !== FALSE)
    echo "loaded remote xml!\n";
else
    echo "failed to load remote xml!\n";

libxml_disable_entity_loader(true);
if ($dom->load($remote_xml) !== FALSE)
    echo "loaded remote xml after libxml_disable_entity_loader(true)!\n";
else 
    echo "failed to remote xml after libxml_disable_entity_loader(true)!\n";

在我的机器上:

loaded remote xml!
PHP Warning:  DOMDocument::load(): I/O warning : failed to load external entity "https://stackoverflow.com/opensearch.xml" in ...
failed to remote xml after libxml_disable_entity_loader(true)!

这可能与PHP有关bug但PHP真的很愚蠢,因为:

libxml_disable_entity_loader(true);
$dom->loadXML(file_get_contents($remote_xml));

工作得很好。