Critical Vulnerabilities Patched in REHub Theme and Plugin

Published 3 April 2024
Rafie Muhammad
Security Researcher at Patchstack
Table of Contents

This blog post is about the REHub theme and plugin vulnerabilities. If you're a REHub user, please update the plugin to at least version 19.6.2 on both the theme and the plugin.

All paid Patchstack users are protected from this vulnerability. Sign up for the free Community account first, to scan for vulnerabilities and apply protection for only $5 / site per month with Patchstack.

For plugin developers, we have security audit services and Enterprise API for hosting companies.

About the REHub Theme and Plugin

The theme REHub (premium version), which has over 35,000 sales, is known as the more popular price comparison and multi-vendor marketplace theme in WordPress. This theme is bundled with a required REHub Framework plugin. This theme is developed by Sizam Design.

This premium WordPress theme is a modern multipurpose hybrid theme. This theme covers many modern business models for online websites. Each part can be configured and used separately, and we can combine them all in one site.

The security vulnerability

This plugin suffers from multiple critical vulnerabilities and could allow users to include arbitrary local .PHP files and inject a malicious SQL query into a WordPress database query execution.

The first vulnerability is Unauthenticated Local File Inclusion. This vulnerability allows any unauthenticated user to include arbitrary .PHP files that are available on the server. In the worst case, this could lead to a code execution if the user is able to fully or partially control some content on the .PHP files on the server. The second and third vulnerability is Subscriber+ SQL Injection. This vulnerability allows any authenticated user to inject a malicious SQL query into a WordPress database query execution. The described vulnerabilities were fixed in version 19.6.2 and assigned CVE-2024-31231, CVE-2024-31233, and CVE-2024-31234 respectively.

Unauthenticated Local File Inclusion

The underlying vulnerable code exists in the ajax_action_re_filterpost function:

