Image Hotspot by DevVN <= 1.2.9 - Authenticated (Author+) Stored Cross-Site Scripting via Custom Field Meta
Description
The Image Hotspot by DevVN plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'hotspot_content' custom field meta in all versions up to, and including, 1.2.9 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
<=1.2.9Source Code
WordPress.org SVN# Research Plan: CVE-2025-14445 - Image Hotspot by DevVN Stored XSS ## 1. Vulnerability Summary The **Image Hotspot by DevVN** plugin (up to version 1.2.9) is vulnerable to **Authenticated Stored Cross-Site Scripting (XSS)**. The vulnerability exists because the plugin fails to sanitize or escape t…
Show full research plan
Research Plan: CVE-2025-14445 - Image Hotspot by DevVN Stored XSS
1. Vulnerability Summary
The Image Hotspot by DevVN plugin (up to version 1.2.9) is vulnerable to Authenticated Stored Cross-Site Scripting (XSS). The vulnerability exists because the plugin fails to sanitize or escape the hotspot_content metadata when saving and subsequently rendering it. Users with Author level permissions or higher can inject malicious scripts into this field, which will execute in the browser of any user (including administrators) who views the affected page or post.
2. Attack Vector Analysis
- Vulnerable Parameter:
hotspot_content(found in post metadata). - Vulnerable Endpoint: Standard WordPress post update endpoint (
/wp-admin/post.php) or a specific AJAX handler if the plugin uses one for hotspot management. - Authentication Requirement: Authenticated user with Author role or higher (required to edit/create posts and manage their own hotspots).
- Preconditions: The plugin must be active, and a hotspot must be created or edited.
3. Code Flow (Inferred)
- Input Source: An Author or higher user submits a request to save a hotspot. This typically involves the
save_posthook or an AJAX action. - Processing: The plugin captures the
hotspot_contentvalue from$_POST. - Storage (Sink 1): The plugin calls
update_post_meta($post_id, 'hotspot_content', $raw_input)without applyingwp_kses_post()orsanitize_text_field(). - Retrieval: When a frontend page containing the hotspot is loaded (often via a shortcode like
[devvn_ihotspot]), the plugin callsget_post_meta($post_id, 'hotspot_content', true). - Output (Sink 2): The retrieved content is echoed directly into the HTML response (e.g.,
echo $hotspot_content;) without being passed throughesc_html()orwp_kses().
4. Nonce Acquisition Strategy
Since this exploit requires Author level access, the agent will have a valid session and can access the WordPress admin dashboard.
- Identify the Post Type: The plugin likely registers a custom post type for hotspots (e.g.,
ihotspot). - Navigate to Editor: Navigate to
/wp-admin/post-new.php?post_type=ihotspot(inferred slug). - Extract Nonces:
- The standard WordPress post editing nonce is stored in the hidden input
_wpnonce. - If the plugin uses a custom AJAX interface, navigate to the hotspot editor and use
browser_evalto find any localized script variables:browser_eval("window.devvn_ihotspot_params?.nonce")(inferred name).- Alternatively, search the HTML source for
nonceusingbrowser_navigate.
- The standard WordPress post editing nonce is stored in the hidden input
5. Exploitation Strategy
Step 1: Authentication
Log in to the WordPress instance as a user with the Author role.
Step 2: Identification of Post Meta Structure
Before injecting, we need to know how hotspot_content is formatted. It is often part of a JSON-encoded array or a repeater field.
- Action: Create a dummy hotspot with the text "NORMAL_CONTENT" and save it.
- Verification: Use
wp post meta list <ID>to see exactly how the data is stored in the database.
Step 3: Injection via Post Update
Assuming the standard post.php flow:
- Request Method: POST
- URL:
http://localhost:8080/wp-admin/post.php - Content-Type:
application/x-www-form-urlencoded - Body Parameters:
action:editpostpost_ID:<TARGET_POST_ID>_wpnonce:<EXTRACTED_NONCE>hotspot_content(or the specific meta box field name):{"1":{"content":"<img src=x onerror=alert('CVE-2025-14445')>"}}(Structure inferred; format as discovered in Step 2).
Step 4: Triggering the XSS
- Action: Navigate to the public URL of the post where the hotspot is embedded or view the hotspot CPT directly if it has a frontend view.
- Target URL:
http://localhost:8080/?p=<TARGET_POST_ID>or the page containing the[devvn_ihotspot]shortcode.
6. Test Data Setup
- Create Author User:
wp user create attacker attacker@example.com --role=author --user_pass=password - Create Hotspot Post:
- As admin:
wp post create --post_type=ihotspot --post_title="XSS Test" --post_status=publish --post_author=<ATTACKER_ID> - (Note: Adjust
ihotspotif the actual slug differs after plugin inspection).
- As admin:
- Embed Shortcode: Create a standard page to display the hotspot:
wp post create --post_type=page --post_title="Hotspot Display" --post_content='[devvn_ihotspot id="<HOTSPOT_ID>"]' --post_status=publish
7. Expected Results
- The
hotspot_contentmeta should be saved exactly as provided, including the<script>or<img>tags. - When viewing the page containing the hotspot, the browser should execute the JavaScript (e.g., an alert box appearing).
- The response HTML should contain the raw, unescaped payload:
...content="<img src=x onerror=alert('CVE-2025-14445')>"...
8. Verification Steps
- Check Database State:
wp post meta get <POST_ID> hotspot_content
Verify that the output contains the raw HTML payload and hasn't been stripped bysanitize_text_field. - Verify Frontend Output:
Usehttp_requestto GET the page containing the hotspot and grep for the payload string.response_body.contains("<img src=x onerror=alert('CVE-2025-14445')>")
9. Alternative Approaches
- AJAX Entry Point: If the plugin doesn't use the standard
post.phpfor meta, search forwp_ajax_devvn_save_hotspot(inferred) in the plugin code and target that AJAX action. - Payload Variations:
- If
"is escaped but'is not:<img src=x onerror='alert(1)'> - If simple tags are blocked, try SVG-based XSS:
<svg/onload=alert(1)> - If the content is rendered inside a JS string (e.g., a tool-tip library), try breaking out of the string:
");alert(1);//
- If
- Shortcode Discovery: If
[devvn_ihotspot]is incorrect, usegrep -r "add_shortcode" wp-content/plugins/devvn-image-hotspot/to find the correct shortcode. - Meta Key Discovery: If
hotspot_contentis not the direct key, usewp post meta list <ID>after a manual save to identify the exact key (e.g., it might be prefixed like_devvn_hotspot_data).
Summary
The Image Hotspot by DevVN plugin for WordPress is vulnerable to Authenticated Stored Cross-Site Scripting via the 'hotspot_content' meta field. Authenticated users with Author-level permissions or higher can inject malicious JavaScript into this field, which is stored without sanitization and rendered on the frontend without proper escaping, leading to script execution in the context of any user viewing the affected page.
Vulnerable Code
// In devvn-image-hotspot/admin/admin.php (inferred location) // Processing and saving the meta data without sanitization if (isset($_POST['hotspot_content'])) { update_post_meta($post_id, 'hotspot_content', $_POST['hotspot_content']); } --- // In devvn-image-hotspot/includes/frontend.php (inferred location) // Retrieving and echoing the meta data without escaping $hotspot_content = get_post_meta($post_id, 'hotspot_content', true); // ... rendering logic ... echo '<div class="hotspot-tooltip">' . $hotspot_content . '</div>';
Security Fix
@@ -10,7 +10,7 @@ - update_post_meta($post_id, 'hotspot_content', $_POST['hotspot_content']); + update_post_meta($post_id, 'hotspot_content', wp_kses_post($_POST['hotspot_content'])); @@ -45,7 +45,7 @@ - echo '<div class="hotspot-tooltip">' . $hotspot_content . '</div>'; + echo '<div class="hotspot-tooltip">' . wp_kses_post($hotspot_content) . '</div>';
Exploit Outline
The exploit requires an attacker to have Author-level authentication. 1. The attacker logs into the WordPress dashboard and creates or edits an 'Image Hotspot' post. 2. During the save process, the attacker intercepts the request (typically a POST request to post.php) and modifies the 'hotspot_content' parameter to include a malicious payload, such as <img src=x onerror=alert(document.cookie)>. 3. The plugin saves this raw payload into the post metadata. 4. The attacker then places the shortcode for that hotspot (e.g., [devvn_ihotspot id="123"]) onto any public page. 5. When any user, including an administrator, views that page, the unsanitized metadata is rendered directly into the HTML, triggering the JavaScript execution in their browser.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.