{"id":"CVE-2026-105829","aliases":["GHSA-97jj-33gv-5xf9"],"url":"https://o3.security/vulnerability/CVE-2026-105829","summary":"League CommonMark 1.3.0 before 2.10.2 Stored XSS via DisallowedRawHtml Bypass","details":"﻿## 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 `<script` 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/<(\\/?(?:title|textarea|style|xmp|iframe|noembed|noframes|script|plaintext)[\\s\\/>])/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 `<script` alone opens an HTML block. Because a rendered `HtmlBlock` has no trailing newline, the regex has nothing to match and the `<` 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<div>\n<script\n\n<span src=\"/evil.js\">\n```\n\nOutput:\n\n```html\n<div>\n<script\n<span src=\"/evil.js\">\n```\n\nA browser parses that as `<script src=\"/evil.js\">` with a junk `<span` attribute, and the script runs. `<iframe` with `<span onload=\"...\">` works the same way and does not need a later `</script>` in the page.\n\nControl: `<script src=\"/evil.js\"></script>` is correctly escaped to `&lt;script src=\"/evil.js\">&lt;/script>`.\n\n## Affected versions\n\n1.3.0 (when the extension was added) through the current release. The `</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('/<(\\/?(?:%s))([\\s\\/>]|$)/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 `<div>`, `<scripts>` and `<span class=\"a\">` untouched. The existing unit test only covers tag names followed by another character, so a case for a bare tag name should be added.","published":"2026-10-08T14:10:30.523Z","modified":"2026-10-21T02:54:34.424997647Z","cvss":null,"epss":null,"cisaKev":null,"exploitsKnown":null,"affectedPackages":[{"ecosystem":"Packagist","name":"league/commonmark","fixedVersion":"2.10.2"}],"fix":{"url":"https://github.com/thephpleague/commonmark/commit/411afcc2a7402756d96c89af8882c724d12d47ca","label":"thephpleague/commonmark@411afcc"},"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/105xxx/CVE-2026-105829.json"},{"type":"ADVISORY","url":"https://github.com/thephpleague/commonmark/security/advisories/GHSA-97jj-33gv-5xf9"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-105829"},{"type":"ADVISORY","url":"https://www.vulncheck.com/advisories/league-commonmark-1.3.0-before-2.10.2-stored-xss-via-disallowedrawhtml-bypass"},{"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"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-10-21T02:54:34.424997647Z"}}