Critical RCE Patched in Bricks Builder Theme

Published 19 February 2024
Rafie Muhammad
Security Researcher at Patchstack
Table of Contents

The vulnerability in the Bricks Builder Theme was originally reported by snicco to the Patchstack bug bounty program for WordPress. We are collaborating with the researcher to release the content of this security advisory article.

This blog post is about the Bricks Builder Theme vulnerability. If you’re a Bricks Builder Theme user, please update the plugin to at least version

You can sign up for the Patchstack Community plan to be notified about vulnerabilities as soon as they become disclosed. Patchstack users with protection enabled have been protected from this vulnerability.

For plugin developers, we have security audit services and Threat Intelligence Feed API for hosting companies.

What to do when you have a vulnerable plugin installed?

  1. Check if there’s an available update for the plugin. Developers often release patches to fix vulnerabilities. Update to the latest version as soon as possible.
  2. If an update isn’t available or if the vulnerability poses a significant risk remove the plugin from your site. This will prevent any potential exploitation until a fix is available.
  3. Enable protection from your Patchstack account, so this and future vulnerabilities would get automatic virtual patches and they would not be exploited.

About the Bricks Builder theme

The theme Bricks Builder (premium version) is estimated to have around 25,000 currently active installations. The Bricks Builder theme is known as the more popular premium site builder theme.

Bricks Builder Theme

Bricks Builder theme is proclaimed to be an innovative, community-driven, visual site builder for WordPress. This theme enables users to design unique, performant, and scalable websites with a Code-free approach.

The security vulnerability

This plugin suffers from an unauthenticated Remote Code Execution (RCE) vulnerability. This vulnerability allows any unauthenticated user to execute arbitrary PHP code on the WordPress site. The described vulnerability was fixed in version and assigned CVE-2024-25600.

Unauthenticated RCE

The underlying vulnerable code exists in the prepare_query_vars_from_settings function:

