endangcamon/CVE-2026-7465-POC

GitHub: endangcamon/CVE-2026-7465-POC

Stars: 2 | Forks: 0

# CVE-2026-7465 - Spectra Gutenberg Blocks <= 2.19.25 # Authenticated (Contributor+) Remote Code Execution via Arbitrary PHP Function Call via Block Attributes ================================================================================ ## VULNERABILITY SUMMARY ================================================================================ | Field | Value | |------------------------|----------------------------------------------------------| | CVE ID | CVE-2026-7465 | | Plugin | Spectra Gutenberg Blocks - Website Builder for Block Editor | | Vulnerable Versions | <= 2.19.25 | | Attack Type | Remote Code Execution (RCE) | | Access Level Required | Contributor and above | | Attack Complexity | Low | | User Interaction | None (exploit triggers on post view) | | CVSS Score | Critical (estimated 9.9) | | Vulnerable File | classes/class-uagb-init-blocks.php (lines 329-336) | ================================================================================ ## VULNERABILITY DETAILS ================================================================================ ### Root Cause The `render_block()` method in `class-uagb-init-blocks.php` passes user-controlled block attributes (`$block['attrs']`) directly to `WP_Block_Type_Registry::register()` without ANY validation or sanitization of the `render_callback` field. **Vulnerable Code (lines 329-336):** // Register only UAG blocks. if ( ! empty( $block['blockName'] ) && strpos( $block['blockName'], 'uagb/' ) !== false ) { // Register block on server-side to support WP Hide blocks feature introduce in WP 6.9. $registry = WP_Block_Type_Registry::get_instance(); // Only register if the block is NOT already registered. if ( ! $registry->is_registered( $block['blockName'] ) ) { $registry->register( $block['blockName'], $block['attrs'] ); // <-- VULNERABLE LINE } } ### Why This Is Dangerous `$block['attrs']` comes directly from the post content (block comment delimiters). Any user who can create or edit posts (Contributor+) can embed arbitrary JSON attributes in block comments. When these attributes are passed to `WP_Block_Type_Registry::register()`, they become properties of the new `WP_Block_Type` object, including: - `render_callback` - Arbitrary PHP callable (function name, class method) - `attributes` - Block attribute schema - `script_handles` / `view_script_handles` - Script handles to enqueue - `style_handles` - Style handles to enqueue The most critical property is `render_callback`. Setting this to any valid PHP callable makes the block "dynamic" (`is_dynamic()` returns true when `is_callable($render_callback)` is true). When WordPress encounters a registered dynamic block, it calls: call_user_func( $this->block_type->render_callback, $this->attributes, $block_content, $this ); This invokes the attacker-specified function with attacker-controlled arguments, achieving Arbitrary PHP Function Call. ================================================================================ ## EXPLOITATION TECHNIQUE ================================================================================ ### Two-Block Payload The exploit requires exactly two blocks in the same post content: **BLOCK 1 (Registration Block):** - Block name: Any `uagb/`-prefixed name (e.g., `uagb/cve-2026-7465`) - Block attributes: `{"render_callback": ""}` - Purpose: When Spectra's `render_block` filter processes this block, it registers the fake block type with the attacker-specified `render_callback` in the `WP_Block_Type_Registry`. **BLOCK 2 (Trigger Block):** - Block name: SAME as Block 1 (e.g., `uagb/cve-2026-7465`) - Block attributes: Function-specific arguments - Inner content: Optional, used as second argument to the callback - Purpose: Since the block type is now registered with a `render_callback`, WordPress's block rendering engine will call `call_user_func()` on it, executing the attacker-specified function with the second block's attributes and content as arguments. ### Sequential Block Rendering Flow WordPress parses post content → encounters Block 1 (uagb/cve-2026-7465) │ ├─ Is block registered? → NO → Output static HTML (no render_callback called) │ ├─ apply_filters('render_block') → Spectra's render_block() at priority 5 │ ├─ Block name starts with "uagb/"? → YES │ ├─ Is registered? → NO │ └─ $registry->register("uagb/cve-2026-7465", {"render_callback": "wp_insert_user"}) │ → WP_Block_Type created with malicious render_callback │ WordPress continues → encounters Block 2 (uagb/cve-2026-7465) │ ├─ Is block registered? → YES (registered by Block 1) │ ├─ is_dynamic()? → YES (is_callable("wp_insert_user") = true) │ ├─ call_user_func("wp_insert_user", $attributes, $content, $block) │ → wp_insert_user(array with admin user data) → ADMIN USER CREATED! │ └─ apply_filters('render_block') → Spectra's render_block() at priority 5 ├─ Block name starts with "uagb/"? → YES └─ Is registered? → YES → Skip (already registered) ================================================================================ ## EXPLOIT VARIANTS ================================================================================ ### Variant 1: Privilege Escalation via wp_insert_user (PHP 8.x Safe) **Impact:** Administrator user creation → Full site takeover → RCE Block 1: Block 2: **Why it works on PHP 8.x:** - `wp_insert_user` is a WordPress userland function (defined in PHP) - PHP userland functions silently ignore extra arguments (deprecation notice only) - The 3-argument call `call_user_func("wp_insert_user", $attrs, $content, $block)` effectively becomes `wp_insert_user($attrs)` (extra args ignored) - `$attrs` contains the full user creation array with role=administrator **Post-exploitation:** 1. Log in as the new administrator 2. Navigate to Appearance > Theme File Editor 3. Edit a PHP template file with `` 4. Access the modified file URL for direct OS command execution ### Variant 2: Direct OS Command Execution via array_walk (PHP 7.x) **Impact:** Immediate OS command execution Block 1: Block 2: system **Mechanism:** - `array_walk($attributes, $content, $block)` is called by WordPress - `array_walk` accepts `(array, callable, mixed)` → type-compatible in PHP 8.x - For each element, calls `$content($value, $key, $userdata)` - `system("whoami", 0, $block_obj)` → command executes (PHP 7.x) - On PHP 8.x: `system()` is an internal function, ArgumentCountError for the 3rd argument from `array_walk` **Note:** This variant works reliably on PHP 7.x. On PHP 8.x, use Variant 1 (wp_insert_user) instead. ### Variant 3: Arbitrary Post Creation via wp_insert_post **Impact:** Content injection, phishing page creation, SEO spam Block 1: Block 2: ### Variant 4: Webshell Deployment Chain **Impact:** Persistent PHP webshell on the server This requires a multi-step chain: 1. Use `wp_insert_user` to create an admin user 2. Use the admin session to upload a malicious plugin via `wp-admin/plugin-install.php` 3. The plugin contains a PHP webshell Alternatively, use `wp_insert_post` to create a post, then use admin access to modify theme files via the theme editor. ================================================================================ ## PROOF OF CONCEPT RESULTS (Lab Verified) ================================================================================ ### Environment - WordPress: Latest (6.x) - PHP: 8.2.12 (XAMPP, Windows) - Plugin: Spectra Gutenberg Blocks 2.19.25 (active) - Database: MySQL (XAMPP) ### Test Results **TEST 1: Arbitrary render_callback Registration** - Registered fake block `uagb/poc-test-*` with `render_callback = "phpinfo"` - Result: SUCCESS - Block type registered, `is_dynamic()` = true **TEST 2: wp_insert_user Privilege Escalation (PHP 8.2)** - Registered block with `render_callback = "wp_insert_user"` - Called `call_user_func("wp_insert_user", $user_data, "", null)` - Result: SUCCESS - Administrator user created (ID: 19) - Verification: User role confirmed as `administrator` in database **TEST 3: array_walk + passthru Direct RCE (PHP 8.2)** - Registered block with `render_callback = "array_walk"` - Called `array_walk(array("whoami"), "passthru", $mock_block)` - Result: FAILED - PHP 8.2 ArgumentCountError (passthru expects max 2 args) - Note: Would work on PHP 7.x where extra args produce warnings only **TEST 4: wp_insert_post Content Injection (PHP 8.2)** - Registered block with `render_callback = "wp_insert_post"` - Called `call_user_func("wp_insert_post", $post_data, "", null)` - Result: SUCCESS - New post created (ID: 82) **TEST 5: Full HTTP Exploit Chain (Frontend Rendering)** - Created post (ID: 83) with two-block payload via direct DB - Accessed post via HTTP: `GET /WordpressLabReseacher/?p=83` - Spectra's `render_block` filter registered the fake block type - WordPress rendered second block → `wp_insert_user` called - Result: SUCCESS - Admin user `rce_poc_admin_0752d0ac` (ID: 21) created - Role verified: `a:1:{s:13:"administrator";b:1;}` ================================================================================ ## REMEDIATION ================================================================================ ### Recommended Fix The `render_block()` method should validate `$block['attrs']` before passing it to `WP_Block_Type_Registry::register()`. Specifically: 1. **Whitelist allowed properties**: Only pass known-safe block type properties (e.g., `attributes` schema, `supports`). Never pass `render_callback` from user-controlled data. 2. **Strip dangerous properties**: Remove `render_callback`, `script_handles`, `view_script_handles`, and any other properties that could be abused before registering the block type. 3. **Validate block names**: Only register block names that correspond to actual Spectra block types (check against a known list of valid block slugs). ### Example Fix public function render_block( $block_content, $block ) { if ( ! empty( $block['blockName'] ) && strpos( $block['blockName'], 'uagb/' ) !== false ) { $registry = WP_Block_Type_Registry::get_instance(); if ( ! $registry->is_registered( $block['blockName'] ) ) { // FIXED: Remove dangerous properties from attrs before registering $safe_attrs = $block['attrs']; unset( $safe_attrs['render_callback'] ); unset( $safe_attrs['script_handles'] ); unset( $safe_attrs['view_script_handles'] ); unset( $safe_attrs['editor_script_handles'] ); unset( $safe_attrs['style_handles'] ); unset( $safe_attrs['view_style_handles'] ); unset( $safe_attrs['editor_style_handles'] ); unset( $safe_attrs['variation_callback'] ); $registry->register( $block['blockName'], $safe_attrs ); } } // ... rest of method } ================================================================================ ## TIMELINE ================================================================================ - Vulnerability introduced: When the render_block() registration feature was added (to support WP Hide blocks in WP 6.9) - Affected versions: All versions up to and including 2.19.25 - Patched version: > 2.19.25 (pending/available) ================================================================================ ## REFERENCES ================================================================================ - https://plugins.trac.wordpress.org/browser/ultimate-addons-for-gutenberg/trunk/classes/class-uagb-init-blocks.php#L335 - https://plugins.trac.wordpress.org/browser/ultimate-addons-for-gutenberg/tags/2.19.25/classes/class-uagb-init-blocks.php#L335 - https://plugins.trac.wordpress.org/browser/ultimate-addons-for-gutenberg/trunk/classes/class-uagb-init-blocks.php#L330 - https://plugins.trac.wordpress.org/browser/ultimate-addons-for-gutenberg/tags/2.19.25/classes/class-uagb-init-blocks.php#L330 ================================================================================ ## FILES IN THIS DIRECTORY ================================================================================ ================================================================================