{"id":"CVE-2026-91991","aliases":["GHSA-wwv5-g3v4-889x"],"url":"https://o3.security/vulnerability/CVE-2026-91991","summary":"Tornado before 6.5.8 Cookie Attribute Injection via Capitalized kwargs","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#  -> 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\")   # -> 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).","published":"2026-09-15T15:18:32.408Z","modified":"2026-10-13T17:27:09.859315729Z","cvss":null,"epss":null,"cisaKev":null,"exploitsKnown":null,"affectedPackages":[{"ecosystem":"PyPI","name":"tornado","fixedVersion":"6.5.8"}],"fix":{"url":"https://github.com/tornadoweb/tornado/pull/3704","label":"tornadoweb/tornado#3704"},"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/91xxx/CVE-2026-91991.json"},{"type":"ADVISORY","url":"https://github.com/tornadoweb/tornado/security/advisories/GHSA-wwv5-g3v4-889x"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-91991"},{"type":"ADVISORY","url":"https://www.vulncheck.com/advisories/tornado-before-6.5.8-cookie-attribute-injection-via-capitalized-kwargs"},{"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"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-10-13T17:27:09.859315729Z"}}