public static function prepare_query_vars_from_settings( $settings = [], $fallback_element_id = '' ) {
	$query_vars = $settings['query'] ?? [];

	// Some elements already built the query vars. (carousel, related-posts) (@since 1.9.3)
	if ( isset( $query_vars['bricks_skip_query_vars'] ) ) {
		return $query_vars;

	// Unset infinite scroll
	if ( isset( $query_vars['infinite_scroll'] ) ) {
		unset( $query_vars['infinite_scroll'] );

	// Unset isLiveSearch (@since 1.9.6)
	if ( isset( $query_vars['is_live_search'] ) ) {
		unset( $query_vars['is_live_search'] );

	// Do not use meta_key if orderby is not set to meta_value or meta_value_num (@since 1.8)
	if ( isset( $query_vars['meta_key'] ) ) {
		$orderby = isset( $query_vars['orderby'] ) ? $query_vars['orderby'] : '';
		if ( ! in_array( $orderby, [ 'meta_value', 'meta_value_num' ] ) ) {
			unset( $query_vars['meta_key'] );

	$object_type = self::get_query_object_type();
	$element_id  = self::get_query_element_id();

		* Use PHP editor
		* Returns PHP array with query arguments
		* Supported if 'objectType' is 'post', 'term' or 'user'.
		* No merge query.
		* @since 1.9.1
	if ( isset( $query_vars['useQueryEditor'] ) && ! empty( $query_vars['queryEditor'] ) && in_array( $object_type, [ 'post','term','user' ] ) ) {
		$post_id                      = Database::$page_data['preview_or_post_id'];
		$php_query_raw                = bricks_render_dynamic_data( $query_vars['queryEditor'], $post_id );
		$query_vars['posts_per_page'] = get_option( 'posts_per_page' );

		// Define an anonymous function that simulates the scope for user code
		$execute_user_code = function () use ( $php_query_raw ) {
			$user_result = null; // Initialize a variable to capture the result of user code

			// Capture user code output using output buffering
			$user_result = eval( $php_query_raw ); // Execute the user code
			ob_get_clean(); // Get the captured output

			return $user_result; // Return the user code result
---------------------- CUTTED HERE ----------------------

Notice that there is an eval function call with $php_query_raw supplied as the parameter. The $php_query_raw itself is constructed from $query_vars['queryEditor'] value that will be processed first by bricks_render_dynamic_data function.

The prepare_query_vars_from_settings function itself can be called from several processes in the code. One of it is coming from __construct function inside the Query class:

public function __construct( $element = [] ) {

	$this->element_id = ! empty( $element['id'] ) ? $element['id'] : '';

	// Check for stored query in query history (@since 1.9.1)
	$query_instance = self::get_query_by_element_id( $this->element_id );

	if ( $query_instance ) {
		// Assign the history query instance properties to this instance, avoid running the query again
		foreach ( $query_instance as $key => $value ) {
			if ( $key === 'id' ) {
			$this->$key = $value;
	} else {
		$this->object_type = ! empty( $element['settings']['query']['objectType'] ) ? $element['settings']['query']['objectType'] : 'post';

		// Remove object type from query vars to avoid future conflicts
		unset( $element['settings']['query']['objectType'] );

		$this->settings = ! empty( $element['settings'] ) ? $element['settings'] : [];

		// STEP: Set the query vars from the element settings (@since 1.8)
		$this->query_vars = self::prepare_query_vars_from_settings( $this->settings );
---------------------- CUTTED HERE ----------------------

This Query class also could be called from several processes in the code. If we trace back the process call of one of the cases, we can see that this class first can be called from function render_element (“includes/api.php”) => function render_element (“includes/ajax.php”) => function init (“includes/elements/base.php”) => function render() (“includes/elements/posts.php”) => new Query()

If we check function render_element (“includes/api.php”), it is actually a function to handle one of the registered REST API endpoints:

public function rest_api_init_custom_endpoints() {
	// Server-side render (SSR) for builder elements via window.fetch API requests
			'methods'             => 'POST',
			'callback'            => [ $this, 'render_element' ],
			'permission_callback' => [ $this, 'render_element_permissions_check' ],
---------------------- CUTTED HERE ----------------------

The API endpoint is already protected by render_element_permissions_check function that is applied as the function to check permission on the permission_callback parameter. Let’s see how the function handles the permission:

public function render_element_permissions_check( $request ) {
	$data = $request->get_json_params();

	if ( empty( $data['postId'] ) || empty( $data['element'] ) || empty( $data['nonce'] ) ) {
		return new \WP_Error( 'bricks_api_missing', __( 'Missing parameters' ), [ 'status' => 400 ] );

	$result = wp_verify_nonce( $data['nonce'], 'bricks-nonce' );

	if ( ! $result ) {
		return new \WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie check failed' ), [ 'status' => 403 ] );

	return true;

The function only checks for a nonce value and no proper permission or role check is applied. This bricks-nonce value itself is publicly available on the front end of a WordPress site.

With this condition, any unauthenticated user can fetch the nonce and trigger the Remote Code Execution (RCE).

Note that this vulnerability can be reproduced with an Unauthenticated user with the default installation configuration of the theme.

Disclosure note

We decided to release the technical advisory article on this vulnerability early since we have detected active exploitation in our monitoring system. This Bricks Builder Theme vulnerability is currently being exploited and we are seeing attacks from several IP addresses, most of the attacks are from the following IP addresses:


We are also aware of one of the malware that is specifically used on a post-exploitation process of this vulnerability. This malware has a built-in feature to disable some of the security-related plugins such as Wordfence and Sucuri.

The patch

The patch includes an implementation of a permission check on the render_element_permissions_check function. The patch also implements a sanitization on the $php_query_raw using the Helpers::sanitize_element_php_code function. The patch can be seen below:

Bricks Builder Theme vulnerability
Bricks Builder Theme vulnerability


In general, we recommend not to utilize the eval function on a plugin and theme development process. If the process still needs an eval function to be processed, we recommend applying a very strict permission or role check when accessing the feature and also applying filtering and sanitization on the supplied PHP code.


10 February, 2024We receive the vulnerability report from snicco and reached out to the Bricks Builder team.
12 February, 2024Bricks Builder team send us a proposed patch and we are able to validate the patch. We deploy a vPatch for this vulnerability to protect our customers.
13 February, 2024Bricks Builder version released to patch the reported issue. We add the vulnerability to the Patchstack vulnerability database.
14 February, 2024First exploitation attempts confirmed on our monitoring system.
19 February, 2024Security advisory article publicly released.

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 which makes it easier to report, manage, and address vulnerabilities in your software.
  • If you’re a security researcher, join the Patchstack Alliance community and report vulnerabilities to our bug bounty program to earn rewards.

The latest in Security Advisories

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