function ajax_action_re_filterpost() {  
    check_ajax_referer( 'filter-nonce', 'security' );
    $args = (!empty($_POST['filterargs'])) ? rh_sanitize_multi_arrays($_POST['filterargs']) : array();
    $innerargs = (!empty($_POST['innerargs'])) ? rh_sanitize_multi_arrays($_POST['innerargs']) : array();
    $offset = (!empty($_POST['offset'])) ? intval( $_POST['offset'] ) : 0;
    $template = (!empty($_POST['template'])) ? sanitize_text_field( $_POST['template'] ) : '';
    $sorttype = (!empty($_POST['sorttype'])) ? rh_sanitize_multi_arrays( $_POST['sorttype'] ) : '';
    $tax = (!empty($_POST['tax'])) ? rh_sanitize_multi_arrays( $_POST['tax'] ) : '';
    $containerid = (!empty($_POST['containerid'])) ? sanitize_text_field( $_POST['containerid'] ) : '';

------------------ CUT HERE ------------------

    if ( $wp_query->have_posts() ) {
        while ($wp_query->have_posts() ) {
            $wp_query->the_post();
            ob_start();
            if(!empty($innerargs)) {extract($innerargs);}
            include(rh_locate_template('inc/parts/'.$template.'.php'));
            $i++;
            $response .= ob_get_clean();
        }
        wp_reset_query();
        if ($i >= $perpage){
            $response .='<div class="re_ajax_pagination"><span data-offset="'.$offsetnext.'" data-containerid="'.$containerid.'"'.$page_sorting.' class="re_ajax_pagination_btn def_btn">' . esc_html__('Next', 'rehub-theme') . '</span></div>';
        } 
    }           
    else {
        $response .= '<div class="clearfix flexbasisclear"><span class="no_more_posts">'.__('No more!', 'rehub-theme').'<span></div>';
    } 

The function itself is attached to the wp_ajax_nopriv_re_filterpost hook which can be accessed by unauthenticated users. Notice that the $template variable is constructed from $_POST['template'] parameter with the insufficient sanitize_text_field function. The sanitize_text_field itself is not enough to prevent a path traversal payload. The $template variable will be included using the include function and will go through the rh_locate_template function first. An unauthenticated user could just simply supply a path traversal payload to include arbitrary local .PHP files.

Subscriber+ SQL Injection

First, let's check the underlying vulnerable code on the REHub theme. It exists in the get_products_title_list function located inside of the rehub-elementor/abstracts/content-base-widget.php file:

public function get_products_title_list() {
    global $wpdb;

    //$post_types = get_post_types( array('public'   => true) );
    //$placeholdersformat = array_fill(0, count( $post_types ), '%s');
    //$postformat = implode(", ", $placeholdersformat);

    $query = [
        "select" => "SELECT SQL_CALC_FOUND_ROWS ID, post_title FROM {$wpdb->posts}",
        "where"  => "WHERE post_type IN ('post', 'product', 'blog', 'page')",
        "like"   => "AND post_title NOT LIKE %s",
        "offset" => "LIMIT %d, %d"
    ];

    $search_term = '';
    if ( ! empty( $_POST['search'] ) ) {
        $search_term = $wpdb->esc_like( $_POST['search'] ) . '%';
        $query['like'] = 'AND post_title LIKE %s';
    }

    $offset = 0;
    $search_limit = 100;
    if ( isset( $_POST['page'] ) && intval( $_POST['page'] ) && $_POST['page'] > 1 ) {
        $offset = $search_limit * absint( $_POST['page'] );
    }

    $final_query = $wpdb->prepare( implode(' ', $query ), $search_term, $offset, $search_limit );
    // Return saved values

    if ( ! empty( $_POST['saved'] ) && is_array( $_POST['saved'] ) ) {
        $saved_ids = $_POST['saved'];
        $placeholders = array_fill(0, count( $saved_ids ), '%d');
        $format = implode(', ', $placeholders);

        $new_query = [
            "select" => $query['select'],
            "where"  => $query['where'],
            "id"     => "AND ID IN( $format )",
            "order"  => "ORDER BY field(ID, " . implode(",", $saved_ids) . ")"
        ];

        $final_query = $wpdb->prepare( implode(" ", $new_query), $saved_ids );
    }

    $results = $wpdb->get_results( $final_query );
    $total_results = $wpdb->get_row("SELECT FOUND_ROWS() as total_rows;");
    $response_data = [
        'results'       => [],
        'total_count'   => $total_results->total_rows
    ];

    if ( $results ) {
        foreach ( $results as $result ) {
            $response_data['results'][] = [
                'id'    => $result->ID,
                'text'  => esc_html( $result->post_title )
            ];
        }
    }

    wp_send_json_success( $response_data );
}

Notice that the $saved_ids variable is constructed from $_POST['saved'] and used on the $new_query["order"] object without proper sanitization. The value then will be constructed on the $final_query variable and will be executed as a SQL query.

An identical case exists in the REHub Framework plugin, the underlying vulnerable code also exists in the get_products_title_list function:

public function get_products_title_list()
{
	global $wpdb;

	$query = [
		"select" => "SELECT SQL_CALC_FOUND_ROWS ID, post_title FROM {$wpdb->posts}",
		"where"  => "WHERE post_type IN ('post', 'product', 'blog', 'page')",
		"like"   => "AND post_title NOT LIKE %s",
		"offset" => "LIMIT %d, %d"
	];

	$search_term = '';
	if (!empty($_POST['search'])) {
		$search_term = $wpdb->esc_like($_POST['search']) . '%';
		$query['like'] = 'AND post_title LIKE %s';
	}

	$offset = 0;
	$search_limit = 100;
	if (isset($_POST['page']) && intval($_POST['page']) && $_POST['page'] > 1) {
		$offset = $search_limit * absint($_POST['page']);
	}

	$final_query = $wpdb->prepare(implode(' ', $query), $search_term, $offset, $search_limit);
	// Return saved values

	if (!empty($_POST['saved']) && is_array($_POST['saved'])) {
		$saved_ids = $_POST['saved'];
		$placeholders = array_fill(0, count($saved_ids), '%d');
		$format = implode(', ', $placeholders);

		$new_query = [
			"select" => $query['select'],
			"where"  => $query['where'],
			"id"     => "AND ID IN( $format )",
			"order"  => "ORDER BY field(ID, " . implode(",", $saved_ids) . ")"
		];

		$final_query = $wpdb->prepare(implode(" ", $new_query), $saved_ids);
	}

	$results = $wpdb->get_results($final_query);
	$total_results = $wpdb->get_row("SELECT FOUND_ROWS() as total_rows;");
	$response_data = [
		'results'       => [],
		'total_count'   => $total_results->total_rows
	];

	if ($results) {
		foreach ($results as $result) {
			$response_data['results'][] = [
				'value'    => $result->ID,
				'id'    => $result->ID,
				'label'  => esc_html($result->post_title)
			];
		}
	}

	wp_send_json_success($response_data);
}

Note that all of the vulnerabilities are reproducible on a default installation and activation of the REHub theme and REHub Framework plugin with a requirement of the Elementor plugin installation.

The patch

For the Unauthenticated Local File Inclusion vulnerability, the vendor decided to add sanitize_file_name function to sanitize the $template variable. The patch can be seen below:

For both of the Subscriber+ SQL Injection vulnerabilities, the vendor decided to apply an integer cast to the $saved_ids variable using intval.

Conclusion

The vulnerabilities discussed here underscore the importance of securing all aspects of a plugin, especially those designed for local file inclusion and SQL query execution.

In the context of SQL query execution, we recommend developers to force cast the value to integer if the intended value is indeed should be an integer value before constructing the value to the SQL query. We can also use $wpdb->prepare() statement with specifying "%d" as the input format.

In the context of local file inclusion, we recommend applying a sanitization using sanitize_file_name function to prevent path traversal and additionally apply a strict whitelist check to only allow certain files to be included.

Timeline

17 March, 2024We found the vulnerability and starting to create a reports.
18 March, 2024We reach out to the theme vendor regarding the discovered vulnerabilities.
19 March, 2024REHub theme and plugin version 19.6.2 released to patch the reported issues.
03 April , 2024Added the vulnerabilities to the Patchstack vulnerability database. Security advisory article published.

Help us make the Internet a safer place

Making the WordPress ecosystem more secure is a team effort, and we believe that plugin developers and security researchers should work together.

  • If you're a plugin developer, join our mVDP program that makes it easier to report, manage and address vulnerabilities in your software.
  • If you're a security researcher, join Patchstack Alliance to report vulnerabilities & earn rewards.

The latest in Security Advisories

Looks like your browser is blocking our support chat widget. Turn off adblockers and reload the page.
crossmenu