CVE-2026-82221 - Unauthenticated Reflected Cross-Site Scripting (XSS) in the WordPress RegistrationMagic plugin

[research] • [cve] • [xss]

$ cat summary.txt


CVE-2026-82221 was recently published, an unauthenticated XSS vulnerability I discovered in the WordPress RegistrationMagic plugin, affecting plugin versions <= 6.0.9.8. In this article, I intend to provide a detailed analysis of the vulnerability, including its root cause, potential impact, and its proof of concept.


01. Summary

RegistrationMagic, a WordPress registration form plugin developed by Metagauss, is vulnerable to unauthenticated reflected Cross-Site Scripting (XSS) in versions up to 6.0.9.8. CVE-2026-82221 allows an attacker, without needing to log in, to inject arbitrary JavaScript into the page through the activetab request parameter, exploiting a classic case of escaping applied to the wrong context: the value is sanitized as if it were going to be output in HTML, but it is actually printed without quotes inside a <script> block.

The vulnerability was fixed in version 6.0.9.9, released on August 27, 2026.


   
CVE CVE-2026-82221
CWE CWE-79 — Improper Neutralization of Input During Web Page Generation
CVSS 3.1 7.1 (High) — AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:L
Authentication required None
User interaction Required (victim must click a malicious link)
Component RegistrationMagic (registration_magic.php)
Affected versions <= 6.0.9.8
Fixed version 6.0.9.9

02. Impact

Since this is a reflected XSS, the impact depends on social engineering (the victim needs to click on a malicious link), but some of the possible effects include:


RegistrationMagic is used by more than 8,000 sites worldwide, expanding the potential impact of this vulnerability.


03. Technical Details

The vulnerability exploits the absence of type validation on a parameter that is, semantically, always numeric, combined with the use of an escaping function that is incompatible with the actual output context (HTML encoding in a JavaScript sink). The flaw is located in the repository in the file public/controllers/class_rm_front_controller.php, around line 291, and propagates to the account area rendering templates.

The manipulated parameter, activetab, is read directly from the request without any type validation:

// v6.0.9.8 (vulnerable) — class_rm_front_controller.php
if (isset($request->req['activetab']) && $request->req['activetab'] != '') {
    $distinct = true;
    $active_tabs = $request->req['activetab'];
}
$data->active_tab_index = $distinct
    ? $active_tabs
    : (isset($request->req['rm_tab']) ? (int) $request->req['rm_tab'] : 0);

The active_tab_index field, coming directly from user input, should have been normalized by the plugin’s central sanitization class, includes/class_rm_sanitizer.php. However, activetab was not included in the allowlist of parameters forced to integer via absint():

// v6.0.9.8 (vulnerable) — class_rm_sanitizer.php
if (in_array($key, array(
    'rm_form_id', 'rm_submission_id',
    'rm_tr', 'rm_reqpage',
    'rm_form_page_no', 'page_no',
    'field_id', 'rm_field_id', 'field_order'))) {
    $request[$key] = absint($value);
}

As a result, the value reaches the sink intact, inside the rendering templates, where it is printed without quotes, as a numeric literal, inside a <script> block:

// v6.0.9.8 (vulnerable) — template_rm_registrations_view.php
activeTabIndex: <?php echo esc_html($data->active_tab_index); ?>

// v6.0.9.8 (vulnerable) — template_rm_front_submissions.php
activeTabIndex: <?php echo wp_kses_post((string)$data->active_tab_index); ?>
...
redirecttosametab(<?php echo wp_kses_post((string)$data->active_tab_index); ?>);

The core of the flaw is that esc_html() and wp_kses_post() are encoding functions aimed at HTML context — they only neutralize < > & " '. The actual sink, however, is a JavaScript literal without string delimiters. In this context, characters such as ;, (, ), /, and space are not neutralized by these functions, allowing the reflected value to terminate the numeric expression expected by the JavaScript parser and inject arbitrary code controlled by the attacker.


04. Affected Endpoints

The affected endpoint corresponds to the plugin’s front-end account area screens, accessible via the [RM_Front_Submissions] / [CRF_Submissions] shortcodes, published on any page of the site that uses them. The activetab parameter (GET/REQUEST), combined with the layout_view routing parameter, determines which vulnerable template is rendered:

