Unplanned
Last Updated: 15 Nov 2024 13:26 by ADMIN
Andrew
Created on: 17 Dec 2021 03:13
Category: Editor
Type: Bug Report
1
XSS within RadEditor

Hi,

I am contacting you today to let you know I have found cross-site scripting vectors within the latest version of the RadEditor. I have attached images of the payloads that seem to bypass the XSS filter.

The second payload only works on Firefox browsers, but the first works on Chrome browsers too. While it still requires users to click on the link to trigger XSS, it can be easily social engineered in most situations.

5 comments
ADMIN
Rumen
Posted on: 15 Nov 2024 13:26

Hi Norman,

The payloads demonstrated by Andrew are indeed not mitigated by the default XSS-stripping mechanisms provided by RadEditor:

  • StripDomEventAttributes: This handles DOM event attributes (e.g., onclick, onmouseover) but does not apply to SVG to or href attributes.
  • RemoveScripts: This removes <script> tags but does not sanitize inline attributes or certain SVG tags (<set>, <animate>, <brute>).
  • StripCssExpressions: This specifically addresses CSS expressions like expression(), but SVG attributes are outside its scope.

As detailed in Peter Milchev’s response, mitigating these vectors requires additional measures beyond the provided filters. The recommended approach is to implement a custom client-side filter alongside server-side sanitization. Here’s a summary of the remediation steps:

Server-Side Content Sanitization

Below is an example of how you can sanitize the content on the server side by removing potentially harmful SVG attributes (to and href) that reference javascript:

C#

    protected void Page_Load(object sender, EventArgs e)
    {
            string content = RadEditor1.Content;

            // Regular expression to identify 'javascript:' in `to` and `href` attributes within SVG elements
            string pattern = @"(<(set|animate|brute)[^>]*?\s(?:to|href)\s*=\s*['""]?)javascript:[^'""]*";

            // Replace occurrences of 'javascript:' with an empty string
            content = System.Text.RegularExpressions.Regex.Replace(content, pattern, "$1", System.Text.RegularExpressions.RegexOptions.IgnoreCase);

            // Assign the sanitized content back to the editor
            RadEditor1.Content = content;
    }

VB.NET 

Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)
    ' Get the content from RadEditor
    Dim content As String = RadEditor1.Content

    ' Regular expression to identify 'javascript:' in `to` and `href` attributes within SVG elements
    Dim pattern As String = "(<(set|animate|brute)[^>]*?\s(?:to|href)\s*=['""]?)javascript:[^'""]*"

    ' Replace occurrences of 'javascript:' with an empty string
    content = System.Text.RegularExpressions.Regex.Replace(content, pattern, "$1", System.Text.RegularExpressions.RegexOptions.IgnoreCase)

    ' Assign the sanitized content back to the editor
    RadEditor1.Content = content
End Sub

Here is an improved version that matches tags with special attributes that can potentially execute JavaScript

    protected void Page_Load(object sender, EventArgs e)
    {
        string content = RadEditor1.Content;

        // Matches tags and attributes that can potentially execute JavaScript
        string pattern = @"(<([a-zA-Z0-9]+)[^>]*?\s(?:to|href|xlink:href|src|action|formaction|style)\s*=\s*['""]?)javascript:[^'""]*";

        // Replace occurrences of 'javascript:' with an empty string
        content = System.Text.RegularExpressions.Regex.Replace(content, pattern, "$1", System.Text.RegularExpressions.RegexOptions.IgnoreCase);

        // Assign the sanitized content back to the editor
        RadEditor1.Content = content;
    }

VB.NET

    Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
        Dim content As String = RadEditor1.Content

        ' Matches tags and attributes that can potentially execute JavaScript
        Dim pattern As String = "(<([a-zA-Z0-9]+)[^>]*?\s(?:to|href|xlink:href|src|action|formaction|style)\s*=\s*['""]?)javascript:[^'""]*"

        ' Replace occurrences of 'javascript:' with an empty string
        content = System.Text.RegularExpressions.Regex.Replace(content, pattern, "$1", System.Text.RegularExpressions.RegexOptions.IgnoreCase)

        ' Assign the sanitized content back to the editor
        RadEditor1.Content = content
    End Sub

