Skip to content
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

Feat: Enable XSS defense by default #611

Merged
merged 3 commits into from
Nov 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README-EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export default {
| imageFilter | Function | null | Image file filter Function, params is a `File Object`, you should return `Boolean` about the test result |
| imageClick | function | null | Image Click Function |
| tabSize | Number | null | How many spaces equals one tab, default \t |
| xssOptions | Object | null | xss options: [https://github.com/leizongmin/js-xss](https://github.com/leizongmin/js-xss) |
| xssOptions | Object | {} | xss rule configuration, enabled by default, set to false to turn off, custom rule reference [https://jsxss.com/zh/options.html](https://jsxss.com/zh/options.html) |
| toolbars | Object | As in the following example | toolbars |

```javascript
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export default {
| imageFilter | function | null | 图片过滤函数,参数为一个`File Object`,要求返回一个`Boolean`, `true`表示文件合法,`false`表示文件不合法 |
| imageClick | function | null | 图片点击事件,默认为预览,可覆盖 |
| tabSize | Number | \t | tab转化为几个空格,默认为\t |
| xssOptions | Object | null | xss规则配置,参考 [https://github.com/leizongmin/js-xss](https://github.com/leizongmin/js-xss) |
| xssOptions | Object | {} | xss规则配置, 默认开启,设置false可以关闭,自定义规则参考 [https://jsxss.com/zh/options.html](https://jsxss.com/zh/options.html) |
| toolbars | Object | 如下例 | 工具栏 |

```javascript
Expand Down
19 changes: 19 additions & 0 deletions src/lib/core/rules.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export const HEADER_FLAG = ' _MD-HEADER_ ';

export function headRule(tocHeadRule) {
return function (tokens, index) {
let code = tocHeadRule(tokens, index);
var label = tokens[index + 1];
if (label.type === 'inline') {
return code.replace('<a', `<a${HEADER_FLAG}`);
}
return code;
};
}

export function recoverHead(tag, html) {
if (tag === 'a' && html.indexOf(HEADER_FLAG) !== -1) {
return html.replace(HEADER_FLAG, '');
}
return html;
}
5 changes: 5 additions & 0 deletions src/lib/mixins/markdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import hljsLangs from '../core/hljs/lang.hljs.js'
import {
loadScript
} from '../core/extra-function.js'
import { headRule } from '../core/rules.js'

var markdown_config = {
html: true, // Enable HTML tags in source
xhtmlOut: true, // Use '/' to close single tags (<br />).
Expand Down Expand Up @@ -89,6 +91,9 @@ markdown.use(mihe, hljs_opts)
.use(taskLists)
.use(toc)

const tocHeadRule = markdown.renderer.rules.heading_open;
markdown.renderer.rules.heading_open = headRule(tocHeadRule);

export default {
data() {
return {
Expand Down
78 changes: 64 additions & 14 deletions src/mavon-editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,9 @@ import md_toolbar_left from './components/md-toolbar-left.vue'
import md_toolbar_right from './components/md-toolbar-right.vue'
import "./lib/font/css/fontello.css"
import './lib/css/md.css'
const xss = require('xss');
import { recoverHead } from './lib/core/rules.js'
import { FilterXSS } from 'xss';

export default {
mixins: [markdown],
props: {
Expand Down Expand Up @@ -197,10 +199,10 @@ export default {
return CONFIG.toolbars
}
},
xssOptions: { // 工具栏
type: Object,
xssOptions: { // XSS 选项
type: [Object, Boolean],
default() {
return null
return {}
}
},
codeStyle: { // <code></code> 样式
Expand Down Expand Up @@ -305,7 +307,8 @@ export default {
},
p_external_link: {},
textarea_selectionEnd: 0,
textarea_selectionEnds: [0]
textarea_selectionEnds: [0],
_xssHandler: null
};
},
created() {
Expand Down Expand Up @@ -640,10 +643,65 @@ export default {
console.warn('hljs color scheme', val, 'do not exist, hljs color scheme will not change');
}
},
xssHandler(htmlCode) {
if (this._xssHandler) {
return this._xssHandler.process(htmlCode);
}

let originalTagFun;
if (typeof this.xssOptions['onTag'] === 'function') {
originalTagFun = this.xssOptions['onTag'];
}
this.xssOptions['onTag'] = function(tag, html, info) {
let code = recoverHead(tag, html);
if (originalTagFun) {
code = originalTagFun(tag,code);
}
if (html !== code) {
return code;
}
}

let originalTagAttr;
if (typeof this.xssOptions['onTagAttr'] === 'function') {
originalTagAttr = this.xssOptions['onTagAttr'];
}
this.xssOptions['onTagAttr'] = function (tag, name, value) {
const whiteClass = {
"div": ['hljs-left', 'hljs-center', 'hljs-right', 'hljs-*'],
"code": ['lang-language','lang-*'],
"span": ['hljs-*']
};

let newValue, oriValue;
if (name === 'class' &&
whiteClass[tag] &&
whiteClass[tag].find(el => {
return !!value.match(el)
}))
{
newValue = name + '="' + value + '"';
}

if (originalTagAttr) {
oriValue = originalTagAttr(tag, name, value);
}

if (newValue || oriValue) {
return oriValue || newValue;
}
};

this._xssHandler = new FilterXSS(this.xssOptions);
return this._xssHandler.process(htmlCode);
},
iRender(toggleChange) {
var $vm = this;
this.$render($vm.d_value, function(res) {
// render
// HTML 渲染前先进行过滤,避免 xss 问题,默认情况下开始此功能
if (typeof $vm.xssOptions === 'object') {
res = $vm.xssHandler(res);
}
$vm.d_render = res;
// change回调 toggleChange == false 时候触发change回调
if (!toggleChange)
Expand Down Expand Up @@ -674,14 +732,6 @@ export default {
this.iRender();
},
value: function (val, oldVal) {
// Escaping all XSS characters
// escapeHtml (html) {
// return html
// }
if (this.xssOptions) {
val = xss(val, this.xssOptions);
}

if (val !== this.d_value) {
this.d_value = val
}
Expand Down
2 changes: 1 addition & 1 deletion webpack/webpack.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ var config = {
// hot: true,
// noInfo: true
},
devtool: '#eval-source-map'
devtool: 'source-map'
}

var res = merge([base, config]);
Expand Down