Unpatched Authenticated RCE in Oxygen and Breakdance Builder

Published 4 April 2024
Updated 26 August 2024
Table of Contents

Updates since April 4, 2024

  • April 6th, 2024 – Patchstack received an email from Oxygen containing a new Breakdance plugin version.
  • April 8th, 2024 – Patchstack validated this new Breakdance version; it completely removed the ability for non-administrators to use the Breakdance editor and they now state that their role management feature will be reworked. Patchstack replied stating that this is acceptable, but we will keep an eye out on future versions with this rework. Patchstack also stated the vulnerability entry will be updated as soon as this version is released.
  • April 9th, 2024 – Patchstack received version 1.7.1 of Breakdance to validate. Patchstack approved this version and communicated that it is awaiting its public release.
  • April 30th, 2024 – Breakdance released version 1.7.1 to resolve an unrelated security vulnerability and they did not include the patch they proposed to us last time completely setting us back to where we were to begin with. We bumped the affected version of Breakdance to 1.7.1. Note that this unrelated security vulnerability can be chained to accomplish the RCE; https://www.youtube.com/watch?v=9glx54-LfRE
  • May 2nd, 2024 – Patchstack sent another email to the Oxygen/Breakdance team asking about an update on the release status for the patch they previously sent for review.
  • May 7th, 2024 – A new version of Breakdance 1.7.2 was released, and the security advisory only mentioned a fix for an unrelated privilege escalation vulnerability. This remote code execution vulnerability was not mentioned. In addition, we have not received any reply to the email we sent on May 2nd, 2024.
  • May 16th, 2024 – Patchstack sent an email to the Oxygen team regarding an unrelated vulnerability, additionally, we asked about the status of this vulnerability. Patchstack has still not received any response since April 9th, 2024.
  • June 17th, 2024 – Patchstack has still not received any response since April 9th, 2024. At this point, we assume they will refuse to apply a proper fix for these vulnerabilities, and we will stop updating this blog post in addition to stopping our contact attempts to the Oxygen team. The Oxygen team is still welcome to contact us to provide us with a proper patch for these vulnerabilities.

This blog post is about an unpatched Remote Code Execution (RCE) vulnerability discovered in Oxygen and Breakdance builder. At the time of publication of this security advisory article, there is still no patch available on the latest version of the affected components. We hope that the developer will be implementing the proper security measures soon. In this article we will be explaining why this is important.

About the Oxygen and Breakdance Builder

The Oxygen and Breakdance builders (premium version) are two popular page builder plugins for WordPress. Both are owned & maintained by the same company – Soflyy.

The security vulnerability

These two builders are affected by an authenticated Remote Code Execution (RCE) vulnerability. This issue enables the lowest-permission user on both components to execute arbitrary PHP code. Despite both vendors insisting that this is an intended feature, permitting arbitrary code execution by the lowest-permission user on the component should not be allowed and goes against best security practices.

This vulnerability persists in the latest versions of Oxygen Builder (version 4.9) and Breakdance Builder (version 1.7.2). The aforementioned vulnerabilities are designated as CVE-2024-31380 and CVE-2024-31390.

Authenticated RCE in Oxygen

By default, Oxygen Builder restricts access to its features to users with the Administrator role. However, administrators have the option to grant permissions to specific roles on the WordPress site through the Client Control feature.

There is indeed a note regarding an Important Security Warning that essentially warns users that granting a user with Oxygen access could allow the user to execute arbitrary PHP code. Additionally, the vendor also has specific documentation regarding this feature that can be accessed here. Here is how the documentation looked like before we reported the vulnerability to them:

After receiving our report, the vendor seemingly understood it’s a security issue, but asked wether additional warnings would lower the CVSS score. Later, they became determined that a patch was unnecessary, asserting that the issue was an intended feature and that warning the users about the security risk is enough.

Subsequently, they updated the documentation as follows:

Notice on the previous version of the documentation, the Edit Only permission on the Client Control feature is supposed to be “safe to let a client into Oxygen as they can only change styling and content. There’s no access to code …”.

