[Auth] User-bind mode does not check the bound account is the one whose groups it returns (privilege escalation) #40
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
From the scadaproj#15 shared-library review (
code-reviews/Auth/findings.md, Auth-007), verified at91fb2b4. Update the finding's Resolution row when fixed.ZB.MOM.WW.Auth/src/ZB.MOM.WW.Auth.Ldap/LdapAuthService.cs:329Description
CompleteAsUserauthenticates by binding asUserBindTemplate.Replace("{0}", username)(line 329–332). It thenruns a separate search,
(<UserNameAttribute>=<username>)(line 345 →SearchUser, line 361), and returns thegroups and username of whatever single entry that search finds (line 349 →
SucceedFromEntry). Two differentdirectory lookups resolve the bind name and the search filter. Nothing checks that the entry found is the account
the directory just authenticated. There is no comparison of DNs, no RFC 4532 "Who am I?", and no search keyed on
the bind identity. The XML doc and README describe the UPN mismatch as guidance only ("a mismatch can … bind a
different account than the one whose groups are read"). The validator's failure message
(
LdapOptionsValidator.cs:77) offers"{0}@example.com"as its first example template. A DN-form template is alsospliced in unescaped (documented), which is another way for the bind identity and the search key to diverge.
Failure scenario
BindMode=UserwithUserBindTemplate="{0}@corp.com"andUserNameAttribute=sAMAccountNameon AD, a combinationthe validator message suggests. Mallory's account has explicit UPN
jsmith@corp.comandsAMAccountName=jsmith2,which is common after a rename or when two people share a name. A separate admin account has
sAMAccountName=jsmith.Mallory signs in as
jsmithwith her own password:(sAMAccountName=jsmith)returns the admin's entry.LdapAuthResult.Success("jsmith", "Admin Smith", ["UG_Admins"])is returned.That is privilege escalation with valid credentials of a different user. I reproduced this through the
FakeLdapConnectionseam: the bind went tojsmith@corp.testand the result carried the other entry's groups.Recommendation
After the bind, identify the entry by the bound identity, not the typed name:
userPrincipalName=<bindName>.u:DOMAIN\sam) and check it againstthe found entry's
sAMAccountName.{0}(RFC 4514).Fail with
GroupLookupFailedon a mismatch. At minimum, reorder the validator example to lead withEXAMPLE\{0}. Add a test where the bind name and the searched entry differ.Fixed on main
d549a78(Auth-007 → Resolved), Auth 0.4.4 unpublished. Reproduced live on a throwaway GLAuth (config committed under tests/.../Integration/glauth-userbind/): mallory logged in with her own password and got boss's Admins group. Fix: after the bind, LDAP 'Who am I?' (RFC 4532) identifies the bound account; the looked-up entry must be that account (DN or sAMAccountName match). If the server can't answer, only a DN template counts as proof (bind DN must equal the found entry's DN); otherwise GroupLookupFailed. {0} is now DN-escaped in DN templates; validator message leads with EXAMPLE{0}. 11/13 new unit tests + the live mismatch test fail without the fix; live positive control passes. Consumer impact: AD down-level/UPN templates unchanged where UPN and sAMAccountName agree; a UPN template now refuses accounts where they differ; non-AD User mode needs an exact DN template (GLAuth: cn={0},ou=,ou=users,). Only User-mode consumer is BTDB Manager (AD). Evidence: LocalDb 236/236; Auth 344 passed / 78 skipped (live SQL/LDAP gated) after rebase; code-reviews --check consistent.