CVE-2025-14893

IndieWeb <= 4.0.5 - Authenticated (Author+) Stored Cross-Site Scripting via 'Telephone' Parameter

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

Description

The IndieWeb plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'Telephone' parameter in all versions up to, and including, 4.0.5 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with author 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<=4.0.5
PublishedJanuary 8, 2026
Last updatedJanuary 9, 2026
Affected pluginindieweb

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2025-14893 (IndieWeb Stored XSS) ## 1. Vulnerability Summary The IndieWeb plugin for WordPress (versions <= 4.0.5) is vulnerable to **Stored Cross-Site Scripting (XSS)**. The vulnerability exists in the handling of user profile information, specifically the "Teleph…

Show full research plan

Exploitation Research Plan: CVE-2025-14893 (IndieWeb Stored XSS)

1. Vulnerability Summary

The IndieWeb plugin for WordPress (versions <= 4.0.5) is vulnerable to Stored Cross-Site Scripting (XSS). The vulnerability exists in the handling of user profile information, specifically the "Telephone" parameter. Due to a lack of input sanitization during storage and output escaping during rendering, an authenticated user with Author-level permissions or higher can inject arbitrary JavaScript into their profile. This script executes when any user (including administrators) views the affected user's profile page or any page rendering their h-card (IndieWeb profile).

2. Attack Vector Analysis

  • Endpoint: wp-admin/profile.php (standard WordPress profile update) or a specific IndieWeb settings page if the plugin overrides the profile UI.
  • Vulnerable Parameter: telephone (or a variation like tel found in the h-card settings).
  • Action: update (via POST request).
  • Authentication Required: Author-level access (PR:L in CVSS, specifically Author+). Authors can edit their own profile fields by default in WordPress.
  • Preconditions: The IndieWeb plugin must be active, and h-card functionality (which typically exposes the telephone field) should be enabled.

3. Code Flow (Inferred)

  1. Input Registration: The plugin uses hooks like show_user_profile and edit_user_profile to inject a "Telephone" input field into the WordPress user profile page.
  2. Storage: The plugin hooks into personal_options_update and edit_user_profile_update.
  3. Vulnerable Sink (Storage): The handler function likely retrieves $_POST['telephone'] and saves it using update_user_meta($user_id, 'tel', $_POST['telephone']) without applying sanitize_text_field() or wp_strip_all_tags().
  4. Retrieval: When rendering the h-card (frontend or backend), the plugin calls get_user_meta($user_id, 'tel', true).
  5. Vulnerable Sink (Output): The retrieved value is printed directly to the page (e.g., echo $tel;) without using esc_attr() or esc_html().

4. Nonce Acquisition Strategy

Since this vulnerability involves updating a WordPress user profile, it is protected by the core WordPress profile nonce.

  1. Navigate to Profile: Use browser_navigate to go to http://[target]/wp-admin/profile.php.
  2. Extract Nonce: Use browser_eval to extract the core _wpnonce and the user ID.
    // Extract the profile update nonce
    document.querySelector('#_wpnonce').value;
    
  3. Extract Field Name: Confirm the exact name attribute of the Telephone field.
    // Find the input associated with "Telephone"
    Array.from(document.querySelectorAll('label')).find(el => el.textContent.includes('Telephone'))?.getAttribute('for');
    

5. Exploitation Strategy

The goal is to inject a payload into the "Telephone" field that triggers when the profile is viewed.

  1. Login as Author: Log into the WordPress instance with Author credentials.
  2. Identify Field: Locate the telephone field on the profile page. (Commonly tel or indieweb_telephone).
  3. Submit Update: Send a POST request to wp-admin/profile.php with the XSS payload.
    • URL: http://localhost:8080/wp-admin/profile.php
    • Method: POST
    • Headers: Content-Type: application/x-www-form-urlencoded
    • Payload:
      _wpnonce=[NONCE]&_wp_http_referer=/wp-admin/profile.php&user_id=[ID]&nickname=[NICK]&email=[EMAIL]&telephone="><script>alert(document.domain)</script>&action=update
      
  4. Trigger XSS: Navigate to the author's public profile page (e.g., /?author=[ID]) or the page where the IndieWeb h-card is rendered.

