{"id":"GHSA-97jj-33gv-5xf9","summary":"league/commonmark: DisallowedRawHtml bypassed when a disallowed tag name ends the raw-HTML literal","details":"\ufeff## Summary\n\nThe `DisallowedRawHtml` extension does not escape a disallowed tag when the tag name is the last thing in the raw HTML. A Markdown line containing just `\u003cscript` is emitted unchanged, and the next block can supply its attributes. With the shipped GFM defaults this allows stored XSS by anyone who can post Markdown.\n\n## Details\n\n`DisallowedRawHtmlRenderer` escapes tags with this regex:\n\n```\n/\u003c(\\/?(?:title|textarea|style|xmp|iframe|noembed|noframes|script|plaintext)[\\s\\/\u003e])/i\n```\n\nThe trailing character class requires one character after the tag name. The block parser does not: `RegexHelper::PARTIAL_HTMLBLOCKOPEN` accepts end of line after a tag name, so `\u003cscript` alone opens an HTML block. Because a rendered `HtmlBlock` has no trailing newline, the regex has nothing to match and the `\u003c` passes through.\n\nIn the browser the newline is still present, so the tag name terminates there and whatever follows becomes attributes.\n\nThis is the same filter that GHSA-4v6x-c7xx-hw9f fixed in 2.8.1. That fix widened the character class but still requires one character, so this case was not covered.\n\n## Reproduction\n\nRender this with `GithubFlavoredMarkdownConverter` and default settings:\n\n```\n\u003cdiv\u003e\n\u003cscript\n\n\u003cspan src=\"/evil.js\"\u003e\n```\n\nOutput:\n\n```html\n\u003cdiv\u003e\n\u003cscript\n\u003cspan src=\"/evil.js\"\u003e\n```\n\nA browser parses that as `\u003cscript src=\"/evil.js\"\u003e` with a junk `\u003cspan` attribute, and the script runs. `\u003ciframe` with `\u003cspan onload=\"...\"\u003e` works the same way and does not need a later `\u003c/script\u003e` in the page.\n\nControl: `\u003cscript src=\"/evil.js\"\u003e\u003c/script\u003e` is correctly escaped to `&lt;script src=\"/evil.js\"\u003e&lt;/script\u003e`.\n\n## Affected versions\n\n1.3.0 (when the extension was added) through the current release. The `\u003c/style` and mid-line forms are only affected as continuation lines inside an already-open HTML block.\n\n## Preconditions\n\n- `html_input` is `allow` (the default)\n- The `DisallowedRawHtml` extension is active, which the GFM extension enables automatically\n- Untrusted users can post Markdown\n\nSetting `html_input` to `escape` or `strip` fully mitigates this.\n\n## Suggested fix\n\nAllow end of string after the tag name:\n\n```php\n$regex = \\sprintf('/\u003c(\\/?(?:%s))([\\s\\/\u003e]|$)/i', \\implode('|', \\array_map('preg_quote', $tags)));\n\nreturn \\preg_replace($regex, '&lt;$1$2', $rendered);\n```\n\nThis escapes every bypass shape above and leaves `\u003cdiv\u003e`, `\u003cscripts\u003e` and `\u003cspan class=\"a\"\u003e` untouched. The existing unit test only covers tag names followed by another character, so a case for a bare tag name should be added.","modified":"2026-09-30T15:45:41.319759238Z","published":"2026-09-30T15:36:33Z","database_specific":{"github_reviewed":true,"github_reviewed_at":"2026-09-30T15:36:33Z","nvd_published_at":null,"cwe_ids":["CWE-78","CWE-79"],"severity":"MODERATE"},"references":[{"type":"WEB","url":"https://github.com/thephpleague/commonmark/security/advisories/GHSA-97jj-33gv-5xf9"},{"type":"WEB","url":"https://github.com/thephpleague/commonmark/commit/411afcc2a7402756d96c89af8882c724d12d47ca"},{"type":"PACKAGE","url":"https://github.com/thephpleague/commonmark"},{"type":"WEB","url":"https://github.com/thephpleague/commonmark/releases/tag/2.10.2"}],"affected":[{"package":{"name":"league/commonmark","ecosystem":"Packagist","purl":"pkg:composer/league/commonmark"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"1.3.0"},{"fixed":"2.10.2"}]}],"versions":["1.3.0","1.3.1","1.3.2","1.3.3","1.3.4","1.4.0","1.4.1","1.4.2","1.4.3","1.5.0","1.5.1","1.5.2","1.5.3","1.5.4","1.5.5","1.5.6","1.5.7","1.5.8","1.6.0","1.6.1","1.6.2","1.6.3","1.6.4","1.6.5","1.6.6","1.6.7","2.0.0","2.0.0-beta1","2.0.0-beta2","2.0.0-beta3","2.0.0-rc1","2.0.0-rc2","2.0.1","2.0.2","2.0.3","2.0.4","2.1.0","2.1.1","2.1.2","2.1.3","2.10.0","2.10.1","2.2.0","2.2.1","2.2.2","2.2.3","2.2.4","2.2.5","2.3.0","2.3.1","2.3.2","2.3.3","2.3.4","2.3.5","2.3.6","2.3.7","2.3.8","2.3.9","2.4.0","2.4.1","2.4.2","2.4.3","2.4.4","2.5.0","2.5.1","2.5.2","2.5.3","2.6.0","2.6.1","2.6.2","2.7.0","2.7.1","2.8.0","2.8.1","2.8.2","2.8.3","2.9.0","2.9.1","2.9.2"],"database_specific":{"last_known_affected_version_range":"\u003c= 2.10.1","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-97jj-33gv-5xf9/GHSA-97jj-33gv-5xf9.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N"}]}