{"id":"GHSA-wwv5-g3v4-889x","summary":"Tornado: Incomplete fix for CVE-2026-35536: cookie attribute injection re-opened via the legacy case-insensitive `**kwargs` path in `set_cookie`","details":"## Summary\nThe CVE-2026-35536 fix added a validation loop that rejects `[\\x00-\\x20\\x3b\\x7f]`, but only for the\nhardcoded **lowercase** keys `name`/`domain`/`path`/`samesite`. The still-live deprecated `**kwargs` path\nwrites attacker-supplied attribute values straight into the `Morsel` with no validation, and because\n`Morsel.__setitem__` is case-insensitive, a capitalized kwarg (`Domain=`, `Path=`, `SameSite=`, `Max-Age=`)\nroutes to the same reserved attribute while bypassing the loop — re-opening `;`-delimited attribute injection.\n\n```python\nself.set_cookie(\"sid\", \"abc\", Domain=\"evil.com; Secure; SameSite=None\")\n#  -\u003e Set-Cookie: sid=abc; Domain=evil.com; Secure; SameSite=None; Path=/\n# Sanity (the canonical lowercase named arg IS blocked):\nself.set_cookie(\"sid\", \"abc\", domain=\"evil.com; Secure\")   # -\u003e http.cookies.CookieError\n```\n\nThe patch's regression test (`SetCookieForbiddenCharHandler`) only exercises the four named params, never the\n`**kwargs` path, so the gap is not regression-covered.\n\n## Affected code\n- `tornado/web.py` → `RequestHandler.set_cookie`: the validation loop covers only the lowercase named args;\n  the trailing `if kwargs:` loop does `morsel[k] = v` with no character validation.\n\n## Steps to reproduce\n`GET /upper` (uses `Domain=` kwarg) emits `Set-Cookie: c_upper=v; Domain=evil.com; Secure; SameSite=None; Path=/`; `GET /lower` (uses lowercase\n`domain=`) returns a `CookieError`.\n\n## Impact\nInjection of independent cookie attributes (force/drop `Secure`/`HttpOnly`/`SameSite`, rebind `Domain`/`Path`)\n— the same impact CVE-2026-35536 closed, via the sibling path the patch missed. Conditional on the app using\na capitalized/legacy keyword.\n\n## Suggested remediation\nApply the same `[\\x00-\\x20\\x3b\\x7f]` validation to every entry in the `**kwargs` loop (after normalizing the\nkey case), or remove the deprecated kwargs path; add a regression test for capitalized kwargs.\n\n## Credit\nReported as part of an incomplete-patch measurement study (responsible disclosure).","aliases":["CVE-2026-91991"],"modified":"2026-09-16T03:56:06.997644655Z","published":"2026-09-01T20:17:23Z","database_specific":{"nvd_published_at":null,"cwe_ids":["CWE-74"],"severity":"LOW","github_reviewed":true,"github_reviewed_at":"2026-09-01T20:17:23Z"},"references":[{"type":"WEB","url":"https://github.com/tornadoweb/tornado/security/advisories/GHSA-wwv5-g3v4-889x"},{"type":"WEB","url":"https://github.com/tornadoweb/tornado/pull/3704"},{"type":"WEB","url":"https://github.com/tornadoweb/tornado/pull/3706"},{"type":"WEB","url":"https://github.com/tornadoweb/tornado/commit/6ef836e43e1278530041376adb32504daa977b91"},{"type":"WEB","url":"https://github.com/tornadoweb/tornado/commit/da284767eae8e1f0484f123b8c3225f6465b09c7"},{"type":"PACKAGE","url":"https://github.com/tornadoweb/tornado"},{"type":"WEB","url":"https://github.com/tornadoweb/tornado/releases/tag/v6.5.8"}],"affected":[{"package":{"name":"tornado","ecosystem":"PyPI","purl":"pkg:pypi/tornado"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"6.5.5"},{"fixed":"6.5.8"}]}],"versions":["6.5.5","6.5.6","6.5.7"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-wwv5-g3v4-889x/GHSA-wwv5-g3v4-889x.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N"}]}