CVE-2025-4611 PoC

Mitre Description:

The Slim SEO – Fast & Automated WordPress SEO Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's slim_seo_breadcrumbs shortcode in all versions up to, and including, 4.5.3 due to insufficient input sanitization and output escaping on user supplied attributes. This makes it possible for authenticated attackers, with contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.

CVE publish date: 21 may 2025

https://nvd.nist.gov/vuln/detail/CVE-2025-4611

analysis: i have installed the right version through wordpress

we want to install the vulnarable version 4.5.3 and fixed version 4.5.4 for diffrent comparison

Vulnarable version
fixed version

the vulnarable file in /slim-seo/src/Breadcrumbs.php

we will compare the 2 file in diffrent versions

line 109 & 110

Vulnerable Function

The vulnerable function is:

public function render_shortcode( $atts ): string {
    $this->args = wp_parse_args( $atts, $this->args );
    $this->parse(); // builds $this->current and $this->links

    if ( 'true' === $this->args['display_current'] ) {
        $items[] = sprintf(
            '<span class="breadcrumb breadcrumb--last" aria-current="page">%s</span>',
            $this->current // Vulnerable value
        );
    }

    ...
}

The value of $this->current is output directly into HTML without escaping. If an attacker can influence this value, they can inject JavaScript or HTML into the page.


Call Chain: How It Gets Triggered

1. render_shortcode() is registered as a shortcode handler

add_shortcode( 'slim_seo_breadcrumbs', [ $this, 'render_shortcode' ] );

This means it will execute when a page or post includes:

[slim_seo_breadcrumbs]

2. render_shortcode() calls parse()

Within render_shortcode(), the method parse() is called:

$this->parse();

This method sets $this->current depending on the context post page


How the XSS Works

Case: Search Page Context

Inside the parse() function:

elseif ( is_search() ) {
    $this->current = sprintf( $this->args['label_search'], get_search_query() );
}

The default label_search is:

'label_search' => __( 'Search Results for &#8220;%s&#8221;', 'slim-seo' ),

So the final output becomes:

$this->current = 'Search Results for “' . get_search_query() . '”';

If the attacker controls get_search_query(), they can inject payloads.


No Output Encoding

The HTML is rendered as:

<span class="breadcrumb breadcrumb--last" aria-current="page">
  {raw $this->current}
</span>

in older version there’s no esc_html(), htmlspecialchars(), or wp_kses(). This leads directly to XSS.


Steps:

  1. WordPress detects this as a search query (is_search() returns true).

  2. get_search_query() returns the attacker’s input.

  3. $this->current becomes “<script>alert(1)</script>”.

  4. It gets rendered directly into the page.

  5. JavaScript executes in the victim’s browser.

PoC:

on pages tab add a new page

after that go to + and search in patterns for breadcrumb

add the breadcrumb and go to settings for breadcrumb and change the label home to

<img src=x onerror=alert(1)>

save the page and view the XSS :)

Last updated