Fedora Account System
Red Hat Associate
Red Hat Customer
AI_ONLY_REPORT package: sssd-2.12.0-1.el10 ------ Summary: LDAP Access Control Bypass When `pwd_expire_policy_warn` Precedes Restrictive Rules in `ldap_access_order`: an expired-password warning is converted into a successful PAM result before later LDAP access rules run, allowing configured `filter`, `host`, or similar restrictive checks to be bypassed. Requirements to exploit: A valid LDAP-backed account whose password is expired, an LDAP deployment using `access_provider = ldap` with a compatible password-expiration policy, `ldap_access_order` placing `pwd_expire_policy_warn` before restrictive rules, and a non-password authentication path such as SSH public key that still invokes SSSD account/access checks. Component affected: `sssd-2.12.0-1.el10`, LDAP access-control evaluation in `src/providers/ldap/sdap_access.c` (`sdap_access_check_next_rule()`) and success mapping in `src/providers/ldap/ldap_access.c` (`sdap_pam_access_handler_done()`). Version affected: `sssd-2.12.0-1.el10` in LDAP deployments where `ldap_access_order` places `pwd_expire_policy_warn` before restrictive rules. Patch available: no released package fix established; proposed patch included below Version fixed: unknown Upstream coordination: Not notified. CVSS: CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N - 6.8 (MEDIUM) AV:N - reachable over the network through services that rely on SSSD LDAP account/access checks, such as SSH. AC:H - exploitation depends on a specific non-default rule ordering, an expired-password state, and a compatible non-password authentication path. PR:L - the attacker needs a valid account that can authenticate to the target service. UI:N - no separate victim interaction is required once the attacker initiates authentication. S:U - the impact remains within the vulnerable service's own security scope. C:H - a successful bypass can expose data on systems or services that should have remained inaccessible under `filter`, `host`, or `rhost` restrictions. I:H - the same bypass can permit unauthorized actions within those protected systems or services. A:N - the flaw does not inherently reduce availability. Impact: Moderate. This is a real authorization bypass with meaningful confidentiality and integrity consequences where LDAP access rules are used as security boundaries, but exploitation requires a valid account, an expired-password condition, and a specific non-default `ldap_access_order` configuration. That fits a security flaw that can compromise resources under certain circumstances, while being less easily exploitable than a default-path or broadly reachable access-control failure. Embargo: no Reason: the issue is configuration-dependent, requires an already valid account, and can be mitigated immediately by reordering or removing `pwd_expire_policy_warn` from the front of `ldap_access_order`. Acknowledgement: Aisle Research Vulnerability Details: The documented purpose of `pwd_expire_policy_warn` is to warn while still allowing login. The issue here is narrower: in the LDAP access path, that warning state terminates ordered rule evaluation before later restrictive checks are reached. In `sdap_access_check_next_rule()`, rule processing only continues while `ret == EOK`; when `LDAP_ACCESS_EXPIRE_POLICY_WARN` converts an expired password into `ERR_PASSWORD_EXPIRED_WARN`, the loop stops and returns that non-`EOK` status. ```c while (ret == EOK) { ... case LDAP_ACCESS_EXPIRE_POLICY_WARN: ret = perform_pwexpire_policy(state, state->domain, state->pd, state->access_ctx->type, state->access_ctx->id_ctx->opts); if (ret == ERR_PASSWORD_EXPIRED) { ret = ERR_PASSWORD_EXPIRED_WARN; } break; ... } state->current_rule++; } return ret; ``` That returned status is then treated as success in `sdap_pam_access_handler_done()`, which maps both `EOK` and `ERR_PASSWORD_EXPIRED_WARN` to `PAM_SUCCESS`. In deployments using an ordered configuration such as `pwd_expire_policy_warn,filter,host`, later `filter`, `host`, or `rhost` checks are skipped entirely, so an expired-password user can be accepted by the warning path before the restrictive rules run. Steps to reproduce: 1. Configure an LDAP-backed SSSD domain with `access_provider = ldap`, `ldap_pwd_policy = shadow` (or `mit_kerberos`), `ldap_access_order = pwd_expire_policy_warn,filter,host`, and a restrictive `ldap_access_filter` or host rule that should deny the test user. 2. Ensure the test user's password is marked expired in the LDAP attributes used by the selected password policy. 3. Authenticate through a non-password method that still invokes account/access checks, such as SSH public key login. 4. Expected result: a later `filter` or `host` rule denies access. Actual result: the warning path returns `ERR_PASSWORD_EXPIRED_WARN`, the handler maps it to `PAM_SUCCESS`, and the later restrictive rules are not evaluated. Mitigation: Until a fix is available, do not place `pwd_expire_policy_warn` before restrictive entries in `ldap_access_order`. If warning behavior is still required, place it after `filter`, `host`, `rhost`, or other denial-capable rules, or use `pwd_expire_policy_reject` where expired accounts should not continue through the access chain. Proposed Fix: Record the warning state, continue evaluating the remaining ordered rules, and only return `ERR_PASSWORD_EXPIRED_WARN` after all configured access rules have passed. ```diff diff --git a/src/providers/ldap/sdap_access.c b/src/providers/ldap/sdap_access.c — a/src/providers/ldap/sdap_access.c +++ b/src/providers/ldap/sdap_access.c @@ struct sdap_access_req_ctx { @@ size_t current_rule; enum sdap_access_control_type ac_type; + bool pw_expire_warn_seen; }; @@ state->conn = conn; state->current_rule = 0; + state->pw_expire_warn_seen = false; @@ static errno_t sdap_access_check_next_rule(struct sdap_access_req_ctx *state, case LDAP_ACCESS_EMPTY: /* we are done with no errors */ return EOK; + /* all rules passed; preserve warn semantics if seen */ + return state->pw_expire_warn_seen ? ERR_PASSWORD_EXPIRED_WARN : EOK; @@ case LDAP_ACCESS_EXPIRE_POLICY_WARN: ret = perform_pwexpire_policy(state, state->domain, state->pd, state->access_ctx->type, state->access_ctx->id_ctx->opts); if (ret == ERR_PASSWORD_EXPIRED) { ret = ERR_PASSWORD_EXPIRED_WARN; + state->pw_expire_warn_seen = true; + ret = EOK; } break; ``` ------ This report was generated using AI technology. Always review AI-generated content prior to use