WordPress SQL注入漏洞与提权分析

  • 时间:
  • 浏览:1

WordPress核心功能SQL注入漏洞分析

威胁响应中心研究员对Wordpress核心功能SQL注入漏洞(编号为CVE-2015-5623和CVE-2015-2213)进行了全部的分析

0x00 漏洞概述

在twitter上看了Wordpress核心功能再次出现SQL注入漏洞,想学习下,就深入的跟了下代码,结果发现日本日本外国外国网友留了好大的有一3个多多多坑。觉得觉得存在注入问题图片图片,或者却这样像他blog中所说的那样要能了通过订阅者这样 的低权限来触发SQL注入漏洞。

统统Wordpress漏洞系列的文章目前更新了有一3个多多多次要,有一3个多多多是通过权限绕过实现订阅者权限写一篇文章到回收站,这样 或者通过写入的这篇文章来实现SQL注入漏洞。这有一3个多多多漏洞的描述,TSRC的phithon写的文章觉得或者很清楚了,我这里从我分析的深度来介绍这有一3个多多多漏洞的形成、利用以及phithon省略掉的原文次要内容。

0x01 越权提交文章

_wpnonce的获取

在讲越权漏洞前一天 ,我们要能了介绍一下Wordpress后台的_wpnonce参数,统统参数主或者用来处里CSRF攻击的token。后台大多数敏感功能要能了通过,当前用户信息、功能名称、操作对象id等内容生成token,统统统统我们都不难 在这样token的前一天 进行统统功能的使用。统统CSRF的防护机制间接地导致 着前一天 SQL注入不难 再低权限情况汇报下触发(或者看要能了token),上面讲SQL注入漏洞时,我们都会全部的聊统统问题图片图片有多坑。

并不一定要把_wpnonce的获取放在前面讲,不是则,我们要能了有一3个多多多你会能了们要能了使用编辑提交文章功能的token。统统功能的token我们要能了通过访问后台post.php的post-quickdraft-save功能来获取,严格的来说统统获取最好的法律法律依据就说 我是有一3个多多多信息泄露漏洞,官方也在新版本中进行了修补。下面我我们都来看下统统token泄露的导致 着。次要代码如下:

case 'post-quickdraft-save':
    // Check nonce and capabilities
    $nonce = $_REQUEST['_wpnonce'];
    $error_msg = false;
 
    // For output of the quickdraft dashboard widget
    require_once ABSPATH . 'wp-admin/includes/dashboard.php';
 
    if ( ! wp_verify_nonce( $nonce, 'add-post' ) )
        $error_msg = __( 'Unable to submit this form, please refresh and try again.' );
 
    if ( ! current_user_can( 'edit_posts' ) )
        $error_msg = __( 'Oops, you don’t have access to add new drafts.' );
 
    if ( $error_msg )
        return wp_dashboard_quick_press( $error_msg );

从上面代码我们要能了看出统统功能在发现再次出现错误时,会将错误信息通过wp_dashboard_quick_press统统函数打印到页面上,而统统函数生成的页面的代码涵盖这样 一行:

  • PHP wp_nonce_field( 'add-post' ); 1 wp_nonce_field('add-post');

生成了add-post功能的_wpnonce,也或者说,即使我们都做了统统被禁止的操作,返回页面中也会再次出现统统_wpnonce。

越权提交与条件竞争

如phithon文章所说,或者GP使用逻辑混乱而导致 着的验证绕过。我们都来看post.php文件在刚开始检验文章不是存在的代码:

if ( isset( $_GET['post'] ) )
    $post_id = $post_ID = (int) $_GET['post'];
elseif ( isset( $_POST['post_ID'] ) )
    $post_id = $post_ID = (int) $_POST['post_ID'];
else
    $post_id = $post_ID = 0;
 
global $post_type, $post_type_object, $post;
 
if ( $post_id )
    $post = get_post( $post_id );
 
if ( $post ) {
    $post_type = $post->post_type;
    $post_type_object = get_post_type_object( $post_type );
}

要能了看了在先提取GET中的post参数作为文章id来提取文章信息,或者这样GET的post参数,再去用POST的post_ID参数来提取。而真正检验用户不是对文章具有编辑权限,则是在edit_post中进行的,而这里的判断参数是从POST中提取的:

