{"id":"GHSA-w3jg-pjxf-73p4","revision":null,"records":[{"revision":"88b2f478192b4efba5586f7bb1bfc254","observedAt":"2026-10-23T15:48:27.903+00:00","statement":{"source":"cve-program","id":"CVE-2026-102824","aliases":[],"title":"Russh: Missing X25519 zero-point validation in hybrid ML-KEM key exchange","description":"Russh is a Rust SSH client and server library. Prior to 0.63.0, the hybrid ML-KEM 768 and X25519 implementation in russh/src/kex/hybrid_mlkem.rs accepts an all-zero 32-byte peer X25519 public key in both server_dh and compute_shared_secret, forcing the X25519 contribution to the combined shared secret to zero. A malicious SSH peer can therefore make the combined secret depend only on ML-KEM, defeating the hybrid exchange's intended fallback protection if ML-KEM is later weakened. This issue is fixed in version 0.63.0.","state":0,"publishedAt":"2026-09-29T18:27:41.65+00:00","modifiedAt":"2026-09-30T20:24:58.381+00:00","affected":[{"ecosystem":"Vendor product","name":"russh","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"Eugeny\",\n                    \"product\": \"russh\",\n                    \"versions\": [\n                        {\n                            \"version\": \"< 0.63.0\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N","score":4.3,"severity":"MEDIUM"}],"references":["https://github.com/Eugeny/russh/security/advisories/GHSA-w3jg-pjxf-73p4","https://github.com/Eugeny/russh/commit/8da8967f196472576b1565d518a0ed60fce60f0c","https://github.com/Eugeny/russh/releases/tag/v0.63.0"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-102824","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-102824","identifiers":["CVE-2026-102824"]},"retained":false,"historicalAffected":[]},{"revision":"948cb3bafeff4a3ba1e4b4e9723efd34","observedAt":"2026-11-11T05:29:30.077+00:00","statement":{"source":"euvd","id":"EUVD-2026-89207","aliases":["CVE-2026-102824","GHSA-w3jg-pjxf-73p4"],"title":"Russh is a Rust SSH client and server library. Prior to 0.63.0, the hybrid ML-KEM 768 and X25519 implementation in russh/src/kex/hybrid_mlkem.rs accepts an all-zero 32-byte peer X25519 public key in both server_dh and compute_shared_secret,…","description":"Russh is a Rust SSH client and server library. Prior to 0.63.0, the hybrid ML-KEM 768 and X25519 implementation in russh/src/kex/hybrid_mlkem.rs accepts an all-zero 32-byte peer X25519 public key in both server_dh and compute_shared_secret, forcing the X25519 contribution to the combined shared secret to zero. A malicious SSH peer can therefore make the combined secret depend only on ML-KEM, defeating the hybrid exchange's intended fallback protection if ML-KEM is later weakened. This issue is fixed in version 0.63.0.","state":0,"publishedAt":null,"modifiedAt":null,"affected":[],"metrics":[{"system":"EUVD CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N","score":4.3,"severity":null}],"references":["https://github.com/Eugeny/russh/security/advisories/GHSA-w3jg-pjxf-73p4","https://github.com/Eugeny/russh/commit/8da8967f196472576b1565d518a0ed60fce60f0c","https://github.com/Eugeny/russh/releases/tag/v0.63.0"],"sourceUrl":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2026-89207","license":"ENISA EUVD · source terms and attribution apply","enrichment":{"sourceStatus":null,"weaknesses":[],"productCriteria":[],"sourceDates":{"datePublished":"Sep 29, 2026, 6:27:41 PM","dateUpdated":"Sep 30, 2026, 8:24:58 PM"},"relatedAdvisories":[{"id":"RHSA-2026:74586","title":"Red Hat Security Advisory: Red Hat Hardened Images RPMs Security Update","vulnerabilityIds":["CVE-2026-102821","CVE-2026-102823","CVE-2026-102824","CVE-2026-102825"]}],"note":"EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON."},"key":"euvd:EUVD-2026-89207","identifiers":["EUVD-2026-89207","CVE-2026-102824","GHSA-w3jg-pjxf-73p4"]},"retained":false,"historicalAffected":[]},{"revision":"e3be9f59ecdf417e87d200d0aa0dca37","observedAt":"2026-10-25T04:17:26.915+00:00","statement":{"source":"nvd","id":"CVE-2026-102824","aliases":[],"title":"Russh is a Rust SSH client and server library. Prior to 0.63.0, the hybrid ML-KEM 768 and X25519 implementation in russh/src/kex/hybrid_mlkem.rs accepts an all-zero 32-byte peer X25519 public key in both server_dh and compute_shared_secret,…","description":"Russh is a Rust SSH client and server library. Prior to 0.63.0, the hybrid ML-KEM 768 and X25519 implementation in russh/src/kex/hybrid_mlkem.rs accepts an all-zero 32-byte peer X25519 public key in both server_dh and compute_shared_secret, forcing the X25519 contribution to the combined shared secret to zero. A malicious SSH peer can therefore make the combined secret depend only on ML-KEM, defeating the hybrid exchange's intended fallback protection if ML-KEM is later weakened. This issue is fixed in version 0.63.0.","state":0,"publishedAt":"2026-09-29T19:17:24.543+00:00","modifiedAt":"2026-09-30T21:17:05.92+00:00","affected":[],"metrics":[{"system":"CVSS 3.1 · security-advisories@github.com · Secondary","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N","score":4.3,"severity":"MEDIUM"}],"references":["https://github.com/Eugeny/russh/commit/8da8967f196472576b1565d518a0ed60fce60f0c","https://github.com/Eugeny/russh/releases/tag/v0.63.0","https://github.com/Eugeny/russh/security/advisories/GHSA-w3jg-pjxf-73p4"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-102824","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Deferred","weaknesses":["CWE-327"],"productCriteria":[],"sourceDates":{"published":"2026-09-29T19:17:24.543","lastModified":"2026-09-30T21:17:05.920"},"relatedAdvisories":[],"note":"NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON."},"key":"nvd:CVE-2026-102824","identifiers":["CVE-2026-102824"]},"retained":false,"historicalAffected":[]},{"revision":"94290de5f360450d9e511892a8e4a3ce","observedAt":"2026-10-24T13:19:54.901+00:00","statement":{"source":"osv","id":"GHSA-w3jg-pjxf-73p4","aliases":["CVE-2026-102824"],"title":"Russh: Missing X25519 zero-point validation in hybrid ML-KEM key exchange","description":"## Vulnerability\n\nThe hybrid ML-KEM 768 + X25519 key exchange implementation in `russh/src/kex/hybrid_mlkem.rs` does not validate that the remote peer's X25519 public key is not the zero point (all-zero 32-byte value). This allows a remote peer to force the X25519 contribution to the combined shared secret to zero, reducing the hybrid KEX to a single-algorithm exchange.\n\nAffected code at HEAD (v0.62.4, commit 0089c89):\n\n**Server-side** (`server_dh`, lines 92-93):\n```rust\nlet mut c_pk1 = MontgomeryPoint([0; 32]);\nc_pk1.0.copy_from_slice(c_pk1_bytes);\n// No zero-point check - proceeds to: let k_cl = s_secret * c_pk1;\n```\n\n**Client-side** (`compute_shared_secret`, lines 154-155):\n```rust\nlet mut s_pk1 = MontgomeryPoint([0; 32]);\ns_pk1.0.copy_from_slice(s_pk1_bytes);\n// No zero-point check - proceeds to: let k_cl = x25519_secret * s_pk1;\n```\n\n## Root Cause\n\nCommit a7fc1eb (2026-07-22, \"fix mpint encoding and validate curve25519 keys\") added zero-point validation to the standalone Curve25519 KEX in `russh/src/kex/curve25519.rs` at two locations:\n- `server_dh` line 77: `if client_pubkey.0 == [0u8; 32] { return Err(crate::Error::Kex); }`\n- `compute_shared_secret` line 122: `if remote_pubkey.0 == [0u8; 32] { return Err(crate::Error::Kex); }`\n\nThe same X25519 scalar multiplication pattern appears in `hybrid_mlkem.rs`, but the fix was not applied there.\n\n## Proof of Concept\n\nA malicious SSH client negotiating `mlkem768x25519-sha256` can send a KEX_HYBRID_INIT message containing a valid ML-KEM 768 encapsulation key followed by 32 zero bytes as the X25519 component.\n\nWhen the server computes `k_cl = s_secret * c_pk1`, the result is the zero point regardless of the server's secret scalar. The combined shared secret `K = SHA-256(k_pq || k_cl)` then depends only on the ML-KEM component. The same attack works in reverse against a client connecting to a malicious server.\n\nThe zero X25519 public key passes all existing validation (the length check on line 78 succeeds since 32 bytes is correct). No panic or crash occurs - the exchange completes successfully with a weakened shared secret.\n\n## Impact\n\nThe purpose of hybrid key exchange is defense-in-depth: if either the classical algorithm (X25519) or the post-quantum algorithm (ML-KEM 768) is broken, the combined secret remains secure. By injecting a zero X25519 public key, an attacker eliminates the classical contribution entirely, reducing security to depend solely on ML-KEM.\n\nThis matters in two scenarios:\n1. If ML-KEM 768 is later found to have a weakness (the explicit threat model hybrid KEX is designed to mitigate), sessions where the X25519 component was zeroed out lose their fallback protection.\n2. An active network attacker who can intercept KEX could downgrade the hybrid exchange to effectively single-algorithm security without either peer detecting it.\n\nThe severity is MEDIUM rather than HIGH because the ML-KEM component still provides strong security today, and an active attacker who can modify KEX messages can already perform other attacks unless strict KEX is negotiated.\n\n## Suggested Fix\n\nAdd zero-point checks in `hybrid_mlkem.rs` matching the ones in `curve25519.rs`:\n\n```rust\n// In server_dh, after line 93:\nlet mut c_pk1 = MontgomeryPoint([0; 32]);\nc_pk1.0.copy_from_slice(c_pk1_bytes);\nif c_pk1.0 == [0u8; 32] {\n    return Err(Error::Kex);\n}\n\n// In compute_shared_secret, after line 155:\nlet mut s_pk1 = MontgomeryPoint([0; 32]);\ns_pk1.0.copy_from_slice(s_pk1_bytes);\nif s_pk1.0 == [0u8; 32] {\n    return Err(Error::Kex);\n}\n```\n\nIdeally, also validate against the other small-subgroup points on Curve25519 (there are a small number of low-order points that also yield a zero shared secret), matching the comprehensive validation OpenSSH performs.\n\n### AI tooling\n\nAI assistancewas used for the code audit and for drafting this report. The finding was manually verified against the project's source at the location cited above before reporting it, and the severity and impact assessment are my own.","state":0,"publishedAt":"2026-09-30T23:26:48+00:00","modifiedAt":"2026-09-30T23:30:05.9677285+00:00","affected":[{"ecosystem":"crates.io","name":"russh","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"0.63.0"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"MODERATE"}],"references":["https://github.com/Eugeny/russh/security/advisories/GHSA-w3jg-pjxf-73p4","https://nvd.nist.gov/vuln/detail/CVE-2026-102824","https://github.com/Eugeny/russh/commit/8da8967f196472576b1565d518a0ed60fce60f0c","https://github.com/Eugeny/russh","https://github.com/Eugeny/russh/releases/tag/v0.63.0"],"sourceUrl":"https://github.com/advisories/GHSA-w3jg-pjxf-73p4","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-w3jg-pjxf-73p4","identifiers":["GHSA-w3jg-pjxf-73p4","CVE-2026-102824"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-11-12T01:56:45.7331196+00:00","complete":true,"records":1779,"lastAttemptAt":"2026-11-12T01:56:45.7331196+00:00","lastSuccessAt":"2026-11-12T01:56:45.8811913+00:00","error":null,"lastVerifiedAt":"2026-11-12T01:56:45.881192+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-64752486c567b55b31fb0d7225f5222bcd0019564ac0ac5f0649e242c7b19299-3c214c6a32247ab2d1d3b316a538911dff65582c6da8e67b65c52a6524920b32","complete":true,"records":424535,"lastAttemptAt":"2026-11-12T01:56:45.0952496+00:00","lastSuccessAt":"2026-11-12T01:56:45.0811877+00:00","error":null,"lastVerifiedAt":"2026-11-12T01:56:45.0952496+00:00","snapshotPublishedAt":"2026-11-12T01:20:15+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-bcc8684b65174110b8807dc4d8b03322","complete":false,"records":13760,"lastAttemptAt":"2026-11-12T02:06:55.9169023+00:00","lastSuccessAt":"2026-11-12T02:06:55.913+00:00","error":"671 identifier lookups failed; latest failures include AccessDenied (HTTP 403). Other identifiers were checked independently.","lastVerifiedAt":"2026-11-12T02:06:55.913+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":12946,"expectedIdentifiers":13813,"failedIdentifiers":671},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-11-12T01:56:45.7331196+00:00","complete":true,"records":1097,"lastAttemptAt":"2026-11-12T01:56:45.7331196+00:00","lastSuccessAt":"2026-11-12T01:56:45.8819472+00:00","error":null,"lastVerifiedAt":"2026-11-12T01:56:45.8819473+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-94eaf194888848508d3f9f54dac1b6ce","complete":true,"records":13760,"lastAttemptAt":"2026-11-12T01:57:11.1206168+00:00","lastSuccessAt":"2026-11-12T01:57:11.117+00:00","error":null,"lastVerifiedAt":"2026-11-12T01:57:11.117+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13813,"expectedIdentifiers":13813,"failedIdentifiers":0},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-3562b6c78fd1bd0f0b55daf66a0926ae24f65e462df7f6d928caad458fc696bf","complete":true,"records":9947,"lastAttemptAt":"2026-11-12T01:56:45.5143298+00:00","lastSuccessAt":"2026-11-11T04:27:43.6877707+00:00","error":null,"lastVerifiedAt":"2026-11-12T01:56:45.5143298+00:00","snapshotPublishedAt":"2026-11-11T03:32:46+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-456002455e0ab94bb92c95476d4e6d175c104cd594749982afd96c893728ecbc","complete":true,"records":2010,"lastAttemptAt":"2026-11-12T01:56:45.2815945+00:00","lastSuccessAt":"2026-11-11T02:36:58.1276999+00:00","error":null,"lastVerifiedAt":"2026-11-12T01:56:45.2815945+00:00","snapshotPublishedAt":"2026-11-11T01:17:51+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-baa8362b4daf9e5f39d5563775a55935e7b442c0735d33274a921579ce4a0d66","complete":true,"records":3093,"lastAttemptAt":"2026-11-12T01:56:45.6894904+00:00","lastSuccessAt":"2026-11-11T13:00:02.5446881+00:00","error":null,"lastVerifiedAt":"2026-11-12T01:56:45.6894904+00:00","snapshotPublishedAt":"2026-11-11T12:47:47+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-685fe7fb2b690656c910a1ea42a9d294e8353311ac8ecfa691a9b0ff1b328804","complete":true,"records":231695,"lastAttemptAt":"2026-11-12T01:56:45.3885824+00:00","lastSuccessAt":"2026-11-11T21:58:47.7265713+00:00","error":null,"lastVerifiedAt":"2026-11-12T01:56:45.3885824+00:00","snapshotPublishedAt":"2026-11-11T21:48:35+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}