CVE-2020-12648
Description
A cross-site scripting (XSS) vulnerability in TinyMCE 5.2.1 and earlier allows remote attackers to inject arbitrary web script when configured in classic editing mode.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Cross-site scripting vulnerability in TinyMCE 5.2.1 and earlier allows remote attackers to inject arbitrary web script via iframe content in classic editing mode.
Vulnerability
Details
CVE-2020-12648 is a cross-site scripting (XSS) vulnerability in TinyMCE versions 5.2.1 and earlier when configured in classic editing mode. The root cause is that the HTML parser did not treat `` elements as special elements, causing their content to be parsed as DOM elements instead of raw text [2][3]. This allowed an attacker to inject arbitrary HTML and script code through iframe content.
Exploitation
An attacker can exploit this vulnerability by crafting a malicious payload containing an `` element with embedded script code. When a victim uses the TinyMCE editor in classic mode to view or edit content containing such an iframe, the editor's parser incorrectly processes the iframe's inner content as DOM, executing the injected script. No authentication is required beyond the victim accessing the affected editor instance.
Impact
Successful exploitation leads to arbitrary web script execution in the context of the editor's domain. This can result in data theft, session hijacking, or defacement, depending on the application's use of TinyMCE.
Mitigation
The vulnerability was patched in TinyMCE 5.4.1 and backported to version 4.9.11 [1][2][3]. Users are strongly advised to upgrade to these or later versions. No workaround is available; updating the library is the only effective mitigation.
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
tinymcenpm | < 4.9.11 | 4.9.11 |
tinymcenpm | >= 5.0.0, < 5.4.1 | 5.4.1 |
Affected products
2- TinyMCE/TinyMCEdescription
Patches
22b71c922214dTINY-5943: Backported iframe with content not getting parsed correctly
7 files changed · +35 −4
changelog.txt+2 −0 modified@@ -1,3 +1,5 @@ +Version 4.9.11 (TBD) + Fixed content in an iframe element parsing as dom elements instead of text content #TINY-5943 Version 4.9.10 (2020-04-23) Fixed an issue where the editor selection could end up inside a short ended element (eg br) #TINY-3999 Fixed a security issue related to CDATA sanitization during parsing #TINY-4669
package.json+1 −1 modified@@ -1,6 +1,6 @@ { "name": "tinymce", - "version": "4.9.10", + "version": "4.9.11", "repository": { "type": "git", "url": "https://github.com/tinymce/tinymce.git"
src/core/main/ts/api/html/Schema.ts+1 −1 modified@@ -446,7 +446,7 @@ function Schema(settings?) { textInlineElementsMap = createLookupTable('text_inline_elements', 'span strong b em i font strike u var cite ' + 'dfn code mark q sup sub samp'); - each((settings.special || 'script noscript noframes noembed title style textarea xmp').split(' '), function (name) { + each((settings.special || 'script noscript iframe noframes noembed title style textarea xmp').split(' '), function (name) { specialElements[name] = new RegExp('<\/' + name + '[^>]*>', 'gi'); });
src/core/main/ts/html/ParserFilters.ts+1 −1 modified@@ -23,7 +23,7 @@ const register = (parser, settings: any): void => { const blockElements = Tools.extend({}, schema.getBlockElements()); const nonEmptyElements = schema.getNonEmptyElements(); let parent, lastParent, prev, prevName; - const whiteSpaceElements = schema.getNonEmptyElements(); + const whiteSpaceElements = schema.getWhiteSpaceElements(); let elementRule, textNode; // Remove brs from body element as well
src/core/test/ts/browser/html/DomParserTest.ts+11 −0 modified@@ -694,6 +694,17 @@ UnitTest.asynctest('browser.tinymce.core.html.DomParserTest', function () { ); }); + suite.test('parse iframe XSS', function () { + const serializer = Serializer(); + + LegacyUnit.equal( + serializer.serialize(DomParser().parse( + '<iframe><textarea></iframe><img src="a" onerror="alert(document.domain)" />') + ), + '<iframe><textarea></iframe><img src="a" />' + ); + }); + suite.test('getAttributeFilters/getNodeFilters', function () { const parser = DomParser(); const cb1 = (nodes, name, args) => {};
src/core/test/ts/browser/html/SaxParserTest.ts+2 −1 modified@@ -871,6 +871,7 @@ UnitTest.asynctest('browser.tinymce.core.html.SaxParserTest', function () { const specialHtml = ( '<b>' + '<textarea></b></textarea><title></b></title><script></b></script>' + + '<iframe><img src="image.png"></iframe>' + '<noframes></b></noframes><noscript></b></noscript><style></b></style>' + '<xmp></b></xmp>' + '<noembed></b></noembed>' + @@ -882,7 +883,7 @@ UnitTest.asynctest('browser.tinymce.core.html.SaxParserTest', function () { writer.reset(); parser.parse(specialHtml); LegacyUnit.equal(writer.getContent(), specialHtml); - LegacyUnit.deepEqual(counter.counts, { start: 9, text: 8, end: 9 }); + LegacyUnit.deepEqual(counter.counts, { start: 10, text: 9, end: 10 }); }); suite.test('Parse malformed elements that start with numbers', function () {
src/core/test/ts/browser/html/SchemaTest.ts+17 −0 modified@@ -1,3 +1,4 @@ +import { Arr, Obj } from '@ephox/katamari'; import { LegacyUnit } from '@ephox/mcagar'; import { Pipeline } from '@ephox/agar'; import Schema from 'tinymce/core/api/html/Schema'; @@ -313,6 +314,22 @@ UnitTest.asynctest('browser.tinymce.core.html.SchemaTest', function () { }); }); + suite.test('getSpecialElements', () => { + const schema = Schema(); + const keys = Arr.sort(Obj.keys(schema.getSpecialElements())); + LegacyUnit.equal(keys, Arr.sort([ + 'script', + 'noscript', + 'iframe', + 'noframes', + 'noembed', + 'title', + 'style', + 'textarea', + 'xmp' + ])); + }); + suite.test('isValidChild', function () { let schema;
696e43658dc9TINY-5943: Fixed an issue where iframes with content aren't getting parsed correctly
6 files changed · +34 −4
modules/tinymce/changelog.txt+1 −0 modified@@ -1,5 +1,6 @@ Version 5.4.1 (TBD) Fixed zero-width caret characters included in the Search and Replace plugin results #TINY-4599 + Fixed content in an iframe element parsing as dom elements instead of text content #TINY-5943 Version 5.4.0 (2020-06-30) Added keyboard navigation support to menus and toolbars when the editor is in a ShadowRoot #TINY-6152 Added the ability for menus to be clicked when the editor is in an open shadow root #TINY-6091
modules/tinymce/src/core/main/ts/api/html/Schema.ts+1 −1 modified@@ -487,7 +487,7 @@ function Schema(settings?: SchemaSettings): Schema { const textInlineElementsMap = createLookupTable('text_inline_elements', 'span strong b em i font strike u var cite ' + 'dfn code mark q sup sub samp'); - each((settings.special || 'script noscript noframes noembed title style textarea xmp').split(' '), function (name) { + each((settings.special || 'script noscript iframe noframes noembed title style textarea xmp').split(' '), function (name) { specialElements[name] = new RegExp('<\/' + name + '[^>]*>', 'gi'); });
modules/tinymce/src/core/main/ts/html/ParserFilters.ts+1 −1 modified@@ -76,7 +76,7 @@ const register = (parser: DomParser, settings: DomParserSettings): void => { const blockElements = Tools.extend({}, schema.getBlockElements()); const nonEmptyElements = schema.getNonEmptyElements(); let parent, lastParent, prev, prevName; - const whiteSpaceElements = schema.getNonEmptyElements(); + const whiteSpaceElements = schema.getWhiteSpaceElements(); let elementRule, textNode; // Remove brs from body element as well
modules/tinymce/src/core/test/ts/browser/html/DomParserTest.ts+11 −0 modified@@ -679,6 +679,17 @@ UnitTest.asynctest('browser.tinymce.core.html.DomParserTest', function (success, ); }); + suite.test('parse iframe XSS', function () { + const serializer = Serializer(); + + LegacyUnit.equal( + serializer.serialize(DomParser().parse( + '<iframe><textarea></iframe><img src="a" onerror="alert(document.domain)" />') + ), + '<iframe><textarea></iframe><img src="a" />' + ); + }); + suite.test('getAttributeFilters/getNodeFilters', function () { const parser = DomParser(); const cb1 = (_nodes, _name, _args) => {};
modules/tinymce/src/core/test/ts/browser/html/SaxParserTest.ts+2 −1 modified@@ -909,6 +909,7 @@ UnitTest.asynctest('browser.tinymce.core.html.SaxParserTest', function (success, const specialHtml = ( '<b>' + '<textarea></b></textarea><title></b></title><script></b></script>' + + '<iframe><img src="image.png"></iframe>' + '<noframes></b></noframes><noscript></b></noscript><style></b></style>' + '<xmp></b></xmp>' + '<noembed></b></noembed>' + @@ -920,7 +921,7 @@ UnitTest.asynctest('browser.tinymce.core.html.SaxParserTest', function (success, writer.reset(); parser.parse(specialHtml); LegacyUnit.equal(writer.getContent(), specialHtml); - LegacyUnit.deepEqual(counter.counts, { start: 9, text: 8, end: 9 }); + LegacyUnit.deepEqual(counter.counts, { start: 10, text: 9, end: 10 }); }); suite.test('Parse malformed elements that start with numbers', function () {
modules/tinymce/src/core/test/ts/browser/html/SchemaTest.ts+18 −1 modified@@ -1,5 +1,6 @@ import { Pipeline } from '@ephox/agar'; -import { UnitTest } from '@ephox/bedrock-client'; +import { Assert, UnitTest } from '@ephox/bedrock-client'; +import { Arr, Obj } from '@ephox/katamari'; import { LegacyUnit } from '@ephox/mcagar'; import Schema from 'tinymce/core/api/html/Schema'; @@ -279,6 +280,22 @@ UnitTest.asynctest('browser.tinymce.core.html.SchemaTest', function (success, fa }); }); + suite.test('getSpecialElements', () => { + const schema = Schema(); + const keys = Arr.sort(Obj.keys(schema.getSpecialElements())); + Assert.eq('special elements', keys, Arr.sort([ + 'script', + 'noscript', + 'iframe', + 'noframes', + 'noembed', + 'title', + 'style', + 'textarea', + 'xmp' + ])); + }); + suite.test('isValidChild', function () { const schema = Schema(); ok(schema.isValidChild('body', 'p'));
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-vrv8-v4w8-f95hghsaADVISORY
- github.com/tinymce/tinymce/commit/2b71c922214d388838d930806207a66c14e80f63ghsaWEB
- github.com/tinymce/tinymce/commit/696e43658dc9750ec24fdc4650bd2be9653daf5bghsaWEB
- github.com/tinymce/tinymce/pull/5843ghsaWEB
- github.com/tinymce/tinymce/security/advisories/GHSA-vrv8-v4w8-f95hghsaWEB
- labs.bishopfox.com/advisories/tinymce-version-5.2.1mitrex_refsource_MISC
- www.tiny.cloud/docs/release-notes/release-notes54/ghsaWEB
News mentions
0No linked articles in our index yet.