{"id":"GHSA-53hj-r94p-8c8f","aliases":[],"status":"published","title":"Kanidm has non-constant-time comparison of OAuth2 client_secret","description":"### 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'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.\n\n### Details\n\n- https://github.com/kanidm/kanidm/blob/master/server/lib/src/idm/oauth2.rs#L1135 — variable-time comparison in `check_oauth2_token_exchange`\n- https://cwe.mitre.org/data/definitions/208.html — CWE-208: Observable Timing Discrepancy\n\n### PoC\n\nStatic 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:\n\n```rust\n// server/lib/src/idm/oauth2.rs:1135  (check_oauth2_token_exchange)\nif authz_secret == \u0026secret { … } 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'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.\n\n### Affected versions\n\nAll published `kanidmd_lib` releases; the comparison is still variable-time on `master` at 1.10.0-dev","severity":{"version":"3.1","score":3.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N","level":"low","from":"ghsa"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-208"],"published":"2026-05-06T23:37:56Z","modified":"2026-05-06T23:47:49Z","fixes":[{"ecosystem":"crates.io","package":"kanidm","fixed_in":["1.9.3"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"crates.io","package":"kanidm","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"1.9.2"},{"fixed":"1.9.3"}]}]},{"source":"osv","ecosystem":"crates.io","package":"kanidm","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.9.3"}]}]}],"references":[{"url":"https://github.com/kanidm/kanidm/security/advisories/GHSA-53hj-r94p-8c8f","tags":["web"]},{"url":"https://github.com/advisories/GHSA-53hj-r94p-8c8f"},{"url":"https://github.com/kanidm/kanidm","tags":["package"]}],"sources":{"ghsa":[{"id":"GHSA-53hj-r94p-8c8f","title":"Kanidm has non-constant-time comparison of OAuth2 client_secret","description":"### 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'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.\n\n### Details\n\n- https://github.com/kanidm/kanidm/blob/master/server/lib/src/idm/oauth2.rs#L1135 — variable-time comparison in `check_oauth2_token_exchange`\n- https://cwe.mitre.org/data/definitions/208.html — CWE-208: Observable Timing Discrepancy\n\n### PoC\n\nStatic 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:\n\n```rust\n// server/lib/src/idm/oauth2.rs:1135  (check_oauth2_token_exchange)\nif authz_secret == \u0026secret { … } 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'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.\n\n### Affected versions\n\nAll published `kanidmd_lib` releases; the comparison is still variable-time on `master` at 1.10.0-dev","status":"published","published":"2026-05-06T23:37:56Z","modified":"2026-05-06T23:37:58Z","cvss":[{"version":"3.1","score":3.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N","level":"low"}],"cwes":["CWE-208"],"updated_at":"2026-11-02T14:40:02.883746Z"}],"osv":[{"id":"GHSA-53hj-r94p-8c8f","title":"Kanidm has non-constant-time comparison of OAuth2 client_secret","description":"### 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'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.\n\n### Details\n\n- https://github.com/kanidm/kanidm/blob/master/server/lib/src/idm/oauth2.rs#L1135 — variable-time comparison in `check_oauth2_token_exchange`\n- https://cwe.mitre.org/data/definitions/208.html — CWE-208: Observable Timing Discrepancy\n\n### PoC\n\nStatic 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:\n\n```rust\n// server/lib/src/idm/oauth2.rs:1135  (check_oauth2_token_exchange)\nif authz_secret == \u0026secret { … } 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'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.\n\n### Affected versions\n\nAll published `kanidmd_lib` releases; the comparison is still variable-time on `master` at 1.10.0-dev","status":"published","published":"2026-05-06T23:37:56Z","modified":"2026-05-06T23:47:49.387926Z","cvss":[{"version":"3.1","score":3.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N","level":"low"}],"cwes":["CWE-208"],"updated_at":"2026-11-02T15:46:37.864461Z"}]}}
