CVE-2025-69018

Web Directory Free <= 1.7.12 - 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.13
Patched in
9d
Time to patch

Description

The Web Directory Free plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 1.7.12 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.12
PublishedDecember 28, 2025
Last updatedJanuary 5, 2026
Affected pluginweb-directory-free

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2025-69018 (Web Directory Free) ## 1. Vulnerability Summary **CVE-2025-69018** is a Stored Cross-Site Scripting (XSS) vulnerability in the **Web Directory Free** plugin (versions <= 1.7.12). The vulnerability allows authenticated attackers with **Contributor-level*…

Show full research plan

Exploitation Research Plan: CVE-2025-69018 (Web Directory Free)

1. Vulnerability Summary

CVE-2025-69018 is a Stored Cross-Site Scripting (XSS) vulnerability in the Web Directory Free plugin (versions <= 1.7.12). The vulnerability allows authenticated attackers with Contributor-level permissions or higher to inject malicious JavaScript into directory listings. This occurs because the plugin fails to properly sanitize user-supplied metadata fields during storage and fails to escape those fields when rendering them on the front-end or in the admin dashboard.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/post.php (standard WordPress post processor) or a custom AJAX handler (inferred).
  • Action: editpost or wmd_save_listing (inferred).
  • Vulnerable Parameter: Custom metadata fields associated with the directory listing (e.g., Address, Phone, Website, or custom text fields).
  • Authentication: Requires a user with Contributor role or higher. Contributors can typically create and edit their own listings.
  • Preconditions: The plugin must be active, and a directory listing (custom post type) must be created or edited by the attacker.

3. Code Flow (Inferred)

  1. Entry Point: An authenticated user (Contributor) submits a form to create or update a directory listing (Post Type: wmd-listing - inferred).
  2. Processing: The request is handled by a function hooked to save_post or a specific AJAX action (e.g., Wmd_Listings_Manager::save_listing_data - inferred).
  3. Storage: The plugin retrieves data from $_POST (e.g., $_POST['wmd_address']) and stores it using update_post_meta() without passing it through sanitize_text_field() or wp_kses().
  4. Sink: When a user (e.g., an Admin) views the listing, the plugin retrieves the data using get_post_meta() and outputs it in a template file (e.g., single-listing.php or an admin table) using a raw echo or printf without context-specific escaping like esc_html() or esc_attr().

4. Nonce Acquisition Strategy

Since the attacker is a Contributor, they can access the WordPress dashboard to retrieve a valid nonce.

  1. Navigate to Listing Creator: Use browser_navigate to go to the "Add New Listing" page.
    • URL: http://[target]/wp-admin/post-new.php?post_type=wmd-listing (inferred).
  2. Extract Nonce:
    • If using the standard Post Editor: Use browser_eval to get the value of the _wpnonce field.
      • browser_eval("document.querySelector('#_wpnonce')?.value")
    • If the plugin uses a custom AJAX interface: Look for a localized script object.
      • browser_eval("window.wmd_admin_settings?.nonce") (inferred variable name).
  3. Post ID: For updates, the post_ID is also required, found in the URL or a hidden input field.

5. Exploitation Strategy

The goal is to inject a stored XSS payload into a directory listing field that will execute when an Administrator views the listing.

Step-by-Step Plan:

  1. Identify Target Field: Through the Contributor dashboard, identify text inputs for a listing (e.g., "Street Address").
  2. Capture Request: Observe the POST request when saving a listing to identify the exact parameter name (e.g., wmd_listing_meta[address]).
  3. Prepare Payload: <script>alert('XSS_POC_CVE-2025-69018')</script>.
  4. Execute Injection: Use the http_request tool to send a forged POST request mimicking the save action.

Example Request (Inferred):

POST /wp-admin/post.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded

action=editpost
&post_ID=[POST_ID]
&_wpnonce=[NONCE]
&post_title=Malicious Listing
&wmd_listing_address=<script>alert(document.domain)</script>
&save=Save

6. Test Data Setup

  1. Plugin Installation: Install and activate web-directory-free v1.7.12.
  2. User Creation: Create a user with the Contributor role.
  3. Directory Configuration: Ensure at least one directory category is created so a listing can be assigned.
  4. Shortcode Placement: Create a page with the directory shortcode to view the front-end output:
    • wp post create --post_type=page --post_status=publish --post_title="Directory" --post_content="[web_directory]" (inferred shortcode).

7. Expected Results

  • The POST request returns a 302 Found redirecting back to the post editor.
  • The payload is saved in the wp_postmeta table for the corresponding post_id.
  • When an Administrator navigates to the directory listing on the front-end or views the listing in the "All Listings" admin page, the browser executes the script, and an alert box displaying the domain appears.

8. Verification Steps

  1. Database Check: Use WP-CLI to verify the payload is stored unsanitized:
    • wp post meta get [POST_ID] wmd_listing_address (inferred key).
  2. Front-end Verification: Use the browser_navigate tool to visit the URL of the created listing and check for the alert.
  3. Source Code Inspection: Inspect the page source via browser_eval("document.body.innerHTML") to confirm the <script> tags are present and not escaped (e.g., not converted to &lt;script&gt;).

9. Alternative Approaches

  • Payload Variation: If <script> is filtered by a generic firewall, try attribute-based injection:
    • "><img src=x onerror=alert(1)>
  • Location Variation: If the "Address" field is sanitized, try other fields like "Zip Code", "Phone", or custom field labels/values if the plugin allows Contributors to define them.
  • Admin-Specific XSS: Target fields that only appear in the admin "Edit Listing" view to specifically target Administrative users.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Web Directory Free plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to and including 1.7.12. This is due to the plugin failing to properly sanitize and escape listing metadata fields, allowing authenticated attackers with Contributor-level permissions to inject malicious scripts into directory listings that execute when viewed by other users.

Exploit Outline

An authenticated attacker with Contributor-level access or higher logs into the WordPress dashboard and navigates to the directory listing creation or editing interface. The attacker injects a malicious script payload (e.g., <script>alert(document.domain)</script>) into one of the listing's metadata fields, such as the Street Address, Phone, or Website field. When the listing is saved, the payload is stored unsanitized in the database. The script then executes in the context of any user, including administrators, who views the listing on the front-end or via the admin management pages.

Check if your site is affected.

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