Fedora Account System
Red Hat Associate
Red Hat Customer
Disclaimer: Community trackers are created by Red Hat Product Security team on a best effort basis. Package maintainers are required to ascertain if the flaw indeed affects their package, before starting the update process. AI_ONLY_REPORT package: sssd-2.12.0-1.el10 ------ Summary: OData Injection in Entra ID Lookup via Unescaped Name Input (`entra_id_lookup`): crafted lookup names containing a single quote can alter Microsoft Graph OData `$filter` semantics, broadening Entra user or group queries and causing unintended directory objects to be fetched, processed, and cached. Requirements to exploit: A deployment of `sssd-2.12.0-1.el10` using the IdP provider for Entra ID lookups, an IdP client that can read user or group data from Microsoft Graph, and a low-privileged actor who can trigger a name-based lookup through SSSD with crafted input that reaches `entra_id_lookup()`. Component affected: `sssd-2.12.0-1.el10` IdP provider code in `src/oidc_child/oidc_child_id.c`, `entra_id_lookup()`, with name-based input passed from `src/providers/idp/idp_id.c` and returned objects processed by `src/providers/idp/idp_id_eval.c`. Version affected: `sssd-2.12.0-1.el10`, when the IdP provider is used for Entra ID lookups, for example with `id_provider = idp` and `idp_type = entra_id` Patch available: no released package fix established; proposed patch included below Version fixed: unknown Upstream coordination: Not notified. CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:L - 4.4 (MEDIUM) AV:L - The vulnerable path is reached through local SSSD account lookup activity rather than a directly remote network interface. AC:L - A single quote in the lookup name is sufficient to break OData string literal context. PR:L - A low-privileged local user who can trigger lookups is sufficient in affected deployments. UI:N - No separate victim interaction is required once the lookup is initiated. S:U - The impact remains within the SSSD and configured IdP lookup security scope. C:L - Successful injection can broaden directory reads beyond the intended lookup, subject to the configured Graph client's permissions. I:N - The available evidence shows no direct write or modification primitive. A:L - Broadened queries can return larger result sets, increasing processing and cache population work. Impact: Moderate. This issue can expose additional directory objects and create denial-of-service-like load in affected deployments, but it depends on the Entra ID IdP provider being configured and on the permissions already granted to the configured Graph client. The established outcome is limited to unintended read scope expansion and increased processing; there is no evidence of code execution, direct privilege escalation, or direct data modification. Under Red Hat's severity guidance, this is better characterized as a configuration-dependent confidentiality and availability issue with Moderate impact than an Important or Critical system compromise. Embargo: no Reason: The issue is configuration-dependent, has limited demonstrated impact, and can be addressed by a small escaping change, so public coordinated release with a fix is appropriate. Acknowledgement: Aisle Research Vulnerability Details: `entra_id_lookup()` builds OData `$filter` expressions by interpolating the externally influenced lookup name into single-quoted OData string literals and only URL-encoding the finished expression afterward. URL encoding at that stage does not escape OData string literal context, so a single quote in the name can terminate the literal and append additional operators or conditions. ```c if (sep == NULL || sep == input) { filter = talloc_asprintf(rest_ctx, "startsWith(userPrincipalName,'%s@')", input); } else { filter = talloc_asprintf(rest_ctx, "mail eq '%s' or userPrincipalName eq '%s'", input, input); } ... filter_enc = url_encode_string(rest_ctx, filter); ``` Name-based lookups are forwarded into this path through the IdP provider's `--name` argument handling: ```c extra_args[c] = talloc_asprintf(extra_args, "--name=%s", search_name != NULL ? search_name : filter_value); ``` The response handling path then iterates over every returned object and stores it without re-applying the original query intent after the Graph response is received: ```c json_array_foreach(data, index, obj) { ret = store_func(idp_id_ctx, obj, name); if (ret != EOK) { tmp = json_dumps(obj, 0); DEBUG(SSSDBG_OP_FAILURE, "Failed to store JSON %s [%s].\n", type, tmp); free(tmp); } } ``` As a result, a crafted lookup name can broaden the filter used for Entra user or group searches, leading to over-fetching of directory data within the configured application's existing read permissions, additional processing, and cache pollution. The exact breadth of returned objects depends on the Graph permissions granted to the configured IdP client and on the specific lookup branch reached, but the available code paths support confidentiality and availability impact. The available evidence does not support direct integrity impact. Steps to reproduce: 1. Configure a test domain with `id_provider = idp`, `idp_type = entra_id`, and valid `idp_client_id`, `idp_client_secret`, `idp_token_endpoint`, and `idp_id_scope` values, using an application that can read users or groups from Microsoft Graph. 2. Trigger a name-based user or group lookup via NSS or another SSSD account lookup path that causes SSSD to pass the requested name into the Entra lookup path. 3. Use a crafted lookup name containing a single quote, for example `a') or userPrincipalName ne ('`. Depending on the exact lookup flow, a UPN-style variant of the payload may be needed when the name is propagated internally. 4. Enable verbose SSSD or libcurl debugging and inspect the outbound Graph request URL. 5. Observe that the generated `$filter` no longer represents only the intended exact or prefix lookup. For example, if the `startsWith(userPrincipalName,'%s@')` branch receives the payload above, the resulting filter becomes logically equivalent to `startsWith(userPrincipalName,'a') or userPrincipalName ne ('@')`. 6. Confirm that the returned array is accepted by the IdP evaluation path and that each returned object is processed and stored, demonstrating broadened read scope and added processing work. Mitigation: Until a fix is available, restrict untrusted users from triggering IdP-backed name lookups in deployments using the Entra ID provider path, and keep the configured Graph application permissions as narrow as possible. If operationally acceptable, reject or sanitize lookup names containing single quotes before they reach the Entra lookup path. Increased SSSD or libcurl debug logging can help identify unexpectedly broadened `$filter` requests during monitoring. Proposed Fix: Escape single quotes for OData string literal context before interpolating `input` or `short_name` into the filter, then continue URL-encoding the completed expression. ```diff diff --git a/src/oidc_child/oidc_child_id.c b/src/oidc_child/oidc_child_id.c — a/src/oidc_child/oidc_child_id.c +++ b/src/oidc_child/oidc_child_id.c @@ #include "oidc_child/oidc_child_util.h" #include "util/util.h" +static char *odata_escape_quotes(TALLOC_CTX *mem_ctx, const char *in) +{ + const char *p; + char *out; + + if (in == NULL) return NULL; + out = talloc_strdup(mem_ctx, ""); + if (out == NULL) return NULL; + + for (p = in; *p != '\0'; p++) { + out = (*p == '\'') ? talloc_asprintf_append(out, "''") + : talloc_asprintf_append(out, "%c", *p); + if (out == NULL) return NULL; + } + return out; +} + errno_t entra_id_lookup(...) { @@ char *short_name; + char *short_name; + char *escaped_input; + char *escaped_short_name; @@ + escaped_input = odata_escape_quotes(rest_ctx, input); + if (escaped_input == NULL) { + ret = ENOMEM; + goto done; + } @@ filter = talloc_asprintf(rest_ctx, "startsWith(userPrincipalName,'%s@')", input); + filter = talloc_asprintf(rest_ctx, "startsWith(userPrincipalName,'%s@')", escaped_input); @@ input, input); + escaped_input, escaped_input); @@ filter = talloc_asprintf(rest_ctx, "displayName eq '%s'", input); + filter = talloc_asprintf(rest_ctx, "displayName eq '%s'", escaped_input); @@ filter = talloc_asprintf(rest_ctx, "displayName eq '%s'", input); + filter = talloc_asprintf(rest_ctx, "displayName eq '%s'", escaped_input); } else { + escaped_short_name = odata_escape_quotes(rest_ctx, short_name); + if (escaped_short_name == NULL) { + ret = ENOMEM; + goto done; + } filter = talloc_asprintf(rest_ctx, "displayName eq '%s' or displayName eq '%s'", input, short_name); + escaped_input, escaped_short_name); } ``` ------ This report was generated using AI technology. Always review AI-generated content prior to use