Skip to content

Potential XSS in ViewSource stripScripts due to malformed javascript: URL filtering regex #209

Description

@WiiiiillYeng

Hi, I’d like to report a potential XSS issue in the ViewSource plugin’s regex-based filtering logic.

Summary

dijit/_editor/plugins/ViewSource.js attempts to remove tags containing javascript: URLs in _stripScripts, but the regular expression is malformed. The expression appears to intend to match quoted javascript: attribute values and then use the opening quote as a backreference, but it does not do that.

As a result, a dangerous form action such as action="javascript:alert('XSS')" can remain in the editor content and execute when the form is submitted.

Affected Code

File: dijit/_editor/plugins/ViewSource.js

html = html.replace(/<[^>]*=(\s|)*[("|')]javascript:[^$1][(\s|.)]*[$1][^>]*>/ig, "");

There are four key regex mistakes:

  1. [("|')] is a character class. It does not mean "match either a double quote or a single quote". It matches one character from the set (, ", |, ', or ).
  2. [^$1] is a negated character class. It is not related to capture group 1. It only means "match one character that is not $ and not 1.
  3. [(\s|.)] is a character class. It does not mean "match whitespace or any character". It only matches one character from the set (, \s, |, ., or ).
  4. [$1] is also a character class. It is not a backreference to capture group 1. It only matches either $ or 1.

Because of these mistakes, the regex does not reliably match normal quoted javascript: attribute values.

Proof of Concept

I modified the project-provided test page dijit/tests/editor/test_ViewSource.html and added a PoC editor block:

<div>
    <div id="editor_poc" data-dojo-type="dijit/Editor"
         data-dojo-props='"aria-label":"editor_poc",extraPlugins:["fullScreen", "viewSource"],
        style:"background-color: white; width: 800px;", height:"300px" '>
        <h1>ViewSource Plugin with stripScripts javascript URL bypass PoC</h1>
        <p>Steps to trigger the issue when stripScripts is enabled:</p>
        <ol>
            <li>Toggle to View Source, ensure the payload remains unchanged, then toggle back.</li>
            <li>Submit the rendered form to observe alert execution.</li>
        </ol>
        <!-- PoC payload -->
        <form action="javascript:alert('XSS')"> <input type="submit" value="submit" /> </form>
    </div>
</div>

Reproduction Steps

  1. Open dijit/tests/editor/test_ViewSource.html in the test environment.
  2. Locate the editor_poc editor.
  3. Confirm it uses the default viewSource plugin configuration, where stripScripts is enabled.
  4. Toggle View Source mode on.
  5. Confirm the <form action="javascript:alert('XSS')"> payload remains present.
  6. Toggle View Source mode off.
  7. Submit the rendered form.
  8. alert('XSS') executes.

Expected Result

When stripScripts is enabled, dangerous javascript: URL attributes should be removed or neutralized. The following payload should not remain executable:

<form action="javascript:alert('XSS')">
    <input type="submit" value="submit" />
</form>

Actual Result

The payload is not removed by the _stripScripts regex. The javascript: form action remains in the editor content and executes when the form is submitted.

Screenshots:

Image Image

Security Impact

This issue usually requires the following conditions:

  • The application uses dijit/Editor.
  • The ViewSource plugin is enabled.
  • An attacker can submit, edit, save, or otherwise influence HTML that enters the editor.
  • A victim user views or interacts with the rendered content, such as submitting a form or clicking a link.

If these conditions are met, an attacker can persist malicious javascript: URL attributes in edited content, resulting in stored XSS. If the affected content is only previewed temporarily or passed into the editor for a single request/session, the impact may instead be reflected XSS.

Root Cause

The regex is written as:

/<[^>]*=(\s|)*[("|')]javascript:[^$1][(\s|.)]*[$1][^>]*>/ig

The intended behavior appears to be:

  • match an attribute assignment,
  • match a quote character,
  • match javascript:,
  • consume the rest of the quoted JavaScript URL,
  • match the same closing quote.

But the actual behavior is different:

  • [("|')] is a character class. It matches one character among (, ", |, ', or ). It is not a grouped quote choice in the way the pattern suggests.
  • [(\s|.)]* is a character class repeated zero or more times. It can only consume the literal characters represented inside the class; it does not mean (\s|.)*.
  • [$1] is a character class matching $ or 1. It is not equivalent to \1 and does not refer back to the previously matched quote.

Therefore, normal javascript: attributes such as action="javascript:alert('XSS')" can bypass the filter.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions