Accidentally found a CVE in the Froala WYSIWYG Editor during a pentest.

Affected Products#

All versions of Froala WYSIWYG <=4.3.0

Vulnerability Description#

The vulnerability arises due to errors in parsing <plaintext> tags, and allows for appending HTML elements with event handlers.

Vulnerability Severity#

CVSS 3.1 - 6.1

Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N

Technical Breakdown#

There is a “Code View” option on the Froala editor which exposes the raw HTML that will be rendered. By default there is a sanitation step that is run on the submitted HTML before rendering it. Classic payloads like <img src=1 onerror=alert(1)> and similar do not work because of this sanitation, and of course, <script> tags don’t either.

HTML rendering is pretty error-resistant by design, however how the implementation is error-resistant is not strictly defined and leads to a lot of developer interpretation when presented with invalid HTML. These quirks can often be exploited and even have their own sub-category of vulnerability called mXSS. In this case it seems like the parser automatically wraps text in a <p> tag if the text is not already in a <p> tag - even other elements like <img> or <span>.

The <plaintext> tag is one of a few tags that automatically closes <p> tags in the HTML spec, the Froala editor doesn’t parse it safely, allowing malicious tags that execute javascript to be inserted after <plaintext>.

The minified function clean.html() responsible for cleaning HTML is below

function h(e, t, n, r) {
    void 0 === t && (t = []), void 0 === n && (n = []), void 0 === r && (r = !1);
    var a,
        o = f.merge([], m.opts.htmlAllowedTags);
    for (a = 0; a < t.length; a++) 0 <= o.indexOf(t[a]) && o.splice(o.indexOf(t[a]), 1);
    var i = f.merge([], m.opts.htmlAllowedAttrs);
    for (a = 0; a < n.length; a++) 0 <= i.indexOf(n[a]) && i.splice(i.indexOf(n[a]), 1);
    return (
        i.push("data-fr-.*"),
        i.push("fr-.*"),
        (v = new RegExp("^".concat(o.join("$|^"), "$"), "gi")),
        (C = new RegExp("^".concat(i.join("$|^"), "$"), "gi")),
        (b = new RegExp("^".concat(m.opts.htmlRemoveTags.join("$|^"), "$"), "gi")),
        (E = m.opts.htmlAllowedStyleProps.length ? new RegExp("((^|;|\\s)".concat(m.opts.htmlAllowedStyleProps.join(":.+?(?=;|$))|((^|;|\\s)"), ":.+?(?=(;)|$))"), "gi") : null),
        (e = p(e, u, !0)),
        "undefined" != typeof m.opts.DOMPurify && (e = m.opts.DOMPurify.sanitize(e, { ADD_TAGS: m.opts.htmlAllowedTags, ALLOW_UNKNOWN_PROTOCOLS: !0 })),
        e
    );
}

Froala makes explicit mention of using DOMPurify, but doesn’t package it with the editor, nor mentions it in the documentation. If DOMPurify is loaded and accessible to Froala this exploit would not work.

PoC#

Paste the below code into the “Code View” of a Froala editor

<plaintext><img src=1 onerror=alert(1)>

Suggested Fix#

To better protect against XSS in the current and all future versions, make sure DOMPurify is accessible to the Froala editor.

Otherwise, when an update is available update to the latest version.