本文翻译自:https://insinuator.net/2018/08/a-few-notes-on-wordpress-security/
我们先来看看WordPress的CVE列表,大多数漏洞都不是在WordPressCore里面找到的,而是在第三方插件和主题中。
今天,我们来谈谈WordPress。
对WordPress进行评估可能看起来无聊至极,因为核心功能[已测试]和配置是不允许大范围的安全性错误配置存在的。幸运的是,大多数实例使用的是插件和主题来添加WordPress核心程序尚未提供的功能。
在这篇博文中,我想讨论一下我的发现以及发现它们的方法。此外,我将描述不同厂商的响应情况,从完全没有反应、不理解问题到作出既快速又专业的反应,并要求对准备部署的更新的代码进行审查。
WordPress允许插件通过两种方式注册API路由——要么是为已经经过身份认证的calls注册前缀wp_ajax_
,要么是为尚未经过身份认证的calls注册前缀wp_ajax_nopriv_
(请参阅API插件)
EventON提供了一个日历插件作为核心和多个附加插件以扩展它的功能,包括预订、RSS订阅、票务系统、审查系统和CSV文件导入。
评估的系统仅安装了核心程序和CSV导入程序,但这两个插件都有漏洞,接下来我们会进行讨论。
首先,我们来看看eventON的核心插件。使用下面的代码可以为evo_mdt
AJAX操作注册一个处理程序:
add_action( 'wp_ajax_evo_mdt', array( $this, 'evomdt_ajax' ) );
add_action( 'wp_ajax_nopriv_evo_mdt', array( $this, 'evomdt_ajax' ) );
快速查看evomdt_ajax
函数,可以看到它将未经验证的POST数据传递给了mdt_form
:
function evomdt_ajax(){
if(empty($_POST['type'])) return;
$type = $_POST['type'];
$output = '';
switch($type){
case 'newform':
echo json_encode(array(
'content' =>$this->mdt_form($_POST['eventid'], $_POST['tax']),
'status'=>'good'
)); exit;
break;
case 'editform':
echo json_encode(array(
'content' =>$this->mdt_form($_POST['eventid'], $_POST['tax'],$_POST['termid'] ),
'status'=>'good'
)); exit;
break;
}
函数mdt_form
创建了一个反映AJAX请求中特定的POST参数的HTML输出。
function mdt_form($eventid, $tax, $termid = ''){
ob_start();
?>
<div class='ev_admin_form'>
<div class='evo_tax_entry evoselectfield_saved_data sections'>
<input type="hidden" class='field' name='eventid' value='<?php echo $eventid;?>'/>
<input type="hidden" class='field' name='termid' value='<?php echo $termid;?>'/>
<input type="hidden" class='field' name='tax' value='<?php echo $tax;?>'/>
[...]
return ob_get_clean();
}
在没有选项参数的情况下使用json_encode
函数,会导致PHP不会转义其他字符(参见JSON_HEX_TAG)。因此,我们可以将任意HTML注入到响应中,但是没有浏览器会在JSON响应中评估HTML吗?好吧,只有当您的JSON响应告诉浏览器它实际上就是JSON的时候才会评估。
HTTP/1.1 200 OK
[...]
Content-Type: text/html; charset=UTF-8
{"content":[...]
我是怎么找到这个漏洞的呢?我就不多卖关子了。
首先,让我们来看看所有已经注册的AJAX操作:
grep -rain "add_action([ ]*['\"]wp_ajax_"
这使我们对可用的特权和非特权行为有一个相当好的概述。快速查看处理程序可能会显示如上面所描述的漏洞。
我们来看看CSV导入器。由于插件目的是创建新的事件,因此对用户访问应该进行授权限制。使用上面提到的grep命令,可以得到以下内容:
$ grep -rain "add_action([ ]*['\"]wp_ajax_"
includes/class-ajax.php:13: add_action( 'wp_ajax_'. $ajax_event, array( $this, $class ) );
includes/class-ajax.php:14: add_action( 'wp_ajax_nopriv_'. $ajax_event, array( $this, $class ) );
有趣的是,插件注册了一个wp_ajax_nopriv
操作。检查代码发现:
$ajax_events = array(
'evocsv_001'=>'evocsv_001',
);
foreach ( $ajax_events as $ajax_event => $class ) {
add_action( 'wp_ajax_'. $ajax_event, array( $this, $class ) );
add_action( 'wp_ajax_nopriv_'. $ajax_event, array( $this, $class ) );
}
以及处理函数:
public function evocsv_001(){
if(!is_admin()) exit;
if(!isset($_POST['events'])){
[...]
exit;
}else{
[...]
foreach($event_data as $event){
[...]
$status = $eventon_csv->admin->import_event($processedDATA);
}
}
[...]
echo json_encode($return_content);
exit;
}
有趣的是,代码确实使用is_admin()
函数对管理员身份是否属实进行了核查,所以这个请求应该是安全的,对吗?我们来看一下文档:
Whether the current request is for an administrative interface page. […] Does not check if the user is an administrator; current_user_can() for checking roles and capabilities.
由于WordPress中的AJAX动作是通过wp-admin/admin-ajax.php
文件访问的,所以is_admin()
总是返回true
。
这个插件能允许任何没有身份验证的用户在您的WordPress实例上创建事件。
另一个有用的grep行是搜索$_GET
和$_POST
。
grep -rain "\\$_\(GET\|POST\)"
快速检查CSV导入程序会发现另一个可能存在的漏洞
$ grep -rain "\\$_\(GET\|POST\)"
[...]
includes/class-settings.php:33: $_POST['settings-updated']='Successfully updated values.';
includes/class-settings.php:53: $updated_code = (isset($_POST['settings-updated']))? '<div class="updated fade"><p>'.$_POST['settings-updated'].'</p></div>':null;
[...]
为了验证这一行是易受攻击的,我们看一下文件:
[...]
if( isset($_POST['evocsv_noncename']) && isset( $_POST ) ){
if ( wp_verify_nonce( $_POST['evocsv_noncename'], AJDE_EVCAL_BASENAME ) ){
[...]
$_POST['settings-updated']='Successfully updated values.';
[...]
$updated_code = (isset($_POST['settings-updated']))? '<div class="updated fade"><p>'.$_POST['settings-updated'].'</p></div>':null;
echo $updated_code;
我们可以看到,如果请求中的临时参数没有设置或者是无效的,那么如果设置了,POST参数就会被反射。这就是为什么不应该使用POST变量来存储应用程序数据。
悲剧的是,我们没有收到供应商的任何回复。
顾名思义,Google Map Pro将谷歌地图集成到WordPress页面。
这个漏洞是通过下面的grep行找到的,grep行基本功能是搜索已打开的标签(PHP或HTML),后面跟着变量的回显。
grep -rain "]*echo[ ]\{1,\}\\$"
现在的输出应该包含变量被写入响应中的大多数事件。最后,我在我的输出中找到了下面的行:
core/class.plugin-overview.php:165: <div class=" flippercode-ui fcdoc-product-info" data-current-product=productTextDomain; ?> data-current-product-slug=productSlug; ?> data-product-version = productVersion; ?> data-product-name = "productName; ?>" >
快速检查一下回显变量
$skin = $_GET['skin'];
使用此参数,攻击者可以伪造包含简单有效负载的链接,以在管理员的上下文中执行JavaScript。
供应商确实联系了我们说漏洞已经修复,还说漏洞并不重要,因为只有管理门户受到了影响!?
Jupiter是WordPress的一个主题,它具有一个WYSIWYG编辑器的页面元素。
为了在我们的grep输出中展示这个漏洞,我们修改正则表达式,使之包含将字符串与变量连接起来的回显。
grep -rain "echo \(\(['][^']*[']\|[\"][^\"]*[\"]\)[ ]*.[ ]*\)*\\$"
从这一点来看,如果它们被污染(包含用户控制的输入),则必须检查所有级联变量。使用给定的命令多行输出POST请求的元数据的行为是可以被识别的。将元数据设置为适合的payload,编辑器就可以在显示博客帖子时执行JavaScript代码,而不会在管理界面中显示。
Artbees响应确实迅速,并且提供了一个PHP文件补丁供我们检查。经过一些细微的调整后,我们确定的漏洞已经修复好了。
这是媒体库助理插件
在插件选项站点的包含文档中使用反射的GET参数,攻击者可以伪造链接,从而使任意HTML(以及JavaScript)被包含在管理门户中。由于插件的管理页面需要在请求中显示有效的临时随机数,因此对这个漏洞的攻击可能不切实际。
供应商确实做出了非常快速的响应,为反馈的其他已知的漏洞进行了修复。
我们可以识别几乎所有已安装的WordPress实例插件中存在的漏洞。有些插件是免费的,有些不是。在非免费插件中发现了更为严重的漏洞。如前所述,安装WordPress插件可能会对整个网站产生严重的安全影响。
由于PHP插件是作为源代码交付的,所以我建议使用grep
来快速检查,这也许已经识别出可能存在的安全漏洞,使用的grep
命令是:
grep -rain "add_action([ ]*['\"]wp_ajax_"
grep -rain "echo \(\(['][^']*[']\|[\"][^\"]*[\"]\)[ ]*.[ ]*\)*\\$"
grep -rain "<[^>]*echo[ ]\{1,\}\\$"
grep -rain "echo \(\(['][^']*[']\|[\"][^\"]*[\"]\)[ ]*.[ ]*\)*\\$"
也许这些可以帮助您去识别自己实例中易受攻击的WordPress插件。
Cheers!