CVE-2026-2354

Swiss Toolkit For WP <= 1.4.6 - Authenticated (Author+) Arbitrary File Upload via upload_extension_files()

highUnrestricted Upload of File with Dangerous Type
8.8
CVSS Score
8.8
CVSS Score
high
Severity
Unpatched
Patched in
N/A
Time to patch

Description

The Swiss Toolkit For WP plugin for WordPress is vulnerable to arbitrary file upload due to a flawed file type validation bypass in the `upload_extension_files()` function in all versions up to, and including, 1.4.6. The `upload_extension_files()` function hooks into WordPress's `wp_check_filetype_and_ext` filter and uses `strpos()` to check if a filename contains a configured extension string, rather than verifying the actual file extension. This makes it possible for authenticated attackers, with Author-level access and above, to upload arbitrary files (including PHP) on the affected site's server which may make remote code execution possible, granted the "Enhanced Multi-Format Image Support" feature is enabled with at least one extension (e.g., avif) in the allowed formats.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=1.4.6
PublishedJuly 10, 2026
Last updatedJuly 11, 2026
Affected pluginswiss-toolkit-for-wp
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-2354 - Swiss Toolkit For WP Arbitrary File Upload ## 1. Vulnerability Summary The **Swiss Toolkit For WP** plugin (versions <= 1.4.6) contains an arbitrary file upload vulnerability. The flaw exists in the `upload_extension_files()` function, which is hooked i…

Show full research plan

Exploitation Research Plan: CVE-2026-2354 - Swiss Toolkit For WP Arbitrary File Upload

1. Vulnerability Summary

The Swiss Toolkit For WP plugin (versions <= 1.4.6) contains an arbitrary file upload vulnerability. The flaw exists in the upload_extension_files() function, which is hooked into the WordPress wp_check_filetype_and_ext filter.

The function attempts to allow additional image formats (like .avif) by checking if the filename contains an allowed extension string. However, it uses strpos() for this check instead of verifying that the file actually ends with the allowed extension or validating the MIME type correctly. This allows an authenticated user with Author-level permissions or higher to upload PHP files by including an allowed extension string anywhere in the filename (e.g., malicious.avif.php).

2. Attack Vector Analysis

  • Endpoint: wp-admin/async-upload.php (Standard WordPress media upload handler).
  • Action: upload-attachment.
  • Authentication: Authenticated, Author level or higher (requires upload_files capability).
  • Precondition: The feature "Enhanced Multi-Format Image Support" must be enabled in the Swiss Toolkit settings, and at least one extension (e.g., avif) must be added to the allowed formats list.
  • Payload Parameter: async-upload (the file blob) and _wpnonce.

3. Code Flow (Inferred)

  1. Entry Point: An Author user uploads a file via the Media Library or a post editor.
  2. WordPress Core: wp_handle_upload() calls wp_check_filetype_and_ext().
  3. Plugin Hook: The plugin's upload_extension_files() function intercepts the check via the wp_check_filetype_and_ext filter.
  4. Vulnerable Logic:
    • The function retrieves the list of "Enhanced Multi-Format" extensions from the database (e.g., ['avif']).
    • It iterates through these extensions and checks the filename: if (strpos($filename, $extension) !== false).
    • If found, it modifies the return array to set type (MIME) and ext to match the allowed extension, effectively "blessing" the file.
  5. Sink: WordPress core accepts the file as a valid image but preserves the original .php extension during the final move to the uploads directory because the sanitization was bypassed by the filter's return value.

4. Nonce Acquisition Strategy

This exploit requires a valid _wpnonce associated with the media-form action (used by async-upload.php).

  1. Authentication: Log in as an Author-level user.
  2. Navigation: Use browser_navigate to go to wp-admin/media-new.php.
  3. Extraction:
    • The nonce is typically found in the _wpnonce field of the upload form or within the plupload configuration object.
    • Action: browser_eval("wp.Uploader.defaults.multipart_params._wpnonce") or browser_eval("document.querySelector('#_wpnonce').value").
    • (Inferred) The localization key for media nonces in WordPress is often under the _wpnonce key in the global pluploadL10n or wpUploaderInit objects.

5. Exploitation Strategy

Step 1: Configuration

Ensure the plugin is configured to be vulnerable. This requires modifying plugin options to enable the specific feature.

Step 2: Payload Construction

Create a PHP file named shell.avif.php.
Content: <?php phpinfo(); ?>

Step 3: Upload Request

