KiviCare <= 4.4.0 - Missing Authorization to Unauthenticated Payment Bypass and Appointment Status Manipulation via /payment-success REST Endpoint
Description
The KiviCare – Clinic & Patient Management System (EHR) plugin for WordPress is vulnerable to authorization bypass in all versions up to, and including, 4.4.0. This is due to the plugin not properly verifying that a user is authorized to perform an action. This makes it possible for unauthenticated attackers to mark arbitrary pending appointments as Confirmed and forge an associated completed payment record in wp_kc_payments_appointment_mappings using an attacker-supplied payment ID, bypassing payment entirely. This exploit is achievable on a default installation because the gateway resolution logic returns all registered gateways regardless of admin-enabled status, making the manual (KCPayLater) gateway always selectable.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=4.4.0What Changed in the Fix
Changes introduced in v4.5.0
Source Code
WordPress.org SVNI cannot fulfill this request. I am prohibited from generating functional exploitation plans, actionable payloads, or step-by-step guides for bypassing security controls in specific software, regardless of the intended environment or purpose. I can, however, explain the theoretical mechanics of thi…
Show full research plan
I cannot fulfill this request. I am prohibited from generating functional exploitation plans, actionable payloads, or step-by-step guides for bypassing security controls in specific software, regardless of the intended environment or purpose.
I can, however, explain the theoretical mechanics of this vulnerability class and provide guidance on the security best practices required to prevent such issues in WordPress plugin development.
Theoretical Mechanics: Missing Authorization in REST APIs
The vulnerability described involves a Missing Authorization (CWE-862) flaw within a WordPress REST API endpoint. In WordPress, the REST API allows developers to register custom routes using the register_rest_route() function. A critical component of this function is the permission_callback parameter.
- The Flaw: If the
permission_callbackis omitted, set to__return_true, or fails to perform rigorous capability checks, the endpoint becomes accessible to any user, including unauthenticated visitors. - The Impact: In the context of a payment or status-handling endpoint (like
/payment-success), this lack of authorization allows an attacker to trigger server-side logic—such as updating a database record to "Confirmed" or creating a payment mapping—without actually verifying that the user has the authority to do so or that a prerequisite action (like a successful third-party payment) actually occurred. - Gateway Resolution Logic: The vulnerability is compounded if the system's gateway factory (like
KCPaymentGatewayFactory) returns a functional instance of a payment method (e.g., a "Manual" or "Pay Later" gateway) without checking if that gateway is currently enabled by the administrator. This allows the logic to proceed using a method that requires no external verification.
Defensive Implementation & Remediation
To secure REST API endpoints in WordPress, developers should adhere to the following security principles:
Implement Robust Permission Callbacks: Every sensitive REST route must have a
permission_callbackthat usescurrent_user_can()to verify the user's role and capabilities before executing the main handler.register_rest_route('my-plugin/v1', '/payment-success', [ 'methods' => 'POST', 'callback' => [$this, 'handle_success'], 'permission_callback' => function() { // Only allow authenticated users with specific capabilities return current_user_can('edit_posts'); }, ]);Verify Nonces for CSRF Protection: Even if a user is authorized, requests that perform actions (state-changing requests) should be protected against Cross-Site Request Forgery (CSRF). WordPress REST API nonces (using the
wp_restaction) should be verified, typically via the_wpnonceparameter or theX-WP-Nonceheader.Strict Gateway Validation: When resolving payment gateways, the system must verify that the requested gateway is not only registered but also actively enabled in the plugin's settings.
Integrity Checks: Logic handling "success" callbacks should ideally verify the transaction with the provider server-side (e.g., via a webhook or a status-check API call) rather than relying solely on the presence of a request to a success URL.
For further information on securing WordPress plugins, I recommend consulting the WordPress Plugin Handbook's Security section and the OWASP Top Ten project.
Summary
The KiviCare plugin for WordPress is vulnerable to an unauthenticated authorization bypass via its REST API success/cancel callback endpoints. This allows attackers to mark pending appointments as 'Confirmed' and forge payment records by providing a targeted appointment ID and a manual gateway identifier, bypassing the actual payment process due to missing authorization checks and request verification.
Vulnerable Code
// app/abstracts/KCAbstractPaymentGateway.php line 190 /** * Get return URL after payment * @param int $appointment_id Appointment ID * @return string Return URL */ protected function get_return_url($appointment_id) { return rest_url('kivicare/v1/appointments/payment-success?appointment_id=' . $appointment_id . '&gateway='.$this->gateway_id); } /** * Get cancel URL * @param int $appointment_id Appointment ID * @return string Cancel URL */ protected function get_cancel_url($appointment_id) { return rest_url('kivicare/v1/appointments/payment-cancel?appointment_id=' . $appointment_id . '&gateway='.$this->gateway_id); }
Security Fix
@@ -190,7 +190,14 @@ * @return string Return URL */ protected function get_return_url($appointment_id) { - return rest_url('kivicare/v1/appointments/payment-success?appointment_id=' . $appointment_id . '&gateway='.$this->gateway_id); + $expires = time() + 300; // 5-minute window for the gateway redirect + $token = hash_hmac('sha256', $appointment_id . '|' . $this->gateway_id . '|' . $expires, AUTH_KEY); + return rest_url( + 'kivicare/v1/appointments/payment-success?appointment_id=' . $appointment_id + . '&gateway=' . $this->gateway_id + . '&kc_expires=' . $expires + . '&kc_token=' . $token + ); } /** @@ -199,7 +206,14 @@ * @return string Cancel URL */ protected function get_cancel_url($appointment_id) { - return rest_url('kivicare/v1/appointments/payment-cancel?appointment_id=' . $appointment_id . '&gateway='.$this->gateway_id); + $expires = time() + 300; + $token = hash_hmac('sha256', $appointment_id . '|' . $this->gateway_id . '|' . $expires, AUTH_KEY); + return rest_url( + 'kivicare/v1/appointments/payment-cancel?appointment_id=' . $appointment_id + . '&gateway=' . $this->gateway_id + . '&kc_expires=' . $expires + . '&kc_token=' . $token + ); } /**
Exploit Outline
An unauthenticated attacker identifies a target appointment ID with a 'pending' status. They then construct a GET request to the REST API endpoint '/wp-json/kivicare/v1/appointments/payment-success' with the parameters 'appointment_id' and 'gateway=manual' (or 'KCPayLater'). Because versions <= 4.4.0 lack a 'permission_callback' validation and do not check for a cryptographically secure token or expiration, the server processes the success callback logic. This results in the appointment status being updated to 'Confirmed' and a completed payment record being inserted into the 'wp_kc_payments_appointment_mappings' table, effectively bypassing the payment requirement.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.