In many cases, administrators may grant limited access to the page builder of their WordPress site to agencies or contributors. The minimum realistic scenario typically involves providing ‘Edit Only‘ access to a Contributor role or higher, depending on the circumstances.

With this condition, a lower-permission user on the WordPress site can execute arbitrary PHP code, potentially leading to a complete takeover of the site and server. This deviates from the standard behavior of WordPress and contradicts the core principles of access control, wherein only users with an Administrator role or higher should be permitted to execute arbitrary PHP code.

On the technical side, let’s check how a user with Edit Only permission is able to execute arbitrary PHP code. One of the ways to achieve this is via the oxygen_vsb_current_user_can_access function:

<?php

function ct_exec_code() {
	$nonce  	= $_REQUEST['nonce'];
	$post_id 	= intval( $_REQUEST['post_id'] );

	// check nonce
	if ( ! isset( $nonce, $post_id ) || ! wp_verify_nonce( $nonce, 'oxygen-nonce-' . $post_id ) ) {
	    // This nonce is not valid.
	    die( 'Security check' ); 
	}

	// check user role
	if ( ! oxygen_vsb_current_user_can_access() ) {
		die( 'Security check' );
	}

	// get all data JSON
	$data = file_get_contents('php://input');

	// encode and separate tree from options
	$data = json_decode($data, true);

	$code = $data['code'];
	$term = isset($data['term']) ? $data['term'] : false;
	$post = isset($data['post']) ? $data['post'] : false;

	$code = oxygen_base64_decode_for_json($code);

	// archive template
	if ( $term ) {
		
		$term = json_decode(stripcslashes($term), true);

		/**
		 * Archives
		 */
		
		if ( isset( $term["term_id"] ) ) {

			// get all the registered taxonomies
			$taxonomies = get_taxonomies( array() , 'objects' );

			$query = array (
				/** query_var of the registered taxonomy will act as a key here, 
				 *	e.g. for category the query_var is category_name
				 */
				$taxonomies[$term['taxonomy']]->query_var => $term['slug']
			);
		}

		/**
		 * Post types
		 */
		
		else {
			$query = array( 'post_type' => $term['name'] );
		}

	}
	// single template
	elseif ( $post ) {

		/**
		 * $post is WP_Post object, need to reproduce WP_Query for this post
		 */

		$query = get_query_vars_from_id($post["ID"]);
	}
	// not template
	else {

		$query = isset($_REQUEST['query']) ? $_REQUEST['query'] : "";
		$query = json_decode(stripcslashes($query), true);
	}

	// simulate WP Query
	global $wp_query;
	$wp_query = new WP_Query($query);

	//var_dump($wp_query); // this seems to be OK

	// check for code
	if ( $code ) {
		eval( ' ?>' . $code . '<?php ' );
	}
	else {
		_e('No code found', 'component-theme');
	}

	/* Restore original Post Data. Do we actually need this? */
	wp_reset_postdata();

	die();
}

This function is attached to the wp_ajax_ct_exec_code hook. The function itself basically will check the nonce value first, which can be fetched from the builder view. Then, the function will check the user permission using the oxygen_vsb_current_user_can_access function:

function oxygen_vsb_current_user_can_access()
{

    // Its the super man
    if (is_multisite() && is_super_admin()) {
        return true;
    }

    $user = wp_get_current_user();

    if (!$user) {
        return false;
    }

    $user_edit_mode = oxygen_vsb_get_user_edit_mode();
    if ($user_edit_mode === "true" || $user_edit_mode == 'edit_only') {
        return true;
    }
    if ($user_edit_mode === "false") {
        return false;
    }

    if ($user && isset($user->roles) && is_array($user->roles)) {
        foreach ($user->roles as $role) {
            if ($role == 'administrator') {
                return true;
            }
            $option = get_option("oxygen_vsb_access_role_$role", false);
            if ($option && ($option == 'true' || $option == 'edit_only')) {
                return true;
            }
        }
    }

    return false;
}

The function will return true if the user has Edit Only permission ( $user_edit_mode == ‘edit_only’). Let’s go back to the ct_exec_code function. This function simply will execute the $code variable inside of the eval function. Per PHP’s documentation:

Authenticated RCE in Breakdance

