IndieWeb <= 4.0.5 - Authenticated (Author+) Stored Cross-Site Scripting via 'Telephone' Parameter
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:NTechnical Details
Source Code
WordPress.org SVN# 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 liketelfound in the h-card settings). - Action:
update(via POST request). - Authentication Required: Author-level access (
PR:Lin 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)
- Input Registration: The plugin uses hooks like
show_user_profileandedit_user_profileto inject a "Telephone" input field into the WordPress user profile page. - Storage: The plugin hooks into
personal_options_updateandedit_user_profile_update. - Vulnerable Sink (Storage): The handler function likely retrieves
$_POST['telephone']and saves it usingupdate_user_meta($user_id, 'tel', $_POST['telephone'])without applyingsanitize_text_field()orwp_strip_all_tags(). - Retrieval: When rendering the h-card (frontend or backend), the plugin calls
get_user_meta($user_id, 'tel', true). - Vulnerable Sink (Output): The retrieved value is printed directly to the page (e.g.,
echo $tel;) without usingesc_attr()oresc_html().
4. Nonce Acquisition Strategy
Since this vulnerability involves updating a WordPress user profile, it is protected by the core WordPress profile nonce.
- Navigate to Profile: Use
browser_navigateto go tohttp://[target]/wp-admin/profile.php. - Extract Nonce: Use
browser_evalto extract the core_wpnonceand the user ID.// Extract the profile update nonce document.querySelector('#_wpnonce').value; - 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.
- Login as Author: Log into the WordPress instance with Author credentials.
- Identify Field: Locate the
telephonefield on the profile page. (Commonlytelorindieweb_telephone). - Submit Update: Send a POST request to
wp-admin/profile.phpwith 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
- URL:
- 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
- Install Plugin: Ensure IndieWeb <= 4.0.5 is installed and activated.
- Create User:
wp user create attacker attacker@example.com --role=author --user_pass=password - Configure IndieWeb: Ensure "User Profiles" or "H-Card" support is enabled in IndieWeb settings if required.
- 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
- Check Database: Verify the meta value is stored unsanitized.
wp user meta get [USER_ID] tel - Inspect HTTP Response: Use
http_requestto 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
telephonefield is accessible/updatable via the/wp/v2/users/meREST API endpoint, which might bypass profile page nonce requirements if the plugin extends the REST API.
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
@@ -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.