Potential XSS in ViewSource stripScripts due to malformed javascript: URL filtering regex
- Dominant language
- HTML
- Stars
- 172
- Forks
- 180
- PR merge metrics
- No merged PRs in 30d
Description
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`
```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:
```html
ViewSource Plugin with stripScripts javascript URL bypass PoC
Steps to trigger the issue when stripScripts is enabled:
- Toggle to View Source, ensure the payload remains unchanged, then toggle back.
- Submit the rendered form to observe alert execution.
```
## 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 `` 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:
```html
```
## 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:
## 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:
```js
/<[^>]*=(\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.
Contributor guide
Assessment
This issue has not been assessed yet.