Client-Side Custom Filter
Implementing a client-side filter can help mitigate such issues before the content is posted back to the server. Here is an example of how a custom filter can be added to strip problematic SVG tags and attributes.

  <telerik:RadEditor ID="RadEditor1" runat="server" OnClientLoad="OnClientLoad">
      <Content>
        <a href="javascript:alert('XSS')">Click me</a>
        <svg><use xlink:href="javascript:alert('XSS')"></use></svg>
        <img src="javascript:alert('XSS')" />
        <button formaction="javascript:alert('XSS')">Submit</button>
        <form action="javascript:alert('XSS')"></form>
        <svg>
            <set attributeName="href" to="javascript:alert('XSS')" />
            <animate attributeName="to" to="javascript:alert('XSS')" />
        </svg>
        <custom-tag href="javascript:alert('XSS')">Custom</custom-tag>

        <math><brute href="javascript:alert(1)">click</brute></math>
        <svg><a><rect width="99%" height="99%"></rect><set attributename="href" to="javascript: alert(1)"></set></a></svg>
      </Content>
  </telerik:RadEditor>
  <script type="text/javascript">
      function OnClientLoad(editor, args) {
          editor.get_filtersManager().add(new MyCustomSVGFilter());
      }

      function MyCustomSVGFilter() {
          MyCustomSVGFilter.initializeBase(this);
          this.set_isDom(true);
          this.set_enabled(true);
          this.set_name("MyCustomSVGFilter");
          this.set_description("Strips potentially dangerous attributes that contain 'javascript:' or other harmful values.");
      }

      MyCustomSVGFilter.prototype = {
          encodeScripts: function (contentToEncode) {
              // List of tags to inspect for dangerous attributes
              const tags = [
                  "a", "svg", "use", "img", "div", "button", "form",
                  "set", "animate", "custom-tag", "brute"
              ];

              // Attributes to check for harmful values
              const attributes = [
                  "href", "xlink:href", "src", "to", "formaction", "action", "style"
              ];

              for (let tag of tags) {
                  let elements = contentToEncode.getElementsByTagName(tag);

                  for (let element of elements) {
                      for (let attr of attributes) {
                          let value = element.getAttribute(attr);
                          if (value && this.isDangerous(value)) {
                              element.removeAttribute(attr);
                          }
                      }
                  }
              }

              return contentToEncode;
          },

          isDangerous: function (value) {
              // Identify dangerous values (e.g., starting with "javascript:")
              value = value.toLowerCase();
              return value.startsWith("javascript:") || value.includes("expression(");
          },

          getDesignContent: function (content) {
              return this.encodeScripts(content);
          },

          // The same logic applies when switching to HTML mode or submitting content
          getHtmlContent: function (content) {
              return this.encodeScripts(content);
          }
      };

      MyCustomSVGFilter.registerClass('MyCustomSVGFilter', Telerik.Web.UI.Editor.Filter);
</script>

 

 

Regards,
Rumen
Progress Telerik

Stay tuned by visiting our public roadmap and feedback portal pages! Or perhaps, if you are new to our Telerik family, check out our getting started resources
Norman
Posted on: 11 Nov 2024 14:13
Is this still on Unplanned, is it low priority or not observable?

Shouldn't the following filter block it? ContentFilters="DefaultFilters,StripCssExpressions,StripDomEventAttributes,RemoveScripts"

Thanks
Norman Farrar
ADMIN
Peter Milchev
Posted on: 01 Feb 2022 09:23

Hi Sullivanst,

Thank you for pointing this out, the old check indeed had this flaw. I have updated the code so it first makes the attribute lower case and then searches with indexOf:

 if (attr && attr.toLowerCase().indexOf("javascript") > -1) {

Regards,
Peter Milchev
Progress Telerik

Virtual Classroom, the free self-paced technical training that gets you up to speed with Telerik and Kendo UI products quickly just got a fresh new look + new and improved content including a brand new Blazor course! Check it out at https://learn.telerik.com/.

Matthew
Posted on: 31 Jan 2022 21:46
Looks like it's an issue with the suggested remediation that it's case sensitive, whereas the URL scheme is not. I'll be modifying my implementation to also reject "JAVASCRIPT:" urls ;)
ADMIN
Peter Milchev
Posted on: 23 Dec 2021 14:53

Hello Andrew,

Thank you for bringing up this case, it indeed is not sanitized by the DOM or events sanitizers. The same issue is observed also with the <animate> tag in the SVG as well:

For the time being, you can alleviate the issue by using a custom filter client-side and manually sanitizing the content on the server-side:

Here is a sample implementation of the client-side filter:

 

<script type="text/javascript">
    function OnClientLoad(editor, args) {
        editor.get_filtersManager().add(new MyCustomSVGFilter());
    }

    function MyCustomSVGFilter() {
        MyCustomSVGFilter.initializeBase(this);
        this.set_isDom(true);
        this.set_enabled(true);
        this.set_name("MyCustomSVGFilter");
        this.set_description("Strips set, animate and brute attributes that contain 'javascript:' as value");
    }

    MyCustomSVGFilter.prototype = {
        encodeScripts: function (contentToEncode) {
            var list = contentToEncode.getElementsByTagName("set");
            for (let item of list) {
                var attr = item.getAttribute("to");
                if (attr && attr.toLowerCase().indexOf("javascript") > -1) {
                    item.removeAttribute("to");
                }
            }
            list = contentToEncode.getElementsByTagName("animate");
            for (let item of list) {
                var attr = item.getAttribute("to");
                if (attr && attr.toLowerCase().indexOf("javascript") > -1) {
                    item.removeAttribute("to");
                }
            }

            list = contentToEncode.getElementsByTagName("brute");
            for (let item of list) {
                var attr = item.getAttribute("href");
                if (attr && attr.toLowerCase().indexOf("javascript") > -1) {
                    item.removeAttribute("href");
                }
            }

            return contentToEncode;
        },

        getDesignContent: function (content) {
            return this.encodeScripts(content);
        },

        // The code below is the same because it needs to be applied when switching to HTML mode and also when content is submitted. 
        getHtmlContent: function (content) {
            return this.encodeScripts(content);
        }
    }

    MyCustomSVGFilter.registerClass('MyCustomSVGFilterFilter', Telerik.Web.UI.Editor.Filter);
</script>

 

As a small token of gratitude for helping us identify this, we have updated your Telerik points.

Regards,
Peter Milchev
Progress Telerik

Virtual Classroom, the free self-paced technical training that gets you up to speed with Telerik and Kendo UI products quickly just got a fresh new look + new and improved content including a brand new Blazor course! Check it out at https://learn.telerik.com/.