2 Commits

Author SHA1 Message Date
Eva
7494427b8f Merge bc7a4cf8c5 into 376f687c38 2025-04-04 06:59:34 +02:00
Eva
bc7a4cf8c5 client/markdown: prevent arbitrary tags
Introduced in 0137cf383a, anywhere that
allows markdown e.g. comments allowed any arbitrary tag that wasn't
explicitly banned by DOMPurify, since it cannot know whether it came
from our Markdown renderer or from the user.
People could add arbitrary <style> tags that mess with the page,
<button>s etc. It did not lead to XSS since DOMPurify strips it,
but still unacceptable.
escapeHtml is identical to the old behavior of marked.js sanitize=true
2025-04-04 06:58:24 +02:00
2 changed files with 15 additions and 24 deletions

View File

@ -110,24 +110,22 @@ class StrikeThroughWrapper extends BaseMarkdownWrapper {
}
}
function createRenderer() {
function sanitize(str) {
return str.replace(/&<"/g, (m) => {
if (m === "&") {
return "&amp;";
}
if (m === "<") {
return "&lt;";
}
return "&quot;";
});
}
function escapeHtml(unsafe) {
return unsafe
.toString()
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&apos;");
}
function createRenderer() {
const renderer = new marked.Renderer();
renderer.image = (href, title, alt) => {
let [_, url, width, height] =
/^(.+?)(?:\s=\s*(\d*)\s*x\s*(\d*)\s*)?$/.exec(href);
let res = '<img src="' + sanitize(url) + '" alt="' + sanitize(alt);
let res = '<img src="' + escapeHtml(url) + '" alt="' + escapeHtml(alt);
if (width) {
res += '" width="' + width;
}
@ -156,6 +154,7 @@ function formatMarkdown(text) {
new SmallWrapper(),
new StrikeThroughWrapper(),
];
text = escapeHtml(text);
for (let wrapper of wrappers) {
text = wrapper.preprocess(text);
}
@ -182,6 +181,7 @@ function formatInlineMarkdown(text) {
new SmallWrapper(),
new StrikeThroughWrapper(),
];
text = escapeHtml(text);
for (let wrapper of wrappers) {
text = wrapper.preprocess(text);
}
@ -196,4 +196,5 @@ function formatInlineMarkdown(text) {
module.exports = {
formatMarkdown: formatMarkdown,
formatInlineMarkdown: formatInlineMarkdown,
escapeHtml: escapeHtml,
};

View File

@ -156,16 +156,6 @@ function makeCssName(text, suffix) {
return suffix + "-" + text.replace(/[^a-z0-9]/g, "_");
}
function escapeHtml(unsafe) {
return unsafe
.toString()
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&apos;");
}
function arraysDiffer(source1, source2, orderImportant) {
source1 = [...source1];
source2 = [...source2];
@ -221,7 +211,7 @@ module.exports = {
enableExitConfirmation: enableExitConfirmation,
disableExitConfirmation: disableExitConfirmation,
confirmPageExit: confirmPageExit,
escapeHtml: escapeHtml,
escapeHtml: markdown.escapeHtml,
makeCssName: makeCssName,
splitByWhitespace: splitByWhitespace,
arraysDiffer: arraysDiffer,