CVE-2026-39517

Blog Filter <= 1.7.6 - Authenticated (Contributor+) Stored Cross-Site Scripting

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
1.7.7
Patched in
86d
Time to patch

Description

The Blog Filter plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 1.7.6 due to insufficient input sanitization and output escaping. 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.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=1.7.6
PublishedFebruary 11, 2026
Last updatedMay 7, 2026
Affected pluginblog-filter

What Changed in the Fix

Changes introduced in v1.7.7

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan focuses on exploiting a Stored Cross-Site Scripting (XSS) vulnerability in the **Blog Filter** plugin (<= 1.7.6). The vulnerability stems from the plugin's failure to sanitize and escape shortcode attributes, specifically the `default_filter_term` parameter, when rendering JavaScr…

Show full research plan

This research plan focuses on exploiting a Stored Cross-Site Scripting (XSS) vulnerability in the Blog Filter plugin (<= 1.7.6). The vulnerability stems from the plugin's failure to sanitize and escape shortcode attributes, specifically the default_filter_term parameter, when rendering JavaScript initialization code in the frontend.

1. Vulnerability Summary

The Blog Filter plugin allows users to display filtered blog posts via the [AWL-BlogFilter] shortcode. In versions up to 1.7.6, the attribute default_filter_term (and potentially others) is extracted from the shortcode and echoed directly into a <script> block within the filtering/filters-ajax.php file without any escaping. Because authenticated users with the Contributor role can create posts and include shortcodes, they can inject malicious JavaScript into pages viewed by other users, including administrators.

2. Attack Vector Analysis

  • Shortcode: [AWL-BlogFilter]
  • Vulnerable Attribute: default_filter_term
  • Authentication Level: Authenticated (Contributor+)
  • Vulnerable Sink: Inline JavaScript in filtering/filters-ajax.php
  • Injection Type: Breaking out of a JavaScript string literal.

3. Code Flow

  1. Entry Point: A user with Contributor+ privileges creates or edits a post containing the shortcode: [AWL-BlogFilter default_filter_term="PAYLOAD"].
  2. Processing: In blog-filter-shortcode.php, the awl_blog_filter_shortcode() function is executed:
    • Line 52: $atts = shortcode_atts($defaults, $user_atts, 'AWL-BlogFilter'); populates $atts with user-defined attributes.
    • Line 93: $default_filter = $atts['default_filter_term']; assigns the raw attribute value to a variable.
    • Line 99: extract($atts); makes $default_filter_term available in the local scope.
  3. Rendering: The shortcode function eventually includes template files (likely filtering/filters-ajax.php) to render the filtering logic.
  4. Sink: In filtering/filters-ajax.php:
    • Line 101 (approx):
      options<?php echo $unique_id; ?> = {
          ...
          filter: '<?php echo $default_filter; ?>',
          ...
      }
      
    • The value of $default_filter is echoed directly into the JavaScript filter property. By using a single quote and a semicolon, an attacker can terminate the assignment and inject arbitrary code.

4. Nonce Acquisition Strategy

This vulnerability is triggered during the rendering of a shortcode on the frontend. No WordPress nonce is required to trigger the XSS when viewing a page.

However, if the exploitation agent needs to verify the state of the plugin via AJAX or update settings, it would follow this strategy:

  1. Identify Variable: The plugin uses wp_localize_script for AJAX configuration (visible in blog-filter.php).
  2. JS Variable: Usually localized as a global object (e.g., bfg_admin_obj or similar, inferred from standard WP patterns in blog-filter.php AJAX callbacks).
  3. Extraction:
    • browser_navigate to /wp-admin/edit.php?page=blog-filter-settings-page.
    • browser_eval("window.bfg_admin_nonce") (or the specific key found in the localized script).

5. Exploitation Strategy

The goal is to inject a payload that executes alert(origin) to demonstrate XSS.

Payload Construction:
Since the sink is filter: 'PAYLOAD',, we use:
'); alert(origin); //

Resulting JS:

filter: ''); alert(origin); //',

Step-by-Step Execution:

  1. Create Malicious Post: Use the http_request tool to create a post as a Contributor.
    • URL: http://localhost:8080/wp-admin/post-new.php
    • Method: POST
    • Body: Standard WordPress post creation parameters, including:
      • post_title: XSS Test
      • content: [AWL-BlogFilter default_filter_term="');alert(origin);('"]
      • status: publish (Note: Contributors may only be able to save as pending, requiring an admin to view the preview or publish it).
  2. Navigate to Post: As an administrator (to simulate the high-value target), navigate to the newly created post URL using browser_navigate.
  3. Observation: Use browser_wait_for_selector or check for the alert.

6. Test Data Setup

  1. User: Create a user with the contributor role.
  2. Plugin: Ensure blog-filter version 1.7.6 is installed and active.
  3. Content: The contributor must have a post where they can place the shortcode.

7. Expected Results

  • When the post is rendered, the HTML source will contain the injected JavaScript.
  • The browser will execute alert(origin).
  • The browser's console will not show syntax errors that prevent execution (ensured by the (' tail in the payload).

8. Verification Steps

  1. Source Check: Navigate to the post and inspect the source for:
    filter: '');alert(origin);('',
  2. Database Check: Use WP-CLI to verify the payload is stored:
    wp post get [ID] --field=post_content
  3. Behavioral Check: Use Playwright's page.on('dialog', ...) to programmatically catch the alert.

9. Alternative Approaches

If default_filter_term is sanitized in some environments (though unlikely given the code), check other attributes extracted in blog-filter-shortcode.php that are used in filtering/filters-ajax.php:

  • blog_template: Used in filtering/filters-ajax.php line 34.
  • blog_fixed_grid: Used in multiple if conditions within the JS blocks.
  • blog_on_load_scroll: Used as a data attribute in blog-filter-shortcode.php line 111. If not escaped, this provides an attribute-based XSS vector: "><script>alert(1)</script>.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.