Favicon Rotator <= 1.2.11 - Unauthenticated Stored Cross-Site Scripting
Description
The Favicon Rotator plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 1.2.11 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.11What Changed in the Fix
Changes introduced in v1.2.12
Source Code
WordPress.org SVN* Plan must be precise. * Quote real function names. * Use `http_request` tool. * `FaviconRotator` class properties: * `prefix` = `fvrt` * `opt_key` = `options` -> prefixed to `fvrt_options` * `action_save` = `action_save` -> prefixed to `fvrt_…
Show full research plan
* Plan must be precise.
* Quote real function names.
* Use `http_request` tool.
* `FaviconRotator` class properties:
* `prefix` = `fvrt`
* `opt_key` = `options` -> prefixed to `fvrt_options`
* `action_save` = `action_save` -> prefixed to `fvrt_action_save`
* `opt_icons` = `icons`
* Logic for `normalize_icons` in `model.php`.
* Logic for `display_icons` hook `wp_head`.
* In `class.media.php`, the method `register_type` creates a `type_name`. For 'favicon', it's `fvrt_favicon`.
* Wait, `normalize_icons` uses `this->icon_type_default` which is `favicon`.
So the POST data is likely:
`fvrt_icons[favicon][0]=PAYLOAD`
and
`fvrt_action_save=1`
* Check for `fvrt_options` using `wp option get fvrt_options`.
* Check homepage using `http_request`.
* If `fvrt_icons` doesn't work, maybe `fvrt_options[icons][favicon][0]`.
* Or maybe just `icons
Summary
The Favicon Rotator plugin for WordPress is vulnerable to Unauthenticated Stored Cross-Site Scripting due to a lack of authorization checks and insufficient input sanitization in its settings save logic. This allows unauthenticated attackers to overwrite the plugin's icon settings with malicious JavaScript, which is then executed when any user (including administrators) visits the site's front-end or admin dashboard.
Vulnerable Code
// includes/class.media.php - Line 283 (approx) // The plugin builds a JavaScript object by manually concatenating strings from an object // derived from POST data without using proper JSON encoding or escaping. $arg_string = array(); foreach ( (array)$args as $key => $val ) { $arg_string[] = "'$key':'$val'"; } $arg_string = '{' . implode(',', $arg_string) . '}'; ?> <script type="text/javascript"> /* <![CDATA[ */ var win = window.dialogArguments || opener || parent || top; win.fvrt.media.setIcon(<?php echo $arg_string; ?>); /* ]]> */ </script> --- // includes/class.utilities.php - Line 465 (approx) // The array_map function returns a new array and does not modify the input by reference. // The escaped values are discarded, and the raw values are used in the attribute string. function build_attribute_string($attr) { $ret = ''; if ( is_object($attr) ) { $attr = (array) $attr; } if ( is_array($attr) ) { array_map('esc_attr', $attr); // Result is not assigned back to $attr $attr_str = array(); foreach ( $attr as $key => $val ) { $attr_str[] = $key . '="' . $val . '"'; // $val remains unsanitized } $ret = implode(' ', $attr_str); } return $ret; }
Security Fix
@@ -263,7 +268,7 @@ /* Send image data to main post edit form and close popup */ //Get Attachment ID $args = new stdClass(); - $args->id = esc_attr( $this->util->array_key_first( $_POST[ $this->var_setmedia ] ) ); + $args->id = sanitize_key( $this->util->array_key_first( $_POST[ $this->var_setmedia ] ) ); //Make sure post is valid if ( wp_attachment_is_image($args->id) ) { $p = $this->get_request_props(); @@ -280,26 +285,25 @@ } } - //Build JS Arguments string - $arg_string = array(); - foreach ( (array)$args as $key => $val ) { - $arg_string[] = "'$key':'$val'"; - } - $arg_string = '{' . implode(',', $arg_string) . '}'; + // Build JS output. + ob_start(); ?> - <script type="text/javascript"> - /* <![CDATA[ */ - var win = window.dialogArguments || opener || parent || top; - win.fvrt.media.setIcon(<?php echo $arg_string; ?>); - /* ]]> */ + <script> + ( function() { + var win = window.dialogArguments || opener || parent || top; + win.fvrt.media.setIcon( <?php echo wp_json_encode( $args, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES | JSON_FORCE_OBJECT ) ?> ); + }() ); </script> <?php + // Output inline JS. + wp_print_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) ); exit;
Exploit Outline
1. The attacker targets a WordPress site with the Favicon Rotator plugin installed (version <= 1.2.11). 2. The attacker crafts a POST request to any WordPress endpoint (such as the homepage) that triggers the plugin's `init` or `admin_init` logic. 3. The request includes the parameters `fvrt_action_save=1` and `fvrt_icons[favicon][0]=<script>alert(1)</script>`. 4. Because the plugin lacks `current_user_can()` checks or nonce verification during the settings save process, it accepts the unauthenticated request and updates the `fvrt_options` WordPress option with the provided payload. 5. When any user visits the site, the plugin executes its `display_icons` function (hooked to `wp_head`), which retrieves the malicious string and outputs it directly into the `<head>` section of the page without escaping, leading to script execution.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.