Similar to the Oxygen code execution functionality, the Breakdance builder also allows the administrator user to assign Edit Content Interface Only:

The vendor also put some warnings on their documentation here:

Meanwhile, the Edit Content Interface Only permission itself is the lowest permission available on the builder.

In most cases, administrators grant limited access to the page builder of their WordPress site to agencies or contributors. The minimum realistic scenario usually involves providing ‘Edit Content Interface Only‘ access to a Contributor role or higher, depending on the circumstances. With this setup, a lower-permission user on the WordPress site can execute arbitrary PHP code, potentially leading to a complete takeover of the site and server. This contradicts the standard behavior of WordPress and the core principles of access control, which dictate that only users with an Administrator role or higher should be allowed to execute arbitrary PHP code.

From the technical side, one way to achieve the code execution is via the serverSideRender function:

function serverSideRender($propertiesJsonString, $parentPropertiesJsonString, $elementSlug)
{
    /**
     * Silence operator will be removed after https://github.com/soflyy/breakdance/issues/722 completion
     * @var PropertiesData
     */
    $properties = @json_decode($propertiesJsonString, true);
    /**
     * @var PropertiesData
     */
    $parentProperties = @json_decode($parentPropertiesJsonString, true);

    if ($elementSlug && class_exists($elementSlug)) {
        /**
         * @psalm-suppress MixedMethodCall
         * @var \Breakdance\Elements\Element
         */
        $element = new $elementSlug();
        $ssrResult = $element::ssr($properties ?: [], $parentProperties ?: [], true);

        /**
         * @var string $ssrResult
         * @psalm-suppress TooManyArguments
         */
        $ssrResult = apply_filters("breakdance_builder_ssr_rendered_html", $ssrResult, $properties, $elementSlug, $parentProperties);

        return [
            'html' => $ssrResult,
            'postsGeneratedCssFilePaths' => (object) ScriptAndStyleHolder::getInstance()->getPostsGeneratedCssFilePaths(),
            'dependencies' => ScriptAndStyleHolder::getInstance()->dependencies
        ];

    } else {
        throw new \Exception("Cant render element that doesnt exist -  {$elementSlug}");
    }
}

The function itself is hooked to the breakdance_server_side_render action:

add_action('breakdance_loaded', function () {
    \Breakdance\AJAX\register_handler(
        'breakdance_server_side_render',
        '\Breakdance\Render\serverSideRender',
        'edit',
        true,
        [
            'args' => [
                'properties' => FILTER_UNSAFE_RAW,
                'parentProperties' => FILTER_UNSAFE_RAW,
                'elementSlug' => FILTER_UNSAFE_RAW,
            ]
        ]
    );
});

Users can insert their PHP code payload inside the $parentPropertiesJsonString variable. Depending on the $elementSlug that is used, it will call the ssr function. In this case, we are able to use EssentialElements\CodeBlock as the $elementSlug and it will call the ssr function and eventually, it will include subplugins/breakdance-elements/elements/Code_Block/ssr.php file:

<?php
/**
 * @var array $propertiesData
 */

