GHSA-53HJ-R94P-8C8F

Vulnerability from github – Published: 2026-05-06 23:37 – Updated: 2026-05-06 23:37
VLAI
Summary
Kanidm has non-constant-time comparison of OAuth2 client_secret
Details

Summary

The kanidmd OAuth2 token-exchange (/oauth2/token) and token-introspection (/oauth2/token/introspect) endpoints compare the supplied client_secret against the stored secret using Rust's PartialEq on String, which short-circuits on the first mismatching byte. This produces an observable timing discrepancy that varies with the length of the matching prefix.

Details

  • https://github.com/kanidm/kanidm/blob/master/server/lib/src/idm/oauth2.rs#L1135 — variable-time comparison in check_oauth2_token_exchange
  • https://cwe.mitre.org/data/definitions/208.html — CWE-208: Observable Timing Discrepancy

PoC

Static analysis only — no timing-recovery script was run because remote recovery of a 48-byte high-entropy secret over HTTPS is not practically demonstrable. The variable-time behaviour is established by inspection:

// server/lib/src/idm/oauth2.rs:1135  (check_oauth2_token_exchange)
if authz_secret == &secret { … } else { return Err(Oauth2Error::AuthenticationRequired); }

String: PartialEq delegates to <[u8] as PartialEq>::eq, which checks length equality then iterates byte-by-byte and returns on the first difference.

Impact

An unauthenticated network attacker who can reach the OAuth2 endpoints can submit arbitrary client_id/client_secret pairs and observe response latency. In principle the early-exit comparison leaks the position of the first mismatching byte, providing a timing oracle toward incremental recovery of a confidential client's secret. In practice the stored secret is a server-generated 48-character high-entropy string, the comparison runs inside an async tokio handler behind TLS, and network jitter is orders of magnitude larger than a single byte-compare — so remote recovery is not considered realistic with current techniques. This is a hardening issue rather than a practically exploitable vulnerability.

Affected versions

All published kanidmd_lib releases; the comparison is still variable-time on master at 1.10.0-dev

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 1.9.2"
      },
      "package": {
        "ecosystem": "crates.io",
        "name": "kanidm"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.9.3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-208"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-06T23:37:56Z",
    "nvd_published_at": null,
    "severity": "LOW"
  },
  "details": "### Summary\n\nThe kanidmd OAuth2 token-exchange (`/oauth2/token`) and token-introspection (`/oauth2/token/introspect`) endpoints compare the supplied `client_secret` against the stored secret using Rust\u0027s `PartialEq` on `String`, which short-circuits on the first mismatching byte. This produces an observable timing discrepancy that varies with the length of the matching prefix.\n\n### Details\n\n- https://github.com/kanidm/kanidm/blob/master/server/lib/src/idm/oauth2.rs#L1135 \u2014 variable-time comparison in `check_oauth2_token_exchange`\n- https://cwe.mitre.org/data/definitions/208.html \u2014 CWE-208: Observable Timing Discrepancy\n\n### PoC\n\nStatic analysis only \u2014 no timing-recovery script was run because remote recovery of a 48-byte high-entropy secret over HTTPS is not practically demonstrable. The variable-time behaviour is established by inspection:\n\n```rust\n// server/lib/src/idm/oauth2.rs:1135  (check_oauth2_token_exchange)\nif authz_secret == \u0026secret { \u2026 } else { return Err(Oauth2Error::AuthenticationRequired); }\n```\n\n`String: PartialEq` delegates to `\u003c[u8] as PartialEq\u003e::eq`, which checks length equality then iterates byte-by-byte and returns on the first difference.\n\n### Impact\n\nAn unauthenticated network attacker who can reach the OAuth2 endpoints can submit arbitrary `client_id`/`client_secret` pairs and observe response latency. In principle the early-exit comparison leaks the position of the first mismatching byte, providing a timing oracle toward incremental recovery of a confidential client\u0027s secret. In practice the stored secret is a server-generated 48-character high-entropy string, the comparison runs inside an async tokio handler behind TLS, and network jitter is orders of magnitude larger than a single byte-compare \u2014 so remote recovery is not considered realistic with current techniques. This is a hardening issue rather than a practically exploitable vulnerability.\n\n### Affected versions\n\nAll published `kanidmd_lib` releases; the comparison is still variable-time on `master` at 1.10.0-dev",
  "id": "GHSA-53hj-r94p-8c8f",
  "modified": "2026-05-06T23:37:56Z",
  "published": "2026-05-06T23:37:56Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/kanidm/kanidm/security/advisories/GHSA-53hj-r94p-8c8f"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/kanidm/kanidm"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Kanidm has non-constant-time comparison of OAuth2 client_secret"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…