VYPR
Moderate severityNVD Advisory· Published Aug 14, 2020· Updated Aug 4, 2024

CVE-2020-12648

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.

PackageAffected versionsPatched versions
tinymcenpm
< 4.9.114.9.11
tinymcenpm
>= 5.0.0, < 5.4.15.4.1

Affected products

2

Patches

2
2b71c922214d

TINY-5943: Backported iframe with content not getting parsed correctly

https://github.com/tinymce/tinymceLee NewsonJul 3, 2020via ghsa
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;
     
    
696e43658dc9

TINY-5943: Fixed an issue where iframes with content aren't getting parsed correctly

https://github.com/tinymce/tinymceLee NewsonJul 3, 2020via ghsa
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

News mentions

0

No linked articles in our index yet.