/* helper function for security...
WordPress shortcode injection as an attack vector
Some irresponsible plugin could somehow let a shortcode display a breakdance elements Let's ensure that isn't a security problem */ if (!function_exists('is_inside_shortcode')) { function is_inside_shortcode() { // TODO write this function? return apply_filters('breakdance_php_code_block_is_inside_shortcode', false); } } if (is_inside_shortcode()) { echo "For security reasons, Breakdance's PHP Code Block element is disabled inside shortcodes by default. " . "Override this limitation by filtering breakdance_php_code_block_is_inside_shortcode and returning false."; } else { $code = "?>" . ($propertiesData['content']['content']['php_code'] ?? ''); try { eval($code); } catch (\ParseError $e) { echo 'An error occurred inside the PHP Code Block element: <br />'; echo 'Caught exception: ' . $e->getMessage() . "\n"; echo 'Line: '; echo $e->getLine(); echo "<br />"; } } /* what do we do if the user outputs invalid html with missing closing tags, etc? this won't break the builder, but it will break their frontend */ ?>

The file above will simply execute the value from our input previously in $propertiesData[‘content’][‘content’][‘php_code’] with the eval function. Per PHP’s documentation:

The proposed patch

We recommend implementing a more granular access control by introducing a specific role solely for executing arbitrary PHP code in both Oxygen and Breakdance builders. This permission should only be assignable to users with an Administrator role or higher. Additionally, implementing code signing is advised (and also already present in other page builder tools.)

The objective is to avoid granting low-role users’ capabilities equal to or higher than those of users with administrative privileges (and not an all-or-nothing approach). Following the principles of Least Privilege and Discretionary Access Control, software should adhere to this approach, allowing users to accomplish their authorized tasks with lower permissions and without the ability to inject and execute raw arbitrary PHP code.

According to Calvin Alkan (Snicco) who found similar vulnerabilities in other similar page builder plugins – it is easy to patch.

The issue is not complicated to fix either, as demonstrated by Cwicly & Bricks who patched similar issues within days.

https://snicco.io/vulnerability-disclosure/cwicly/remote-code-execution-cwicly-1-4-0-2#patch

Communication surrounding the issue

On April 2, 2024, Soflyy, the company behind both plugins, published a related article and was denying the presence of a vulnerability in both the Oxygen and Breakdance plugins, which we had reported to them and were planning to disclose. As a result, users of both Oxygen and Breakdance have reached out to us, prompting us to provide additional context and reveal the complete communication timeline.

Oxygen communication

  • February 20, 2024 – The vendor was notified about the vulnerability, and a URL with all the information was provided. On the same day, the vendor responded, accepted the vulnerability report and pointed out that they had updated the documentation and asked if additional warnings would reduce the vulnerability severity score.
  • February 23, 2024 – Patchstack sent an additional comment that Patchstack still considers it a vulnerability, pointing out that it leads to PHP code execution. The vendor replied the same day with disagreement, pointing at warnings on the plugin and documentation.
  • March 5, 2024 – Patchstack sent a group email to Elijah (Soflyy), Calvin (researcher), and several Patchstack research team members. Patchstack reminded Elijah that March 18 was the date for disclosure. Patchstack shifted that day to give them more time to patch and offered another disclosure day if they needed more time. Patchstack also asked if they needed any additional information.
  • March 6, 2024 – Soflyy (Elijah) replied again, pointing out that the issue was with documentation rather than the plugin itself.
  • April 2, 2024 – Patchstack sent another group email. Patchstack said that almost two months had passed since the day Patchstack reported the vulnerabilities in Oxygen and Breakdance, and Patchstack would disclose the vulnerabilities unless they wanted to patch them so Patchstack could give them extra time to do it and postpone the disclosure. No more replies were received from Elijah.
  • April 3, 2024 – Patchstack has disclosed a vulnerability related to Oxygen <= 4.8.1. Oxygen 4.8.2 was released to confuse vulnerability scanners with a higher version of the plugin that was marked as vulnerable. The vulnerable version is updated to <= 4.8.2 on the Patchstack vulnerability database and the related CVE ID entry.

Breakdance communication

  • Feb 12, 2024 – Soflyy was notified about the vulnerability and responded the same day with a message saying he couldn’t access the technical details of the vulnerability, getting a 404 status on the provided link. Also, the vendor provided a video to point at warnings and plugin documentation.
  • Feb 14, 2024 – Soflyy received the PDF file of the original report with complete technical information and our additional comment on code execution.
  • Feb 15, 2024 – Soflyy replied again with the same type of statement, stating that a particular plugin behavior is by design and that there’s nothing wrong with the plugin. The vendor again pointed towards the warnings and documentation.
  • Feb 20, 2024 – Patchstack got an email from the vendor saying they are waiting for our response.
  • Feb 23, 2024 – Patchstack got another email from the vendor asking to reply.
  • Feb 23, 2024 – Patchstack sent the message to the vendor that our triage team is notified about their request to reply.
  • April 2, 2024 – Patchstack sent a group email. Patchstack said that almost two months passed since the day we reported the vulnerabilities in Oxygen and Breakdance, and Patchstack would disclose the vulnerabilities unless they wanted to patch them so Patchstack could give them extra time to do it and postpone the disclosure. No more replies were received from Elijah or Louis. The vendor didn’t respond. Instead, they notified users that the vulnerability Patchstack planned to disclose was not real and leaked screenshots of confidential communication.

Past communications with Soflyy (2021-2023)

It’s worth noting that this isn’t the first time we’ve encountered issues with Soflyy. It’s also important to emphasize that we do vulnerability reporting for free; we aren’t compensated for reporting vulnerabilities to developers. We undertake this effort to assist security researchers with reporting, and as a CNA (CVE Numbering Authority), it’s our voluntary obligation to report vulnerabilities to vendors. Therefore, we put in every effort to reach the vendors and provide as much time as possible for vulnerabilities to be fixed before disclosure. The following timeline serves as an extreme example of how much of our resources could be spent due to poor communication by the vendor as we report security issues to them and try to validate the patches. It also shows that regardless of the communication challenges, we remain patient with the vulnerability disclosure process to make sure it’s done the right way.

  • May 4, 2021 – Patchstack reported a batch of security vulnerabilities to Oxygen.
  • May 5, 2021 – Oxygen (Elijah) responded and got all reports via email.
  • May 13, 2021 – Patchstack followed up and asked about the progress and possible patch release day.
  • May 26, 2021 – Oxygen (Elijah) replied that they were focused on the Oxygen 3.8 release, and patches will be released with the 3.9 version.
  • July 27, 2021 – Patchstack inquired about the progress and expected timeline for the patch once more. Patchstack received a response stating that Oxygen 3.9 is currently under development and that it will likely take several weeks. Patchstack then requested Oxygen (Elijah) to notify us upon the patch release or provide a contact for any developer who can keep us updated on the situation.
  • Feb 2, 2022 – Patchstack reached out to the WP All Import team (also developed by Soflyy, like Oxygen and Breakdance) regarding a vulnerability report we had for them. During this correspondence, Patchstack also brought up the communication issues that were experienced with Oxygen (Elijah). In response, Patchstack received a commitment to follow up with Elijah to ensure a response to our inquiries.
  • Jun 27, 2023 – Patchstack reported a new vulnerability to Oxygen. Patchstack received a response the same day.
  • Jul 4, 2023 – In subsequent communication, Patchstack reiterated the communication issues that were encountered with Oxygen (Elijah) and mentioned that Patchstack was still awaiting their response regarding multiple vulnerabilities reported on May 4, 2021. Their reply stated: ‘I see the last batch of vulnerabilities reported to us in May of 2021. It looks like we patched the most critical of these (as assessed by our development team) in Oxygen 4.0, which was released on May 27, 2022.
  • Jul 5, 2023 – Patchstack asked Oxygen to establish better and more stable communication to avoid such long processing of vulnerabilities.
  • Jul 20, 2023 – Patchstack notified Oxygen about disclosing an unpatched vulnerability after exchanging more emails.
  • Jul 21, 2023 – Elijah from Oxygen responded, stating that the vulnerability had already been patched in a previous version. In response, Patchstack notified Oxygen that they had not received a patched version for validation and that Oxygen wasn’t available for free download from public resources. Elijah acknowledged this, stating that it made sense.
  • Jul 22, 2023 – Oxygen (Louis) sent a short comment that they released the patch six months ago.
  • Jul 24, 2023 – Patchstack asked for a patched version of Oxygen to be sent to us. Oxygen (Elijah) replied that he submitted the patched version via the Patchstack platform.

It took 812 days (2 years, 2 months, 21 days) until Oxygen sent us new version for the validation process and provided all answers that were required. Taking the above into account, we feel that it’s unfair for Soflyy to call us out for poor communication.

Concerns and explanation

Denying the existence of a security vulnerability instead of patching it raises serious questions about the company’s overall understanding of security. It’s also alarming that users of Oxygen/Breakdance are reaching out to us because posts and comments related to these security issues are being deleted by Soflyy from their community groups on social networks.

The vendor also sent out an email to users that this vulnerability does not exist – this statement is factually not true, it was proven and validated by the original researcher Calvin Alkan (Snicco), by multiple Patchstack triage team members and also by other independent security researchers such as Emil Trägårdh. It’s also worth pointing out that in the beginning, Soflyy itself seemed to understand the security risk as they asked if additional warnings could get the CVSS score lower (CVSS scores are calculated based on a specific standard so we can’t just make up a score).

The goal for that functionality in both plugins was to provide builder access to users who don’t have administrator privileges. It made it possible to give builder access to lower permission roles without granting full administrator privileges. However, the lack of security measures in place makes it possible for these low permission users to escalate their privilege to administrator and also take control over the entire website and server via remote code execution. Using this feature in its current state equals to giving full administrator rights to these users, which is the exact opposite of what the feature is trying to solve (and the way how this feature has been presented to users proves that as well).

Additionally, Samuel Wood (Otto) from the WordPress.org team has also reacted on this and has stated that proper security measures should be in place for plugins that allow PHP code execution (such as restricting this to admins, as were proposed to the developer) and due to the risks presented from such functionality, WordPress.org plugin team does not currently accept new plugins into the WordPress plugin repository who allow PHP code execution in such way.

Similar vulnerabilities have been recently found and patched also in other page builder plugins, who have successfully implemented the required security measures in a matter of days. We remain open to assist Oxygen/Breakdance developer to also implement such measures and help them patch the current security issue if our help is needed.

Public reaction

This situation has undoubtedly caused discussions in both the Oxygen and Breakdance community, with even rumours circulating about Patchstack’s possible bias and prejudice against these products. However, we want to emphasize that Patchstack’s main website is also built with Oxygen, so we are also affected by this issue. As of today, we’ve made the decision to move away from Oxygen and other Soflyy products due to this experience. As customers of Soflyy, we share the same concerns as everyone else.

PoC by community

Swedish ethical hacker Emil Trägårdh released the video demonstration of privilege escalation on the Breakdance with detailed description of the possible attack vector.

Conclusion

As of today, April 4th, 2024, both Oxygen and Breakdance page builders remain vulnerable to authenticated remote code execution vulnerabilities. Simply adding a warning to the documentation or near the vulnerable feature does not eliminate the vulnerability. We hope that the developer decides to add required security measures in place, and we remain open to collaborate on this if needed.

Timeline

09 February, 2024Vulnerability report for Breakdance plugin by Snicco received via the Patchstack Bug Bounty program.
10 February, 2024Vulnerability report for Oxygen plugin by Snicco received via the Patchstack Bug Bounty program.
10 February, 2024Breakdance plugin vulnerability reported to the vendor and vendor responded in a series of emails.
14 February, 2024Breakdance vendor got PDF file with all the details of vulnerability and our additional comment about the PHP code execution.
15 February, 2024Breakdance vendor responded by refusing to patch the issue but with the idea make security notice more visible and change the wording to more clear.
20 February, 2024Breakdance vendor asked us to reply to their previous email from 15 February, 2024. Also, Oxygen vendor responded.
23 February, 2024Breakdance vendor asked us again to reply to their previous email from 15 February, 2024 and they got response that we will notify the triage team about this request. We have sent extra comment about Oxygen and got another reply denying the vulnerability.
05 March, 2024Group email sent by us to all parties as a reminder about non patched vulnerability and the offer to postpone disclosure if vendor is willing to release the patch.
06 March, 2024Vendor replied and rejected to release the patch pointing at notices and documentation related to vulnerable function.
02 April, 2024Group email to vendor, researcher and Patchstack triage team members with the final warning before disclosure and offer to postpone disclosure if vendor is willing to patch the issue on both Breakdance and Oxygen plugins. Later that day, vendor released the article publicly denying the vulnerability we were planing to disclose.
03 April, 2024Added the vulnerabilities to the Patchstack vulnerability database. Security advisory article published. CVE IDs published. Oxygen 4.8.2 version released to confuse vulnerability scanners with the higher plugin version than it was at that time marked as vulnerable. Database entry and CVE ID updated to mark 4.8.2 as vulnerable.
05 June, 2024Updated the vulnerable versions of Oxygen to 4.8.3.
26 August, 2024Updated the vulnerable versions of Oxygen to 4.9.

The latest in Security advisories

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