function edit_post( $post_data = null ) {
    global $wpdb;
 
    if ( empty($post_data) )
        $post_data = &$_POST;
 
    // Clear out any data in internal vars.
    unset( $post_data['filter'] );
 
    $post_ID = (int) $post_data['post_ID'];
    $post = get_post( $post_ID );
    $post_data['post_type'] = $post->post_type;
    $post_data['post_mime_type'] = $post->post_mime_type;
 
    if ( ! empty( $post_data['post_status'] ) ) {
        $post_data['post_status'] = sanitize_key( $post_data['post_status'] );
 
        if ( 'inherit' == $post_data['post_status'] ) {
            unset( $post_data['post_status'] );
        }
    }
 
    $ptype = get_post_type_object($post_data['post_type']);
    if ( !current_user_can( 'edit_post', $post_ID ) ) {
        if ( 'page' == $post_data['post_type'] )
            wp_die( __('You are not allowed to edit this page.' ));
        else
            wp_die( __('You are not allowed to edit this post.' ));
    }

我们都继续看这段代码最后的if判断,判断当前用户不是有编辑这篇文章的权限。统统判断最终的操作是在map_meta_cap统统函数中进行的:

case 'edit_post':
    case 'edit_page':
        $post = get_post( $args[0] );
        if ( empty( $post ) )
            break;
 
        if ( 'revision' == $post->post_type ) {
            $post = get_post( $post->post_parent );
        }

要能了看出或者文章是不存在的,这样就会break出switch,在函数刚开始时返回$caps变量,而$caps在函数刚开始的前一天 或者被定义为有一3个多多多空的数组了,也或者说,这里会返回有一3个多多多空数组。下面我们都来向前走,返回到调用map_meta_cap的has_cap函数中,看看了续的操作

public function has_cap( $cap ) {
        if ( is_numeric( $cap ) ) {
            _deprecated_argument( __FUNCTION__, '2.0', 

__('Usage of user levels by plugins and themes is deprecated. Use roles and capabilities instead.') ); $cap = $this->translate_level_to_cap( $cap ); } $args = array_slice( func_get_args(), 1 ); $args = array_merge( array( $cap, $this->ID ), $args ); $caps = call_user_func_array( 'map_meta_cap', $args ); // Multisite super admin has all caps by definition, Unless specifically denied. if ( is_multisite() && is_super_admin( $this->ID ) ) { if ( in_array('do_not_allow', $caps) ) return false; return true; } $capabilities = apply_filters( 'user_has_cap', $this->allcaps, $caps, $args, $this ); $capabilities['exist'] = true; // Everyone is allowed to exist foreach ( (array) $caps as $cap ) { if ( empty( $capabilities[ $cap ] ) ) return false; } return true; }

看最后那个foreach,它检测$caps中的各个元素在$capabilities中不是有不存在的,或者有这样就return false,或者$caps是个空数组,这样 很容易我们都就获得了有一3个多多多true,权限校验成功绕过。这样这样 我们要能了得到的有一3个多多多信息或者,我们要能了通过统统不够去尝试update有一3个多多多何必 存在的文章。

这样现在问题图片图片来了,直接update有一3个多多多不存在的文章是这样意义的,或者数据库执行SQL是肯定会报错,我们都要怎么会会要能成功创建一篇文章呢?

在校验权限前一天 ,数据库执行操作前一天 ,post.php涵盖这样 一段代码:

if ( isset( $post_data['tax_input'] ) ) {
        foreach ( (array) $post_data['tax_input'] as $taxonomy => $terms ) {
            // Hierarchical taxonomy data is already sent as term IDs, so no conversion is necessary.
            if ( is_taxonomy_hierarchical( $taxonomy ) ) {
                continue;
            }
 
            if ( ! is_array( $terms ) ) {
                $comma = _x( ',', 'tag delimiter' );
                if ( ',' !== $comma ) {
                    $terms = str_replace( $comma, ',', $terms );
                }
                $terms = explode( ',', trim( $terms, " \n\t\r\x0B," ) );
            }
 
            $clean_terms = array();
            foreach ( $terms as $term ) {
                // Empty terms are invalid input.
                if ( empty( $term ) ) {
                    continue;
                }
 
                $_term = get_terms( $taxonomy, array(
                    'name' => $term,
                    'fields' => 'ids',
                    'hide_empty' => false,
                ) );

或者在POST的数据中存在名为tax_input的数组参数,这样就对参数中的值使用逗号进行切割,或者对分割出来的每个内容进行一次select查询。这样这里我们要能了想象一下,或者我们都post_ID中填写的是对当前最新文章ID+1的数值,或者在tax_input统统参数加进去去很重多的内容,导致 着它不停地select查询,我们都是要能了在统统停滞的时间当中插入一篇文章(这篇文章的ID刚好是最新文章ID+1),这样上面的update是要能了要能了意义了?