关于WordPressSecurity的几点注意事项

我们先来看看WordPress的CVE列表,大多数漏洞都不是在WordPressCore里面找到的,而是在第三方插件和主题中。

今天,我们来谈谈WordPress。

对WordPress进行评估可能看起来无聊至极,因为核心功能[已测试]和配置是不允许大范围的安全性错误配置存在的。幸运的是,大多数实例使用的是插件和主题来添加WordPress核心程序尚未提供的功能。

在这篇博文中,我想讨论一下我的发现以及发现它们的方法。此外,我将描述不同厂商的响应情况,从完全没有反应、不理解问题到作出既快速又专业的反应,并要求对准备部署的更新的代码进行审查。

WordPress允许插件通过两种方式注册API路由——要么是为已经经过身份认证的calls注册前缀wp_ajax_,要么是为尚未经过身份认证的calls注册前缀wp_ajax_nopriv_(请参阅API插件)

EventON

EventON提供了一个日历插件作为核心和多个附加插件以扩展它的功能,包括预订、RSS订阅、票务系统、审查系统和CSV文件导入。

评估的系统仅安装了核心程序和CSV导入程序,但这两个插件都有漏洞,接下来我们会进行讨论。

漏洞

首先,我们来看看eventON的核心插件。使用下面的代码可以为evo_mdtAJAX操作注册一个处理程序:

1
2
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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输出。

1
2
3
4
5
6
7
8
9
10
11
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操作:

1
grep -rain "add_action([ ]*['\"]wp_ajax_"

这使我们对可用的特权和非特权行为有一个相当好的概述。快速查看处理程序可能会显示如上面所描述的漏洞。

我们来看看CSV导入器。由于插件目的是创建新的事件,因此对用户访问应该进行授权限制。使用上面提到的grep命令,可以得到以下内容:

1
2
3
$ 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操作。检查代码发现:

1
2
3
4
5
6
7
$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 ) );
}

以及处理函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

1
grep -rain "\\$_\(GET\|POST\)"

快速检查CSV导入程序会发现另一个可能存在的漏洞

1
2
3
4
5
$ 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;
[...]

为了验证这一行是易受攻击的,我们看一下文件:

1
2
3
4
5
6
7
8
[...]
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

顾名思义,Google Map Pro将谷歌地图集成到WordPress页面。

漏洞

这个漏洞是通过下面的grep行找到的,grep行基本功能是搜索已打开的标签(PHP或HTML),后面跟着变量的回显。

1
grep -rain "]*echo[ ]\{1,\}\\$"

现在的输出应该包含变量被写入响应中的大多数事件。最后,我在我的输出中找到了下面的行:

core/class.plugin-overview.php:165:

data-current-product-slug=productSlug; ?> data-product-version = productVersion; ?> data-product-name = “productName; ?>” >

快速检查一下回显变量

1
$skin = $_GET['skin'];

使用此参数,攻击者可以伪造包含简单有效负载的链接,以在管理员的上下文中执行JavaScript。

联系供应商

供应商确实联系了我们说漏洞已经修复,还说漏洞并不重要,因为只有管理门户受到了影响!?

Jupiter

Jupiter是WordPress的一个主题,它具有一个WYSIWYG编辑器的页面元素。

漏洞

为了在我们的grep输出中展示这个漏洞,我们修改正则表达式,使之包含将字符串与变量连接起来的回显。

1
grep -rain "echo \(\(['][^']*[']\|[\"][^\"]*[\"]\)[ ]*.[ ]*\)*\\$"

从这一点来看,如果它们被污染(包含用户控制的输入),则必须检查所有级联变量。使用给定的命令多行输出POST请求的元数据的行为是可以被识别的。将元数据设置为适合的payload,编辑器就可以在显示博客帖子时执行JavaScript代码,而不会在管理界面中显示。

联系供应商

Artbees响应确实迅速,并且提供了一个PHP文件补丁供我们检查。经过一些细微的调整后,我们确定的漏洞已经修复好了。

媒体库助理

这是媒体库助理插件

漏洞

在插件选项站点的包含文档中使用反射的GET参数,攻击者可以伪造链接,从而使任意HTML(以及JavaScript)被包含在管理门户中。由于插件的管理页面需要在请求中显示有效的临时随机数,因此对这个漏洞的攻击可能不切实际。

联系供应商

供应商确实做出了非常快速的响应,为反馈的其他已知的漏洞进行了修复。

结论

我们可以识别几乎所有已安装的WordPress实例插件中存在的漏洞。有些插件是免费的,有些不是。在非免费插件中发现了更为严重的漏洞。如前所述,安装WordPress插件可能会对整个网站产生严重的安全影响。

建议

由于PHP插件是作为源代码交付的,所以我建议使用grep来快速检查,这也许已经识别出可能存在的安全漏洞,使用的grep命令是:

1
2
3
4
grep -rain "add_action([ ]*['\"]wp_ajax_"
grep -rain "echo \(\(['][^']*[']\|[\"][^\"]*[\"]\)[ ]*.[ ]*\)*\\$"
grep -rain "<[^>]*echo[ ]\{1,\}\\$"
grep -rain "echo \(\(['][^']*[']\|[\"][^\"]*[\"]\)[ ]*.[ ]*\)*\\$"

也许这些可以帮助您去识别自己实例中易受攻击的WordPress插件。


本文翻译自:https://insinuator.net/2018/08/a-few-notes-on-wordpress-security/


Cheers!

文章作者: Ginove
文章链接: https://ginove.github.io/2018/09/09/关于WordPressSecurity的几点注意事项/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ginove