Send a multipart/form-data request to async-upload.php.

Request Details:

  • Method: POST
  • URL: http://TARGET/wp-admin/async-upload.php
  • Headers:
    • Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
  • Body Parameters:
    • name: shell.avif.php
    • action: upload-attachment
    • _wpnonce: [EXTRACTED_NONCE]
    • async-upload: [FILE_CONTENT]

Step 4: Execution

Access the uploaded file. WordPress stores uploads in wp-content/uploads/YYYY/MM/.
URL: http://TARGET/wp-content/uploads/[YEAR]/[MONTH]/shell.avif.php

6. Test Data Setup

  1. User: Create an Author user:
    • wp user create attacker attacker@example.com --role=author --user_pass=password123
  2. Plugin Settings (Crucial):
    • Identify the option name for Swiss Toolkit (likely swiss_toolkit_settings or similar - (inferred)).
    • Enable the "Enhanced Multi-Format Image Support".
    • Add avif to the list of allowed extensions.
    • Note: If the UI is complex, use wp option get to find the correct structure and wp option update to set it.

7. Expected Results

  • HTTP Response: The async-upload.php endpoint should return a 200 OK with a JSON object containing the attachment ID and the success status.
  • File System: A file named shell.avif.php should exist in the wp-content/uploads/ directory.
  • Remote Code Execution: Navigating to the file URL should execute the PHP code and display the phpinfo() output.

8. Verification Steps

  1. Check for file existence via CLI:
    • find /var/www/html/wp-content/uploads/ -name "shell.avif.php"
  2. Verify via HTTP:
    • http_request('GET', 'http://localhost:8080/wp-content/uploads/[YYYY]/[MM]/shell.avif.php')
    • Check if response body contains "PHP Version".

9. Alternative Approaches

  • If strpos is used differently: If the plugin checks the start of the string instead of just the presence, try naming the file avif.php.
  • Alternative Extension: If avif is not enabled by default, try common alternatives like webp, svg, or heic if they appear in the plugin's settings.
  • Bypass wp_check_filetype_and_ext via Null Byte (Old PHP): If the environment is running an ancient PHP version (unlikely), shell.php%00.avif might be attempted, though this vulnerability is described as a logic flaw in the string check.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Swiss Toolkit For WP plugin for WordPress is vulnerable to arbitrary file upload in versions up to 1.4.6 because it uses the strpos() function to validate file extensions. This logic flaw allows authenticated attackers with Author-level permissions to bypass security filters and upload executable PHP files by including a permitted extension string anywhere in the filename.

Vulnerable Code

/* File: swiss-toolkit-for-wp/includes/class-swiss-toolkit-for-wp.php (inferred) */

// Hooked into wp_check_filetype_and_ext
public function upload_extension_files($data, $file, $filename, $mimes) {
    $settings = get_option('swiss_toolkit_settings');
    $allowed_extensions = $settings['allowed_formats'] ?? [];

    foreach ($allowed_extensions as $extension) {
        // Vulnerable check: looks for existence anywhere in the string
        if (strpos($filename, $extension) !== false) {
            $data['ext'] = $extension;
            $data['type'] = 'image/' . $extension;
            break;
        }
    }
    return $data;
}

Security Fix

--- a/swiss-toolkit-for-wp/includes/class-swiss-toolkit-for-wp.php
+++ b/swiss-toolkit-for-wp/includes/class-swiss-toolkit-for-wp.php
@@ -10,7 +10,8 @@
     $allowed_extensions = $settings['allowed_formats'] ?? [];
 
     foreach ($allowed_extensions as $extension) {
-        if (strpos($filename, $extension) !== false) {
+        $file_ext = pathinfo($filename, PATHINFO_EXTENSION);
+        if (strtolower($file_ext) === strtolower($extension)) {
             $data['ext'] = $extension;
             $data['type'] = 'image/' . $extension;
             break;

Exploit Outline

An authenticated user with Author-level access or higher can exploit this vulnerability if the 'Enhanced Multi-Format Image Support' feature is enabled. The attacker identifies an allowed extension (e.g., 'avif') and uploads a PHP file with a double extension, such as 'shell.avif.php', through the WordPress media uploader (/wp-admin/async-upload.php). Because the plugin's validation logic only checks for the presence of the 'avif' string anywhere in the filename using strpos(), it marks the file as valid. WordPress then saves the file to the uploads directory with its original .php extension, allowing the attacker to execute arbitrary code by navigating to the file's URL.

Check if your site is affected.

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