一、前言
注:此漏洞利用和环境较为复杂,实际价值可能并不是很高,但对于XSS与权限管理也有一定参考价值。
上个月我们公布了WordPress 5.0中一个远程代码执行(RCE)漏洞(需通过身份认证)。本文公布了WordPress 5.1中存在的另一个严重的漏洞利用链,使未经身份认证的攻击者能够在5.1.1版之前的WordPress中获得远程代码执行权限。
二、漏洞影响
如果WordPress站点启用了评论(comment)功能,那么攻击者可以诱骗目标网站管理员访问攻击者设置的一个站点,最终接管目标站点。一旦受害管理员访问恶意网站,攻击者就会在后台通过跨站请求伪造(CSRF)攻击目标WordPress站点,不会因此目标受害者警觉。CSRF攻击中滥用了WordPress中的多个逻辑缺陷及数据过滤错误,并且结合这些缺陷实现RCE,最终完全接管目标站点。
5.1.1版之前默认配置的WordPress受这些漏洞影响。
根据WordPress下载页面的统计数据,互联网上超过33%的站点正在使用WordPress。考虑到博客评论是博客网站的核心功能,默认情况下处于启用状态,因此该漏洞会影响数百万站点。
三、技术分析
攻击过程参考此处视频。
当用户发表新评论时,WordPress并没有检查是否存在CSRF。如果执行检查操作,那某些WordPress功能(如trackbacks
以及pingbacks
)将无法正常工作。这意味着攻击者可以通过CSRF攻击,以WordPress博客管理员用户的身份创建评论。
这可能成为一个安全问题,因为WordPress网站管理员可以在评论中使用任意HTML标签,甚至还可以使用<script>
标签。理论上攻击者可以简单地滥用CSRF漏洞来创建包含恶意JavaScript代码的评论。
WordPress会在评论表单中为管理员生成一个额外的nonce值,通过这种方法尝试解决这个问题。当管理员提交评论并提供有效的nonce值时,WordPress将直接创建评论,没有执行任何过滤此操作。如果nonce值无效,那么评论仍可以创建,但会被过滤处理。
我们可以通过如下代码片段了解WordPress的处理过程。
源文件:/wp-includes/comment.php
(简化版):
⋮
if ( current_user_can( 'unfiltered_html' ) ) {
if (! wp_verify_nonce( $_POST['_wp_unfiltered_html_comment'], 'unfiltered-html-comment' )) {
$_POST['comment'] = wp_filter_post_kses($_POST['comment']); // line 3242
}
} else {
$_POST['comment'] = wp_filter_kses($_POST['comment']);
}
⋮
事实上,从2009年起,WordPress就没有在评论表单中部署CSRF防护机制。
然而,我们发现针对管理员的过滤过程中存在一处逻辑缺陷。如上代码片段中,WordPress始终使用wp_filter_kses()
来过滤评论,除非创建该评论的是具备unfiltered_html
功能的管理员。如果满足该条件,并且没有提供有效的nonce值,那么WordPress就会使用wp_filter_post_kses()
来过滤评论(上述代码第3242行)。
wp_filter_post_kses()
与wp_filter_kses()
在严格程度上有所区别。这两个函数都会处理未经过滤的评论,只在字符串中保留特定的HTML标签及属性。通常情况下,使用wp_filter_kses()
过滤的评论只会留下非常基本的HTML标签及属性,比如<a>
标签以及href
属性。
这样攻击者就可以创建一些评论,其中包含比正常评论更多的HTML标签及属性。然而,虽然wp_filter_post_kses()
更为宽松,但仍会删除可能导致跨站脚本漏洞的HTML标签及属性。
由于我们能注入其他HTML标签及属性,最终还是可以在WordPress中实现存储型XSS。这是因为WordPress会以某种错误的方式解析并处理正常评论中通常不会设置的某些属性,导致出现任意属性注入问题。
当WordPress执行完评论的过滤过程后,就会修改评论字符串中的<a>
标签,以适配SEO(搜索引擎优化)应用场景。
WordPress会将<a>
标志的属性字符串(如href="#" title="some link" rel="nofollow"
)解析成一个关联数组(如下代码片段),其中key为属性名,而value为属性值。
源文件:wp-includes/formatting.php
function wp_rel_nofollow_callback( $matches ) {
$text = $matches[1];
$atts = shortcode_parse_atts($matches[1]);
⋮
随后WordPress会检查其中是否设置了rel
属性。只有通过wp_filter_post_kses()
过滤评论时才会设置该属性。如果设置了该属性,则WordPress会处理rel
属性,然后再次与<a>
标签拼接起来。
源文件:wp-includes/formatting.php
if (!empty($atts['rel'])) {
// the processing of the 'rel' attribute happens here
⋮
$text = '';
foreach ($atts as $name => $value) { // line 3017
$text .= $name . '="' . $value . '" '; // line 3018
}
}
return '<a ' . $text . ' rel="' . $rel . '">'; // line 3021
}
上述代码第3017及3018行处存在缺陷,其中属性值在没有被转义处理的情况下就再次拼接在一起。
攻击者可以创建包含精心构造的<a>
标签的评论,并将title
属性设置为title='XSS " onmouseover=alert(1) id="'
。这个属性是合法的HTML数据,因此可以通过数据过滤检查。然而,只有当title
标签使用单引号时这种方法才有效。
当属性再次拼接时,title
属性会被封装到双引号中(第3018行)。这意味着攻击者可以注入双引号,闭合title
属性,因此可以注入其他HTML属性。
比如:<a title='XSS " onmouseover=evilCode() id=" '>
在处理之后会变成<a title="XSS " onmouseover=evilCode() id=" ">
。
由于此时评论已经被过滤处理过,因此攻击者注入的onmouseover
事件处理函数会存储在数据库中,不会被删除。将这种过滤缺陷与CSRF漏洞结合起来,攻击者就可以将存储型XSS payload注入目标网站中。
创建恶意评论后,为了实现远程代码执行(RCE),下一步攻击者需要让管理员执行已注入的JavaScript。WordPress评论会在目标博客的前端显示,而WordPress本身并没有使用X-Frame-Options
来保护前端页面。这意味着攻击者可以以隐藏iframe
的方式在网站上显示评论。由于注入的属性是一个onmouseover
事件处理函数,因此攻击者可以让iframe
跟随受害者鼠标,立刻触发XSS payload。
这样一来,攻击者就可以在目标网站上使用触发CSRF漏洞的管理员会话来执行任意JavaScript代码。有所的JavaScript都在后台执行,不会引起管理员的注意。
现在攻击者已经可能使用管理员会话来执行任意JavaScript代码,那么也很容易就能实现RCE。默认情况下,WordPress允许博客管理员在管理面板中直接编辑站点主题和插件的.php
文件。攻击者只需要简单插入一个PHP后门,就可以在远程服务器上获得任意PHP代码执行权限。
四、补丁情况
默认情况下,WordPress会自动安装安全更新,因此我们应该已经更新至最新的5.1.1版。如果用户或所属托管商由于某些原因禁用了自动更新功能,那么在安装补丁前,可以考虑禁用评论功能。更为重要的一点是,请管理员确保访问其他网站之前,已经注销当前的管理员会话。
五、时间线
六、总结
本文从CSRF漏洞开始介绍了一个完整的漏洞利用链。攻击者只需要诱导目标网站管理员访问某个恶意网站,然后就可以通过这条利用链来接管使用默认配置的WordPress站点。目标管理员并不会发现攻击者的网站有任何异常,除了访问攻击者设置的网站之外,整条攻击链中没有其他交互过程。
感谢WordPress安全团队,这些小伙伴们非常友善,并且合作解决问题时也非常专业。