CVE-2022-4865: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Cross-site Scripting (XSS) - Stored in GitHub repository usememos/memos prior to 0.9.1.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 9
Published: 2022-12-31T09:15Z
Repository: https://github.com/usememos/memos
Language: TypeScript
Files changed: web/src/labs/marked/parser/Bold.ts, web/src/labs/marked/parser/BoldEmphasis.ts, web/src/labs/marked/parser/Emphasis.ts, web/src/labs/marked/parser/Link.ts
Commit date: 2022-12-31 11:52:57 +0800
Commit message: chore: fix XSS in renderer (#880)


Diff: diff --git a/web/src/labs/marked/parser/Bold.ts b/web/src/labs/marked/parser/Bold.ts
index 578a6ed..af8b875 100644
--- a/web/src/labs/marked/parser/Bold.ts
+++ b/web/src/labs/marked/parser/Bold.ts
@@ -1,7 +1,8 @@
 import { marked } from "..";
 import Link from "./Link";
+import PlainText from "./PlainText";
 
 export const BOLD_REG = /\*\*(.+?)\*\*/;
 
 const matcher = (rawStr: string) => {
   const matchResult = rawStr.match(BOLD_REG);
@@ -12,11 +13,11 @@ const renderer = (rawStr: string): string => {
   const matchResult = matcher(rawStr);
   if (!matchResult) {
     return rawStr;
   }
 
-  const parsedContent = marked(matchResult[1], [], [Link]);
+  const parsedContent = marked(matchResult[1], [], [Link, PlainText]);
   return `<strong>${parsedContent}</strong>`;
 };
 
 export default {
   name: "bold",
diff --git a/web/src/labs/marked/parser/BoldEmphasis.ts b/web/src/labs/marked/parser/BoldEmphasis.ts
index 0fdf37e..450589b 100644
--- a/web/src/labs/marked/parser/BoldEmphasis.ts
+++ b/web/src/labs/marked/parser/BoldEmphasis.ts
@@ -1,7 +1,8 @@
 import { marked } from "..";
 import Link from "./Link";
+import PlainText from "./PlainText";
 
 export const BOLD_EMPHASIS_REG = /\*\*\*(.+?)\*\*\*/;
 
 const matcher = (rawStr: string) => {
   const matchResult = rawStr.match(BOLD_EMPHASIS_REG);
@@ -12,11 +13,11 @@ const renderer = (rawStr: string): string => {
   const matchResult = matcher(rawStr);
   if (!matchResult) {
     return rawStr;
   }
 
-  const parsedContent = marked(matchResult[1], [], [Link]);
+  const parsedContent = marked(matchResult[1], [], [Link, PlainText]);
   return `<strong><em>${parsedContent}</em></strong>`;
 };
 
 export default {
   name: "bold emphasis",
diff --git a/web/src/labs/marked/parser/Emphasis.ts b/web/src/labs/marked/parser/Emphasis.ts
index e339bf3..b8f9126 100644
--- a/web/src/labs/marked/parser/Emphasis.ts
+++ b/web/src/labs/marked/parser/Emphasis.ts
@@ -1,7 +1,8 @@
 import { marked } from "..";
 import Link from "./Link";
+import PlainText from "./PlainText";
 
 export const EMPHASIS_REG = /\*(.+?)\*/;
 
 const matcher = (rawStr: string) => {
   const matchResult = rawStr.match(EMPHASIS_REG);
@@ -12,11 +13,11 @@ const renderer = (rawStr: string): string => {
   const matchResult = matcher(rawStr);
   if (!matchResult) {
     return rawStr;
   }
 
-  const parsedContent = marked(matchResult[1], [], [Link]);
+  const parsedContent = marked(matchResult[1], [], [Link, PlainText]);
   return `<em>${parsedContent}</em>`;
 };
 
 export default {
   name: "emphasis",
diff --git a/web/src/labs/marked/parser/Link.ts b/web/src/labs/marked/parser/Link.ts
index 7b9ac4f..89c4ae6 100644
--- a/web/src/labs/marked/parser/Link.ts
+++ b/web/src/labs/marked/parser/Link.ts
@@ -2,10 +2,11 @@ import { escape } from "lodash-es";
 import Emphasis from "./Emphasis";
 import Bold from "./Bold";
 import { marked } from "..";
 import InlineCode from "./InlineCode";
 import BoldEmphasis from "./BoldEmphasis";
+import PlainText from "./PlainText";
 
 export const LINK_REG = /\[(.*?)\]\((.+?)\)+/;
 
 const matcher = (rawStr: string) => {
   const matchResult = rawStr.match(LINK_REG);
@@ -15,11 +16,11 @@ const matcher = (rawStr: string) => {
 const renderer = (rawStr: string): string => {
   const matchResult = matcher(rawStr);
   if (!matchResult) {
     return rawStr;
   }
-  const parsedContent = marked(matchResult[1], [], [InlineCode, BoldEmphasis, Emphasis, Bold]);
+  const parsedContent = marked(matchResult[1], [], [InlineCode, BoldEmphasis, Emphasis, Bold, PlainText]);
   return `<a class='link' target='_blank' rel='noreferrer' href='${escape(matchResult[2])}'>${parsedContent}</a>`;
 };
 
 export default {
   name: "link",

Vulnerable code:   const parsedContent = marked(matchResult[1], [], [Link]);
  const parsedContent = marked(matchResult[1], [], [Link]);
  const parsedContent = marked(matchResult[1], [], [Link]);
  const parsedContent = marked(matchResult[1], [], [InlineCode, BoldEmphasis, Emphasis, Bold]);
Fixed code: import PlainText from "./PlainText";
  const parsedContent = marked(matchResult[1], [], [Link, PlainText]);
import PlainText from "./PlainText";
  const parsedContent = marked(matchResult[1], [], [Link, PlainText]);
import PlainText from "./PlainText";
  const parsedContent = marked(matchResult[1], [], [Link, PlainText]);
import PlainText from "./PlainText";
  const parsedContent = marked(matchResult[1], [], [InlineCode, BoldEmphasis, Emphasis, Bold, PlainText]);