From 27ab64902b55b499b40ae0a3e0de276dd1238160 Mon Sep 17 00:00:00 2001 From: undefined Date: Wed, 9 Feb 2022 17:15:58 +0800 Subject: [PATCH] ui: fix xss whitelist for katex --- .../ui-default/backendlib/markdown-it-xss.ts | 59 +++++++++++-------- packages/ui-default/backendlib/template.js | 8 +-- packages/ui-default/package.json | 2 +- 3 files changed, 38 insertions(+), 31 deletions(-) diff --git a/packages/ui-default/backendlib/markdown-it-xss.ts b/packages/ui-default/backendlib/markdown-it-xss.ts index 2dba4cc30..f618a3ced 100644 --- a/packages/ui-default/backendlib/markdown-it-xss.ts +++ b/packages/ui-default/backendlib/markdown-it-xss.ts @@ -1,7 +1,35 @@ import * as Xss from 'xss'; const stack = []; -let isFull = false; + +const tagCheck = new Xss.FilterXSS({ + css: false, + whiteList: {}, + onIgnoreTag(tag, html, options) { + if (html.endsWith('/>')) return html; + if (!options.isClosing) { + stack.push(tag); + return html; + } + if (stack.length === 0) { + return html.replace(//g, '>'); // 没有标签可供闭合 + } + if (stack[stack.length - 1] === tag) { + stack.pop(); // 正常关闭 + return html; + } + if (stack.length - 2 >= 0 && stack[stack.length - 2] === tag) { + // 可能丢失了一个结束标签 + stack.pop(); + stack.pop(); + return html; + } + return html.replace(//g, '>'); // 可能多出了一个结束标签 + }, + onIgnoreTagAttr(tag, name, value) { + return value; + }, +}); const xss = new Xss.FilterXSS({ whiteList: { @@ -76,34 +104,13 @@ const xss = new Xss.FilterXSS({ if (name === 'class') return value.replace(/badge/g, 'xss-badge'); return value; }, - onTag(tag, html, options) { - if (!options.isWhite || !isFull) return null; - if (!options.isClosing) { - stack.push(tag); - return null; - } - if (stack.length === 0) return `</${tag}>`; // 没有标签可供闭合 - if (stack[stack.length - 1] === tag) { - stack.pop(); // 正常关闭 - return null; - } - if (stack.length - 2 >= 0 && stack[stack.length - 2] === tag) { - // 可能丢失了一个结束标签 - stack.pop(); - stack.pop(); - return null; - } - return `</${tag}>`; // 可能多出了一个结束标签 - }, }); -xss.process = ((original) => (html: string, full: boolean = false) => { +function ensureTag(html: string) { stack.length = 0; - isFull = full; - const res = original(html); - if (!full) return res; + const res = tagCheck.process(html); return res + stack.map((i) => ``).join(''); -})(xss.process.bind(xss)); +} function xssProtector(md) { function protector(state) { @@ -126,4 +133,4 @@ function xssProtector(md) { md.core.ruler.after('linkify', 'xss', protector); } -module.exports = { xss, xssProtector }; +module.exports = { xss, ensureTag, xssProtector }; diff --git a/packages/ui-default/backendlib/template.js b/packages/ui-default/backendlib/template.js index 9b127f17e..c19f7d370 100644 --- a/packages/ui-default/backendlib/template.js +++ b/packages/ui-default/backendlib/template.js @@ -9,7 +9,7 @@ const argv = require('cac')().parse(); const { findFileSync } = require('@hydrooj/utils/lib/utils'); const status = require('@hydrooj/utils/lib/status'); const markdown = require('./markdown'); -const { xss } = require('./markdown-it-xss'); +const { xss, ensureTag } = require('./markdown-it-xss'); const { misc, buildContent, avatar } = global.Hydro.lib; @@ -63,8 +63,8 @@ class Nunjucks extends nunjucks.Environment { this.addFilter('dumpYaml', (self) => yaml.dump(self)); this.addFilter('serialize', (self, ignoreFunction = true) => serialize(self, { ignoreFunction })); this.addFilter('assign', (self, data) => Object.assign(self, data)); - this.addFilter('markdown', (self, html = false) => xss.process(markdown.render(self, html), true)); - this.addFilter('markdownInline', (self, html = false) => xss.process(markdown.renderInline(self, html), true)); + this.addFilter('markdown', (self, html = false) => ensureTag(markdown.render(self, html), true)); + this.addFilter('markdownInline', (self, html = false) => ensureTag(markdown.renderInline(self, html), true)); this.addFilter('ansi', (self) => misc.ansiToHtml(self)); this.addFilter('base64_encode', (s) => Buffer.from(s).toString('base64')); this.addFilter('base64_decode', (s) => Buffer.from(s, 'base64').toString()); @@ -87,7 +87,7 @@ class Nunjucks extends nunjucks.Environment { else s = s[langs[0]]; } if (s instanceof Array) s = buildContent(s, html ? 'html' : 'markdown', (str) => str.translate(language)); - return xss.process(html ? s : markdown.render(s), true); + return ensureTag(html ? xss.process(s) : markdown.render(s), true); }); this.addFilter('log', (self) => { console.log(self); diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 203ccacb1..5c0340353 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/ui-default", - "version": "4.31.10", + "version": "4.31.11", "author": "undefined ", "license": "AGPL-3.0", "main": "hydro.js",