Skip to content

Commit bd6882d

Browse files
authored
Merge pull request laobubu#44 from 0xGG/feat/prevent-xss
feat: Improve XSS Prevention
2 parents 70e26db + c4e4f0d commit bd6882d

6 files changed

Lines changed: 324 additions & 288 deletions

File tree

demo/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ require([
129129
code: true,
130130
},
131131

132-
inputProps: "textarea",
132+
inputStyle: "contenteditable",
133133
});
134134
editor.setSize(null, "100%"); // set height
135135
editor.on("imageClicked", (args) => {

package.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vickymd",
3-
"version": "0.1.24",
3+
"version": "0.1.25",
44
"description": "An experimental WYSIWYG markdown editor built on top of HyperMD with extended Widgets support",
55
"main": "./everything.js",
66
"types": "./everything.d.ts",
@@ -31,28 +31,28 @@
3131
"@rollup/plugin-json": "^4.0.3",
3232
"@types/codemirror": "^0.0.91",
3333
"@types/glob": "^7.1.1",
34-
"@types/markdown-it": "^10.0.0",
34+
"@types/markdown-it": "^10.0.1",
3535
"@types/mermaid": "^8.2.1",
3636
"@types/prop-types": "^15.7.3",
37-
"@types/react": "^16.9.25",
38-
"@types/react-dom": "^16.9.5",
37+
"@types/react": "^16.9.34",
38+
"@types/react-dom": "^16.9.7",
3939
"@types/tern": "^0.23.3",
4040
"@types/yamljs": "^0.2.30",
41-
"codemirror": "^5.52.2",
41+
"codemirror": "^5.53.2",
4242
"express": "^4.17.1",
4343
"glob": "^7.1.6",
4444
"minimatch": "^3.0.4",
4545
"opn": "^6.0.0",
46-
"rollup": "^2.6.1",
46+
"rollup": "^2.7.3",
4747
"rollup-plugin-commonjs": "^10.1.0",
4848
"rollup-plugin-node-resolve": "^5.2.0",
4949
"rollup-plugin-typescript2": "^0.27.0",
5050
"rollup-plugin-uglify": "^6.0.4",
51-
"sass": "^1.26.3",
51+
"sass": "^1.26.5",
5252
"typescript": "^3.8.3"
5353
},
5454
"peerDependencies": {
55-
"codemirror": "^5.52.2"
55+
"codemirror": "^5.53.2"
5656
},
5757
"optionalDependencies": {
5858
"echarts": "^4.7.0",
@@ -75,10 +75,10 @@
7575
"react-dom": "^16.13.1",
7676
"turndown": "^6.0.0",
7777
"turndown-plugin-gfm": "^1.0.2",
78-
"twemoji": "^12.1.5",
79-
"vega": "^5.10.1",
78+
"twemoji": "^12.1.6",
79+
"vega": "^5.11.1",
8080
"vega-embed": "^6.6.0",
81-
"vega-lite": "^4.10.5",
81+
"vega-lite": "^4.11.0",
8282
"yamljs": "^0.3.0"
8383
},
8484
"dependencies": {}

src/addon/fold-html.ts

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,30 +10,39 @@ import {
1010
Addon,
1111
suggestedEditorConfig,
1212
visitElements,
13-
watchSize
13+
watchSize,
1414
} from "../core";
1515
import { cm_t } from "../core/type";
1616
import {
1717
registerFolder,
1818
breakMark,
1919
FolderFunc,
20-
RequestRangeResult
20+
RequestRangeResult,
2121
} from "./fold";
2222
import "./read-link";
2323

2424
/********************************************************************************** */
2525
/**
2626
* Before folding HTML, check its security and avoid XSS attack! Returns true if safe.
2727
*/
28-
export type CheckerFunc = (html: string, pos: Position, cm: cm_t) => boolean;
28+
export type CheckerFunc = (html: string) => boolean;
2929

30-
export var defaultChecker: CheckerFunc = html => {
30+
// export type CheckerFunc = (html: string, pos: Position, cm: cm_t) => boolean;
31+
export var defaultChecker: CheckerFunc = (html) => {
3132
// TODO: read https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
3233

33-
if (/^<(?:br)/i.test(html)) return false; // check first element...
34-
if (/<(?:script|style|link|meta)/i.test(html)) return false; // don't allow some tags
35-
if (/\son\w+\s*=/i.test(html)) return false; // don't allow `onclick=` etc.
36-
if (/src\s*=\s*["']?javascript:/i.test(html)) return false; // don't allow `src="javascript:` etc.
34+
if (/^<(?:br)/i.test(html)) {
35+
return false; // check first element...
36+
}
37+
if (/<(?:script|style|link|meta|object|embed|iframe)/i.test(html)) {
38+
return false; // don't allow some tags
39+
}
40+
if (/\son\w+\s*=/i.test(html)) {
41+
return false; // don't allow `onclick=` etc.
42+
}
43+
if (/(src|background|href)\s*=\s*["']?javascript:/i.test(html)) {
44+
return false; // don't allow `src="javascript:` etc.
45+
}
3746
return true;
3847
};
3948

@@ -89,11 +98,12 @@ export var defaultRenderer: RendererFunc = (
8998
}
9099

91100
var innerHTML = html.slice(startCh, endCh);
92-
if (innerHTML) ans.innerHTML = innerHTML;
101+
if (innerHTML) {
102+
ans.innerHTML = innerHTML;
103+
}
93104

94105
// resolve relative URLs and change default behavoirs
95-
96-
visitElements([ans], el => {
106+
visitElements([ans], (el) => {
97107
const tagName = el.tagName.toLowerCase();
98108

99109
if (tagName === "a") {
@@ -106,7 +116,7 @@ export var defaultRenderer: RendererFunc = (
106116
const urlAttrs: string[] = {
107117
a: ["href"],
108118
img: ["src"],
109-
iframe: ["src"]
119+
iframe: ["src"],
110120
}[tagName];
111121

112122
if (urlAttrs) {
@@ -167,7 +177,7 @@ export const HTMLFolder: FolderFunc = (stream, token) => {
167177
var addon = getAddon(cm);
168178
var html: string = cm.getRange(from, to);
169179

170-
if (!addon.checker(html, from, cm)) return null; // security check
180+
if (!addon.checker(html)) return null; // security check
171181

172182
// security check pass!
173183

@@ -204,7 +214,7 @@ export const defaultOption: Options = {
204214
checker: defaultChecker,
205215
renderer: defaultRenderer,
206216
stubText: "<HTML>",
207-
isolatedTagName: /^(?:div|pre|form|table|iframe|ul|ol|input|textarea|p|summary|a)$/i
217+
isolatedTagName: /^(?:div|pre|form|table|iframe|ul|ol|input|textarea|p|summary|a)$/i,
208218
};
209219

210220
export const suggestedOption: Partial<Options> = {};
@@ -228,7 +238,7 @@ declare global {
228238

229239
suggestedEditorConfig.hmdFoldHTML = suggestedOption;
230240

231-
CodeMirror.defineOption("hmdFoldHTML", defaultOption, function(
241+
CodeMirror.defineOption("hmdFoldHTML", defaultOption, function (
232242
cm: cm_t,
233243
newVal: OptionValueType
234244
) {
@@ -286,7 +296,6 @@ export class FoldHTML implements Addon.Addon, Options {
286296
inlineMode?: boolean
287297
): CodeMirror.TextMarker {
288298
const cm = this.cm;
289-
290299
var stub = this.makeStub();
291300
var el = this.renderer(html, from, cm);
292301
var breakFn = () => breakMark(cm, marker);
@@ -313,7 +322,7 @@ export class FoldHTML implements Addon.Addon, Options {
313322
/** If element size changed, we notify CodeMirror */
314323
var watcher = watchSize(el, (w, h) => {
315324
const computedStyle = getComputedStyle(el);
316-
const getStyle = name => computedStyle.getPropertyValue(name);
325+
const getStyle = (name) => computedStyle.getPropertyValue(name);
317326

318327
var floating =
319328
w < 10 ||
@@ -343,7 +352,7 @@ export class FoldHTML implements Addon.Addon, Options {
343352
above: false,
344353
coverGutter: false,
345354
noHScroll: false,
346-
showIfHidden: false
355+
showIfHidden: false,
347356
});
348357

349358
let highlightON = () => (stub.className = stubClassHighlight);
@@ -367,7 +376,7 @@ export class FoldHTML implements Addon.Addon, Options {
367376
}
368377

369378
marker = cm.markText(from, to, {
370-
replacedWith
379+
replacedWith,
371380
});
372381

373382
return marker;

src/preview/features/widget.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import MarkdownIt from "markdown-it";
22
import { Attributes } from "../../addon/fold";
3+
import { defaultChecker } from "../../addon/fold-html";
34

45
export default (md: MarkdownIt) => {
56
const renderWidget = (content: string) => {
@@ -44,7 +45,12 @@ export default (md: MarkdownIt) => {
4445
if (content.match(/^<!--\s*@/) && content.match(/-->$/)) {
4546
return renderWidget(content);
4647
} else {
47-
return oldHTMLInlineRenderer(tokens, idx, options, env, slf);
48+
const html = oldHTMLInlineRenderer(tokens, idx, options, env, slf);
49+
if (defaultChecker(html)) {
50+
return html;
51+
} else {
52+
return "";
53+
}
4854
}
4955
};
5056

@@ -54,7 +60,12 @@ export default (md: MarkdownIt) => {
5460
if (content.match(/^<!--\s*@/) && content.match(/-->$/)) {
5561
return renderWidget(content);
5662
} else {
57-
return oldHTMLBlockRenderer(tokens, idx, options, env, slf);
63+
const html = oldHTMLBlockRenderer(tokens, idx, options, env, slf);
64+
if (defaultChecker(html)) {
65+
return html;
66+
} else {
67+
return "";
68+
}
5869
}
5970
};
6071
};

src/widget/hello/hello.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@
33
//
44
// DESCRIPTION: This widget tries to build a widget using react
55

6-
import { Attributes } from "../../addon/fold";
76
import React, { useState } from "react";
87
import ReactDOM from "react-dom";
9-
import { Widget } from "../component/widget";
108
import { WidgetCreator, WidgetArgs } from "..";
119

1210
function Hello(props: WidgetArgs) {
@@ -69,11 +67,6 @@ function Hello(props: WidgetArgs) {
6967

7068
export const HelloWidget: WidgetCreator = (args) => {
7169
const el = document.createElement("span");
72-
ReactDOM.render(
73-
<Widget>
74-
<Hello {...args}></Hello>
75-
</Widget>,
76-
el
77-
);
70+
ReactDOM.render(<Hello {...args}></Hello>, el);
7871
return el;
7972
};

0 commit comments

Comments
 (0)