# REPRO-2026-00234: Blocksy Companion Pro unauthenticated remote code execution ## Summary Status: published Severity: critical Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00234 CVE: CVE-2026-57624 ## Package Name: Unknown Ecosystem: Unknown Affected: Unknown Fixed: Unknown ## Root Cause # CVE-2026-57624 — Blocksy Companion Pro Unauthenticated Remote Code Execution ## Summary Blocksy Companion Pro (by Creative Themes) ships a premium "Code Editor" Gutenberg block (`blocksy-companion-pro/code-editor`) registered in `framework/premium/features/code-editor.php`. The block's `render_callback` extracts PHP source from the block innerHTML (or the `code` attribute) and passes it to `get_eval_content()`, which executes it with `eval('?>' . $code)`. In versions <= 2.1.46 the callback performed **no execution-context check**, so the block executed arbitrary PHP whenever it was rendered through the standard WordPress block renderer (`do_blocks()`) — for example when an unauthenticated visitor requested the single-view URL of a published `ct_content_block` whose `post_content` contained a code-editor block. This yields unauthenticated remote code execution (CVSS 10.0, CWE-94). The vendor fix in 2.1.47 adds a guard that only allows execution when the block is rendered through the legitimate Blocksy content-block renderer (which sets an explicit `ct_allow_code_editor` flag); the standard `do_blocks` path no longer executes the code. ## Impact - **Package / component affected:** `blocksy-companion-pro` plugin, `Blocksy\CodeEditor` class in `framework/premium/features/code-editor.php` (the `blocksy-companion-pro/code-editor` Gutenberg block render callback). - **Affected versions:** Blocksy Companion Pro <= 2.1.46 (fixed in 2.1.47). - **Risk level:** Critical (CVSS 3.1 10.0, `AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H`). - **Consequences:** An unauthenticated remote attacker can execute arbitrary PHP — and therefore arbitrary operating-system commands — in the context of the web server (`www-data`), leading to full site compromise, data exfiltration, and lateral movement on the host. ## Impact Parity - **Disclosed / claimed maximum impact:** Unauthenticated Remote Code Execution (Patchstack: "Required privilege: Unauthenticated", CVSS 10.0, CWE-94). - **Reproduced impact from this run:** Unauthenticated remote code execution confirmed end-to-end. An HTTP GET with no cookies/credentials to a published content-block single-view URL caused the code-editor block's `render_callback` to `eval()` attacker-controlled PHP that ran `shell_exec("id; whoami; hostname; uname -a")`. The command output (`uid=33(www-data) ...`, hostname, kernel string) appeared in the HTTP response and was written to a marker file by the `www-data` user inside the WordPress container. - **Parity:** `full` — the claimed unauthenticated code execution was demonstrated with concrete command output, plus a fixed-version negative control proving the patch blocks it. - **Not demonstrated:** n/a (code execution was demonstrated, not merely a crash). The only element not reproduced in isolation is the *unauthenticated injection* of the code-editor block content into a content block; that is the companion IDOR CVE-2026-57630 (a separate, concurrently-disclosed CVE). The CVE-2026-57624 RCE primitive — unauthenticated `eval()` execution of the block content via the standard `do_blocks` path — is fully reproduced. ## Root Cause `CodeEditor::__construct()` registers the block with a `render_callback` that calls `get_eval_content()`: ```php register_block_type('blocksy-companion-pro/code-editor', [ 'api_version' => 3, 'render_callback' => function ($attributes, $content, $block) { // (vulnerable <=2.1.46: NO context guard here) if (! empty($content)) { $inline_code = str_replace('
', '',
                              str_replace('
', '', html_entity_decode(htmlspecialchars_decode($content)))); return $this->get_eval_content($inline_code); } if (empty($attributes['code'])) return ''; return $this->get_eval_content($attributes['code']); } ]); ``` `get_eval_content()` runs `eval('?' . '>' . $inline_code . $ending)` — i.e. it executes the block's innerHTML as PHP. Because there was no check on *where* the block was being rendered, the code ran in every rendering context, including the plain `do_blocks()` single-post rendering that an unauthenticated visitor triggers by requesting the content block's URL. The **fix** (2.1.47, present in 2.1.48) inserts two guards at the top of the callback: ```php if (is_admin()) { return ''; } if (empty($block->parsed_block['ct_allow_code_editor'])) { return ''; } ``` The `ct_allow_code_editor` flag is set **only** by `CustomPostTypeRenderer::mark_code_editor_blocks()` (in `framework/premium/features/render-custom-post-type.php`), which runs via the `blocksy:block-parser:result` filter during the legitimate Blocksy content-block renderer. The standard WordPress `do_blocks()` path used for a content block's own single-view URL does **not** apply that filter, so `ct_allow_code_editor` is absent there and the patched callback returns `''` instead of `eval()`-ing. This matches the vendor changelog entries "Code editor block - restrict execution to explicit renderer context" and "Code Editor block - restrict post types for which it can be executed". ### Why the REST block-renderer is NOT the vector The WordPress core `WP_REST_Block_Renderer_Controller::get_item_permissions_check()` has required `edit_posts` (or `edit_post` for a supplied `post_id`) since WP 5.0; an unauthenticated caller receives HTTP 401 `block_cannot_read`. This was verified empirically on the deployed WordPress 7.0. Additionally the code-editor block registers no `attributes` schema, so the REST controller rejects a `code` attribute (`rest_additional_properties_forbidden`). The unauthenticated vector is therefore the `do_blocks()` single-view path, not the REST block-renderer. ## Reproduction Steps 1. **Script:** `bundle/repro/reproduction_steps.sh` (self-contained, idempotent). 2. **What it does:** 1. Bundles the real Blocksy Companion Pro v2.1.48 plugin (GPL-mirror copy, pre-activated so the `Premium`/`CodeEditor` classes load without a license) and the free Blocksy theme. 2. Builds **two** plugin zips from the same real code: - **vulnerable** — `framework/premium/features/code-editor.php` with the CVE-2026-57624 `ct_allow_code_editor` execution-context guard removed (restoring the <=2.1.46 render-callback state); - **fixed** — original 2.1.48 (guard present). A build-sanity grep confirms the guard is present in the fixed zip and absent in the vulnerable zip. 3. Deploys a Docker stack: MariaDB + two `wordpress:php8.2-apache` (WordPress 7.0) instances — one with the vulnerable plugin, one with the fixed plugin — plus the Blocksy theme on each. 4. Creates a published `ct_content_block` on each instance whose `post_content` is a `blocksy-companion-pro/code-editor` block whose PHP payload runs `shell_exec("id; whoami; hostname; uname -a")` and writes a marker file. *(Setup step; in a real attack this block content is injected unauthenticated via the companion IDOR CVE-2026-57630.)* 5. Sends an **unauthenticated** HTTP GET (no cookies/credentials) to the content block's single-view URL on each instance. 6. Verifies the marker file + command output on the vulnerable instance and confirms the fixed instance produces no marker and no code output. 7. Writes `bundle/repro/runtime_manifest.json`. 3. **Expected evidence of reproduction:** - Vulnerable: marker file `/tmp/rce_marker.txt` written by `www-data` containing `uid=33(www-data) ...`, plus `BLOCKSY_RCE_EXECUTED::uid=33(...)` in the HTTP response body. - Fixed: `NO_MARKER_FILE`, zero `BLOCKSY_RCE_EXECUTED` occurrences in the response (the `ct_allow_code_editor` guard returns `''`). ## Evidence - `bundle/logs/reproduction_steps.log` — full run transcript (both passes). - `bundle/logs/vuln_marker.txt` — command output captured from the vulnerable container: ``` RCE_CONFIRMED CMD_OUTPUT: uid=33(www-data) gid=33(www-data) groups=33(www-data) www-data Linux 7.0.14-arch1-1 ... x86_64 GNU/Linux ``` - `bundle/logs/fixed_marker_check.txt` — `NO_MARKER_FILE` (fixed build blocks). - `bundle/logs/code-editor-fix-diff.txt` — unified diff of the exact guard reverted (`if (empty($block->parsed_block['ct_allow_code_editor'])) { return ''; }` removed). - `bundle/artifacts/http/vuln_response.html` — HTTP body containing `BLOCKSY_RCE_EXECUTED::uid=33(www-data) gid=33(www-data) groups=33(www-data)`. - `bundle/artifacts/http/fixed_response.html` — HTTP body with zero `BLOCKSY_RCE_EXECUTED` occurrences. - `bundle/artifacts/code-editor.vuln.php` / `code-editor.fixed.php` — the vulnerable and fixed `CodeEditor` source as deployed. - `bundle/repro/runtime_manifest.json` — runtime evidence manifest (`entrypoint_kind=api_remote`, `service_started=true`, `healthcheck_passed=true`, `target_path_reached=true`). **Environment:** Docker; `mariadb:10.11`; `wordpress:php8.2-apache` (WordPress 7.0); Blocksy theme 2.1.48; Blocksy Companion Pro 2.1.48 (vulnerable build = guard reverted; fixed build = original). The unauthenticated requests were issued from a separate container on the compose network (no WordPress auth cookies), i.e. a genuine logged-out HTTP client. ## Recommendations / Next Steps - **Upgrade:** Update Blocksy Companion Pro to 2.1.47 or later (the `ct_allow_code_editor` execution-context guard). The reproduction shows the patched build blocks the `do_blocks` execution path. - **Defense in depth:** In addition to the context guard, treat the code-editor block as a high-risk primitive: restrict the `ct_content_block` post type to `manage_options` for create/edit (already the case), and consider disabling the code-editor block entirely on sites that do not need server-side PHP execution. - **WAF / mitigation:** Block unauthenticated requests to `/ct_content_block//` and to `admin-ajax.php` actions `blc_retrieve_popup_content` / `blc_retrieve_mega_menu_content` when the content block contains a code-editor block, until all sites are patched. - **Companion IDOR:** Also remediate CVE-2026-57630 (unauthenticated IDOR) which is the realistic unauthenticated injection path for the code-editor block content; the two CVEs combine into a single-request unauthenticated RCE on a vulnerable+unpatched-IDOR site. - **Testing:** Add an automated test that renders a code-editor block via the standard `do_blocks()` path (not the `ContentBlocksRenderer`) and asserts the output is empty on patched builds. ## Additional Notes - **Idempotency:** The script tears down its compose stack (`down -v`) at start and rebuilds from the bundled artifacts, so it can be run repeatedly. It was executed twice consecutively; both runs ended with `exit 0` and the same vulnerable/fixed differential. - **Vulnerable-source caveat:** The vulnerable <=2.1.46 plugin build is not downloadable (the commercial Pro plugin is distributed only via freemius and GPL mirrors keep only the latest release). The reproduction therefore builds the vulnerable variant from the **real** 2.1.48 plugin by reverting the exact CVE-2026-57624 security guard (`ct_allow_code_editor` context check), which restores the pre-2.1.47 render-callback state. The `eval()` mechanism, block registration, attribute/innerHTML handling and `get_eval_content()` are all the unmodified vendor code; only the 2.1.47 guard is removed. The fixed variant (original 2.1.48) serves as the negative control. - **Licensing:** The GPL-mirror 2.1.48 copy is pre-activated (`Capabilities::get_plan()` returns `agency_v2` without a license), so the `Premium`/`CodeEditor` block registers and the `blocksy-companion-pro/code-editor` block is available without a valid freemius key — matching a real licensed deployment where an admin has activated Pro. - **Scope:** The demonstrated surface is the unauthenticated `do_blocks` HTTP path (`api_remote`), matching the ticket's `claimed_surface=api_remote`. The attacker-controlled code injection itself is CVE-2026-57630 (separate), noted for completeness; the CVE-2026-57624 RCE primitive (unauthenticated `eval` execution) is fully reproduced and differentially validated against the patch. ## Reproduction Details Reproduced: 2026-07-06T08:19:00.799Z Duration: 2220 seconds Tool calls: 287 Turns: Unknown Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00234 pruva-verify CVE-2026-57624 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00234&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00234/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-57624 - Source: Creative Themes/Blocksy Companion Pro ## Artifacts - bundle/repro/reproduction_steps.sh (reproduction_script, 19511 bytes) - bundle/repro/rca_report.md (analysis, 12455 bytes) - bundle/vuln_variant/reproduction_steps.sh (reproduction_script, 23402 bytes) - bundle/vuln_variant/rca_report.md (analysis, 16573 bytes) - bundle/artifact_promotion_manifest.json (other, 15609 bytes) - bundle/vuln_variant/source_identity.json (other, 3069 bytes) - bundle/vuln_variant/root_cause_equivalence.json (other, 4248 bytes) - bundle/repro/validation_verdict.json (other, 1120 bytes) - bundle/repro/runtime_manifest.json (other, 1507 bytes) - bundle/logs/vuln_marker.txt (other, 208 bytes) - bundle/logs/fixed_marker_check.txt (other, 82 bytes) - bundle/logs/code-editor-fix-diff.txt (other, 788 bytes) - bundle/logs/reproduction_steps.log (log, 12110 bytes) - bundle/logs/vuln_variant/t1_fixed_marker.txt (other, 110 bytes) - bundle/vuln_variant/http/fixed_shortcode_page.html (other, 51815 bytes) - bundle/vuln_variant/http/fixed_singleview.html (other, 51158 bytes) - bundle/vuln_variant/runtime_manifest.json (other, 3403 bytes) - bundle/logs/vuln_variant/reproduction_steps.log (log, 11744 bytes) - bundle/vuln_variant/validation_verdict.json (other, 7660 bytes) - bundle/vuln_variant/variant_manifest.json (other, 6044 bytes) - bundle/vuln_variant/patch_analysis.md (documentation, 13846 bytes) - bundle/logs/vuln_variant/t1_vuln_marker.txt (other, 110 bytes) - bundle/logs/vuln_variant/t2_fixed_marker.txt (other, 70 bytes) - bundle/logs/vuln_variant/t2_vuln_marker.txt (other, 110 bytes) - bundle/logs/vuln_variant/t3_fixed_marker.txt (other, 70 bytes) - bundle/logs/vuln_variant/t3_vuln_marker.txt (other, 70 bytes) - bundle/vuln_variant/http/vuln_shortcode_page.html (other, 51810 bytes) - bundle/vuln_variant/http/vuln_singleview.html (other, 51249 bytes) - bundle/vuln_variant/http/fixed_popup_ajax.json (other, 370 bytes) - bundle/vuln_variant/http/vuln_popup_ajax.json (other, 370 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00234 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00234/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00234 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev