New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uses innerHTML - potentially allows injection attacks #255
Comments
Thanks @cocoademon. It's really a XSS vulnerability. I'll stop using it in my product. |
Why stop using it if you can just call some generic @cocoademon, there is another place where innerHTML is used and using DOMElement won't be a solution: quill-mention/src/blots/mention.js Line 21 in dc1a78c
This is the code where the blot is rendered. |
I can't risk my bussiness product being hacked even if I use any escapeHTML packages. What if a new XSS method emerge? The best solution is to stop assigning user generated value to |
Hello, I've attempted to partially address this issue with #341, which sanitizes I think fully resolving this issue would most likely need to properly support returning HTML from |
I'm thinking, maybe in the meantime we can provide |
Doing even more thinking, we could check if the returned item is a string or object, that would remove the need to change the |
I like this idea. It provides backward compatibility for anyone who never intended to output HTML and an easy upgrade path. What if the returned object is actually a With explicit approach, the upgrade instructions would be something along the lines of: Replace this renderItem(item) {
return '<span style="color: ' + item.color + '">' + item.value + '</span>'
} With this renderItem(item) {
const renderer = document.createElement('template')
renderer.innerHTML = '<span style="color: ' + item.color + '">' + item.value + '</span>'
return renderer.content
} Or even better renderItem(item) {
const element = document.createElement('span')
element.innerText = item.value
element.style.color = item.color
return element
} |
I thinks for backward compatibility. If we prodvide a new bool option named const quill = new Quill("#editor", {
modules: {
mention: {
allowedChars: /^[A-Za-z\sÅÄÖåäö]*$/,
mentionDenotationChars: ["@", "#"],
source: async function(searchTerm, renderList) {
const matchedPeople = await suggestPeople(searchTerm);
renderList(matchedPeople);
},
allowHTML: true // default for `true` in minor version bumping, `false` for major version bumping
}
}
}) Maybe supporting Node.isPrototypeOf(Document) // true
Node.isPrototypeOf(Element) // true
Node.isPrototypeOf(DocumentFragment) // true |
Hmm, i'm running into a roadblock with rendering the mention blots. There'll be no good way to tell the difference between blots that have been saved in the past that rely on Since this is already a potentially breaking change, we could introduce a breaking change into the blot structure by saving text under |
I ended up saving the html in the mention blot under |
Hmm, Before:
After (unserialized):
After (JSON serialized):
I see two consequences to this:
For (1) it arguably is the website developer's responsibility to sanitize user-generated content, including Deltas from Quill, and the proposed implementation would already shield the original editor (whoever inserts these mentions in the editor) from incoming XSS via To address (2), we must refrain from storing objects under The ultimate solution would of course be passing a render function to the blot itself, so the developer that needs a customized blot, based on some custom More realistic solution would be to allow developers to extend from It seems to me like this is the only sure way to avoid stored XSS during hydration of an editor while still allowing custom design for the blot. The changes to blot code may look like this: class MentionBlot extends Embed {
// ...
static create(data) {
const node = super.create();
const denotationChar = document.createElement("span");
denotationChar.className = "ql-mention-denotation-char";
denotationChar.innerText = data.denotationChar;
node.appendChild(denotationChar);
if (typeof this.render === 'function') {
node.appendChild(this.render(data))
} else {
node.innerText += data.value;
}
return MentionBlot.setDataValues(node, data);
}
// ...
} User instructions may look like this: import { MentionBlot } from 'quill-mention'
class StyledMentionBlot extends MentionBlot {
constructor(scroll, node) {
super(scroll, node);
}
static render(data) {
const element = document.createElement('span');
element.innerText = data.value;
element.style.color = data.color;
return element;
}
}
StyledMentionBlot.blotName = "styled-mention";
Quill.register(StyledMentionBlot);
// ...
const quill = new Quill('#editor', {
modules: {
mention: {
// ...
dataAttributes: ['id', 'value', 'denotationChar', 'link', 'target', 'disabled', 'color'],
blotName: 'styled-mention',
}
}
}); And we'd have to export |
This seems like a good path forward! I think it mirrors the way other custom blots are added to Quill. I'm still a bit confused by
but that might just be due to my inexperience with Quill. Currently (if i'm understanding correctly) there's only one To me it seems like this would be easier to implement initially (if they just have one custom type they want to style, just throwing in a custom Please let me know if this sounds good or if i'm on the right track at least 😄 |
I think I'm understanding a bit more after reading into custom blots. It seems like there's been some interest in rendering blots based on editor instance options (which is what we'd need for my proposed solution) which has been discussed here: quilljs/quill#1162 But I'm thinking it might be better to jump through all the hoops of defining custom blots off of |
In renderList, the list items are rendered using innerHTML with no escaping.
If the mentions list is sourced from unsafe (user-sourced) data, this might allow an injection attack when a Quill user hits @.
quill-mention/src/quill.mention.js
Line 391 in 0aa9847
A possible solution is to change renderItem to return a DOMelement, which uses textContent rather than innerHTML.
The text was updated successfully, but these errors were encountered: