{"id":"GHSA-63mc-hw7g-86rr","summary":"Phoenix: Presence keys colliding with `Object.prototype` members break existence checks","details":"### Summary\n\nThe Phoenix JavaScript presence client (`assets/js/phoenix/presence.js`) tests whether a presence already exists using a bare truthiness check (`state[key]`) rather than an own-property check. Because applications commonly track presences under a client-supplied username or id, the presence key can be attacker-controlled. A user who joins a channel and picks a key that names an `Object.prototype` member (`__proto__`, `constructor`, `toString`, `hasOwnProperty`, and similar) makes the lookup return the inherited `Object.prototype` object instead of `undefined`, which is truthy. The code then reads `.metas.map(...)` off it and throws an uncaught `TypeError`, breaking presence sync for every viewer of that channel topic. Any authenticated channel participant can trigger it.\n\n### Details\n\nThe victim is any browser subscribed to a presence channel. When it receives the server's `presence_state` message, it invokes `Presence.syncState`, which iterates the incoming presences and checks whether each one already exists locally via `let currentPresence = state[key]`. `state` is a plain object inheriting from `Object.prototype`. For an ordinary key like `alice`, `state[\"alice\"]` is `undefined` (falsy) and the safe path runs. For the key `__proto__` (or `constructor`, `toString`, etc.), `state[\"__proto__\"]` does not resolve to a tracked presence but to JavaScript's built-in `Object.prototype`, which is truthy. The `if(currentPresence)` guard passes, and the code evaluates `currentPresence.metas.map(m =\u003e m.phx_ref)`. Since `Object.prototype.metas` is `undefined`, calling `.map` on it throws a `TypeError`.\n\nPhoenix wraps no try/catch around channel binding callbacks, so the `TypeError` propagates out of the message handler: `this.state` is never updated and `onSync()` never fires. The malicious key is tracked server-side, so it is re-pushed on every presence update and keeps re-throwing, leaving presence permanently broken until the attacker leaves. `Presence.syncDiff` uses the same unsafe `state[key]` existence-check pattern, so presence diffs fail identically.\n\nTwo scoping points matter. The impact is per channel topic, not global: presence state is per-topic on the server and per-`Presence`-instance in the browser, so only viewers of the topic carrying the malicious key are affected. The bug is a read-time confusion of the prototype object, not prototype pollution: the crash occurs on the `state[\"__proto__\"]` read in `syncState`, before any `state[key] = ...` write is reached, so `Object.prototype` is never mutated and nothing leaks across channels. The fix builds the state and accumulator objects with `Object.create(null)` (or a `Map`) and gates existence checks with `Object.prototype.hasOwnProperty.call(obj, key)`.\n\nIf an application does not pass a client-controlled key to `Presence.track`, it is **not** affected.\n\n### PoC\n\n1. Connect to an application that uses `Phoenix.Presence` and tracks presences under a client-chosen key (e.g. a username).\n2. Join a presence channel choosing the key `__proto__` (or `constructor`, `toString`, `hasOwnProperty`).\n3. The server tracks the presence and pushes `presence_state` / `presence_diff` to every subscriber of that topic.\n4. Each viewer's `Presence.syncState` (or `syncDiff`) reads `state[\"__proto__\"]`, gets the truthy `Object.prototype`, and throws an uncaught `TypeError`.\n5. Presence sync stays broken for all viewers of the topic until the attacker leaves the channel.\n\n### Impact\n\nAn attacker with ordinary channel access can cause a persistent, stored client-side denial of service against every browser viewing a presence channel topic, freezing presence updates for all of them until the attacker disconnects. Any application driving the Phoenix JavaScript presence client with user-influenced presence keys is affected.","aliases":["CVE-2026-56812","EEF-CVE-2026-56812"],"modified":"2026-09-03T20:45:05.072481574Z","published":"2026-09-03T20:30:33Z","database_specific":{"severity":"MODERATE","github_reviewed":true,"github_reviewed_at":"2026-09-03T20:30:33Z","nvd_published_at":"2026-07-07T16:16:40Z","cwe_ids":["CWE-754"]},"references":[{"type":"WEB","url":"https://github.com/phoenixframework/phoenix/security/advisories/GHSA-63mc-hw7g-86rr"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-56812"},{"type":"WEB","url":"https://github.com/phoenixframework/phoenix/commit/7f7b971c1ea0994e3fbd1c11ddb05e780bd38ad8"},{"type":"WEB","url":"https://github.com/phoenixframework/phoenix/commit/89a1c4be161e436241e12b2378a719904b9bd96f"},{"type":"WEB","url":"https://github.com/phoenixframework/phoenix/commit/b90b22521465ece00eb5a19d5aa2b9465b209c85"},{"type":"WEB","url":"https://github.com/phoenixframework/phoenix/commit/beffc4da1e787e572121f68902c63daf4fe7d9c2"},{"type":"WEB","url":"https://cna.erlef.org/cves/CVE-2026-56812.html"},{"type":"PACKAGE","url":"https://github.com/phoenixframework/phoenix"},{"type":"WEB","url":"https://osv.dev/vulnerability/EEF-CVE-2026-56812"}],"affected":[{"package":{"name":"phoenix","ecosystem":"Hex","purl":"pkg:hex/phoenix"},"ranges":[{"type":"SEMVER","events":[{"introduced":"1.2.0-rc.0"},{"fixed":"1.5.15"}]}],"versions":["1.2.0","1.2.0-rc.0","1.2.0-rc.1","1.2.1","1.2.2","1.2.3","1.2.4","1.2.5","1.3.0","1.3.0-rc.0","1.3.0-rc.1","1.3.0-rc.2","1.3.0-rc.3","1.3.1","1.3.2","1.3.3","1.3.4","1.3.5","1.4.0","1.4.0-rc.0","1.4.0-rc.1","1.4.0-rc.2","1.4.0-rc.3","1.4.1","1.4.10","1.4.11","1.4.12","1.4.13","1.4.14","1.4.15","1.4.16","1.4.17","1.4.18","1.4.2","1.4.3","1.4.4","1.4.5","1.4.6","1.4.7","1.4.8","1.4.9","1.5.0","1.5.0-rc.0","1.5.1","1.5.10","1.5.11","1.5.12","1.5.13","1.5.14","1.5.2","1.5.3","1.5.4","1.5.5","1.5.6","1.5.7","1.5.8","1.5.9"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-63mc-hw7g-86rr/GHSA-63mc-hw7g-86rr.json"}},{"package":{"name":"phoenix","ecosystem":"Hex","purl":"pkg:hex/phoenix"},"ranges":[{"type":"SEMVER","events":[{"introduced":"1.6.0-rc.0"},{"fixed":"1.6.17"}]}],"versions":["1.6.0","1.6.0-rc.0","1.6.0-rc.1","1.6.1","1.6.10","1.6.11","1.6.12","1.6.13","1.6.14","1.6.15","1.6.16","1.6.2","1.6.3","1.6.4","1.6.5","1.6.6","1.6.7","1.6.8","1.6.9"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-63mc-hw7g-86rr/GHSA-63mc-hw7g-86rr.json"}},{"package":{"name":"phoenix","ecosystem":"Hex","purl":"pkg:hex/phoenix"},"ranges":[{"type":"SEMVER","events":[{"introduced":"1.7.0-rc.0"},{"fixed":"1.7.24"}]}],"versions":["1.7.0","1.7.0-rc.0","1.7.0-rc.1","1.7.0-rc.2","1.7.0-rc.3","1.7.1","1.7.10","1.7.11","1.7.12","1.7.13","1.7.14","1.7.15","1.7.16","1.7.17","1.7.18","1.7.19","1.7.2","1.7.20","1.7.21","1.7.22","1.7.23","1.7.3","1.7.4","1.7.5","1.7.6","1.7.7","1.7.8","1.7.9"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-63mc-hw7g-86rr/GHSA-63mc-hw7g-86rr.json"}},{"package":{"name":"phoenix","ecosystem":"Hex","purl":"pkg:hex/phoenix"},"ranges":[{"type":"SEMVER","events":[{"introduced":"1.8.0-rc.0"},{"fixed":"1.8.9"}]}],"versions":["1.8.0","1.8.0-rc.0","1.8.0-rc.1","1.8.0-rc.2","1.8.0-rc.3","1.8.0-rc.4","1.8.1","1.8.2","1.8.3","1.8.4","1.8.5","1.8.6","1.8.7","1.8.8"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-63mc-hw7g-86rr/GHSA-63mc-hw7g-86rr.json"}},{"package":{"name":"phoenix","ecosystem":"npm","purl":"pkg:npm/phoenix"},"ranges":[{"type":"SEMVER","events":[{"introduced":"1.2.0-rc.0"},{"fixed":"1.5.15"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-63mc-hw7g-86rr/GHSA-63mc-hw7g-86rr.json"}},{"package":{"name":"phoenix","ecosystem":"npm","purl":"pkg:npm/phoenix"},"ranges":[{"type":"SEMVER","events":[{"introduced":"1.6.0-rc.0"},{"fixed":"1.6.17"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-63mc-hw7g-86rr/GHSA-63mc-hw7g-86rr.json"}},{"package":{"name":"phoenix","ecosystem":"npm","purl":"pkg:npm/phoenix"},"ranges":[{"type":"SEMVER","events":[{"introduced":"1.7.0-rc.0"},{"fixed":"1.7.24"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-63mc-hw7g-86rr/GHSA-63mc-hw7g-86rr.json"}},{"package":{"name":"phoenix","ecosystem":"npm","purl":"pkg:npm/phoenix"},"ranges":[{"type":"SEMVER","events":[{"introduced":"1.8.0-rc.0"},{"fixed":"1.8.9"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-63mc-hw7g-86rr/GHSA-63mc-hw7g-86rr.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N"}]}