# 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