Route (layout_view) Rendered template
(default, without layout_view) template_rm_front_submissions.php
registrations template_rm_registrations_view.php
payments template_rm_user_payments_view.php

Vulnerable URL pattern:

https://target.com/page-with-shortcode/?layout_view=registrations&activetab=<PAYLOAD>

05. Proof of Concept

The proof of concept can be accessed in my vulnerability repository on GitHub, available at: gabrielftanaka/CVE-2026-82221-PoC

Exploitation consists of luring the victim into clicking a link to a page on the target site containing the RegistrationMagic account area shortcode, with the activetab parameter containing the JavaScript payload. Since the value is reflected without proper sanitization inside the <script> block of the response, the code is executed within the origin context of the vulnerable site, in the victim’s browser.


06. Fix

Metagauss fixed this vulnerability in version 6.0.9.9, released on August 27, 2026, through changes in three points of request processing: strengthening the sanitization allowlist, explicit type validation in the controller, and correcting the escaping function used at the sink. Below is the diff containing the main changes implemented by the developers in the layer responsible for processing the active tab.

--- includes/class_rm_sanitizer.php
  if (in_array($key, array(
      'rm_form_id', 'rm_submission_id',
      'rm_tr', 'rm_reqpage',
+     'rm_reqpage_sub', 'rm_reqpage_pay',
+     'rm_tab', 'activetab',
      'rm_form_page_no', 'page_no',
      'field_id', 'rm_field_id', 'field_order'))) {
      $request[$key] = absint($value);
  }

--- public/controllers/class_rm_front_controller.php
  if (isset($request->req['activetab']) && $request->req['activetab'] != '') {
      $distinct = true;
-     $active_tabs = $request->req['activetab'];
+     $active_tabs = is_numeric($request->req['activetab']) ? absint($request->req['activetab']) : 0;
  }
- $data->active_tab_index = $distinct
-     ? $active_tabs
-     : (isset($request->req['rm_tab']) ? (int) $request->req['rm_tab'] : 0);
+ $data->active_tab_index = $distinct
+     ? absint($active_tabs)
+     : ((isset($request->req['rm_tab']) && is_numeric($request->req['rm_tab'])) ? absint($request->req['rm_tab']) : 0);

--- public/views/template_rm_registrations_view.php
- activeTabIndex: <?php echo esc_html($data->active_tab_index); ?>
+ activeTabIndex: <?php echo absint($data->active_tab_index); ?>

The fix implements a layered approach to mitigate the vulnerability. First, the activetab parameter now belongs to the allowlist of fields normalized by the central sanitizer, being converted to an integer right at the request’s entry point. Next, the controller now explicitly validates, via is_numeric(), that the received value is numeric before converting it with absint(). Finally, in the templates — the actual sink of the problem — the HTML escaping function was replaced with absint(), appropriate for the context of a numeric literal inside JavaScript. As a result, any attempt to insert HTML markup or JavaScript expressions in place of the tab index is automatically converted to an integer value, completely eliminating the vulnerability’s exploitation vector, regardless of which layer the validation is observed at.


07. Recommendations

It is recommended to update RegistrationMagic to version 6.0.9.9 or higher, which fixes the vulnerability through proper validation of the activetab parameter and explicit conversion to integer values before rendering. If updating is not immediately possible, it is recommended to deploy a WAF/virtual patch capable of blocking XSS payloads in the activetab parameter, in addition to monitoring access logs for requests containing this parameter with characters such as “<”, “script”, “onerror”, or “javascript:”. As an additional layer of defense, it is also recommended to adopt a restrictive Content Security Policy (CSP), reducing the impact of any unidentified reflected XSS.


08. Conclusion

Exploitation of this vulnerability allows an unauthenticated attacker to trigger the execution of arbitrary JavaScript in the browser of RegistrationMagic victims, abusing an inconsistency between the actual output context (JavaScript without quotes) and the escaping function used (aimed at HTML) on the activetab parameter. This case demonstrates how the absence of type validation on a semantically numeric piece of data, combined with an incomplete sanitization allowlist, can open the door to XSS even in code that appears to have output protections. The fix applied by Metagauss, validating the data type both on input and output, eliminates the root cause of the flaw and reinforces the importance of choosing the escaping function according to the actual rendering context, rather than relying solely on the presence of some generic sanitization.


09. References