6. Test Data Setup

  1. Install Plugin: Ensure IndieWeb <= 4.0.5 is installed and activated.
  2. Create User:
    wp user create attacker attacker@example.com --role=author --user_pass=password
    
  3. Configure IndieWeb: Ensure "User Profiles" or "H-Card" support is enabled in IndieWeb settings if required.
  4. Create Page: (If needed to view the h-card)
    wp post create --post_type=page --post_status=publish --post_title="IndieWeb Test" --post_content='[indieweb_hcard]'
    

7. Expected Results

  • Upon submitting the profile update, the "Telephone" field in the database should contain the raw <script> tag.
  • When visiting the Author's profile page or the page containing the h-card, the browser should execute the JavaScript, resulting in an alert box.
  • The HTML source of the rendered page should show: <input ... value=""><script>alert(document.domain)</script>"> (if injected into an attribute) or simply the raw script tag in the body.

8. Verification Steps

  1. Check Database: Verify the meta value is stored unsanitized.
    wp user meta get [USER_ID] tel
    
  2. Inspect HTTP Response: Use http_request to GET the profile page and grep for the payload.
    # Expected to find unescaped script
    http_request GET "http://localhost:8080/author/attacker/" | grep "<script>alert"
    

9. Alternative Approaches

  • Attribute Breakout: If the telephone is rendered inside an href="tel:..." attribute, use:
    javascript:alert(1) or "><script>alert(1)</script>
  • Admin Page XSS: Check if the XSS triggers in the admin dashboard when an administrator views the "All Users" list or the specific user's edit page. This would upgrade the impact to a potential privilege escalation.
  • REST API: Check if the telephone field is accessible/updatable via the /wp/v2/users/me REST API endpoint, which might bypass profile page nonce requirements if the plugin extends the REST API.
Research Findings
Static analysis — not yet PoC-verified

Summary

The IndieWeb plugin for WordPress (<= 4.0.5) is vulnerable to Stored Cross-Site Scripting (XSS) via the 'Telephone' profile parameter. Authenticated users with Author-level access or higher can inject arbitrary JavaScript into their profile settings, which then executes in the browser of any user viewing the affected profile or its associated h-card. This occurs because the plugin fails to sanitize the input during storage and lacks proper output escaping during rendering.

Vulnerable Code

// Inferred from research plan: Storage logic in profile update handler
update_user_meta($user_id, 'tel', $_POST['telephone']);

---

// Inferred from research plan: Retrieval and output logic in h-card or profile display
$tel = get_user_meta($user_id, 'tel', true);
echo $tel;

Security Fix

--- a/indieweb.php
+++ b/indieweb.php
@@ -10,1 +10,1 @@
-update_user_meta($user_id, 'tel', $_POST['telephone']);
+update_user_meta($user_id, 'tel', sanitize_text_field($_POST['telephone']));
@@ -20,1 +20,1 @@
-$tel = get_user_meta($user_id, 'tel', true);
-echo $tel;
+$tel = get_user_meta($user_id, 'tel', true);
+echo esc_attr($tel);

Exploit Outline

To exploit this vulnerability, an attacker must have at least Author-level privileges. 1. Log in to the WordPress dashboard and navigate to the profile edit page (wp-admin/profile.php). 2. Locate the 'Telephone' field provided by the IndieWeb plugin. 3. Inject a payload such as '"><script>alert(document.domain)</script>' into the field. 4. Save the profile changes. 5. The XSS triggers whenever the attacker's profile page is viewed by an administrator or any other user, or whenever a page rendering the user's IndieWeb h-card is accessed.

Check if your site is affected.

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