CVE-2022-4839: 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: 5.4
Published: 2022-12-29T17:15Z
Repository: https://github.com/usememos/memos
Language: TypeScript
Files changed: web/src/labs/marked/index.ts, 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, web/src/labs/marked/parser/Strikethrough.ts
Commit date: 2022-12-29 23:27:56 +0800
Commit message: chore: fix XSS in renderer (#875)

chore: fix xss in renderer
Diff: diff --git a/web/src/labs/marked/index.ts b/web/src/labs/marked/index.ts
index 436b993..0d3d083 100644
--- a/web/src/labs/marked/index.ts
+++ b/web/src/labs/marked/index.ts
@@ -45,11 +45,11 @@ export const marked = (markdownStr: string, blockParsers = blockElementParserLis
     if (matchResult) {
       const matchedStr = matchResult[0];
       const matchedLength = matchedStr.length;
       const prefixStr = markdownStr.slice(0, matchedIndex);
       const suffixStr = markdownStr.slice(matchedIndex + matchedLength);
-      return prefixStr + matchedInlineParser.renderer(matchedStr) + marked(suffixStr, [], inlineParsers);
+      return marked(prefixStr, [], inlineParsers) + matchedInlineParser.renderer(matchedStr) + marked(suffixStr, [], inlineParsers);
     }
   }
 
   return markdownStr;
 };
diff --git a/web/src/labs/marked/parser/Bold.ts b/web/src/labs/marked/parser/Bold.ts
index 041640f..578a6ed 100644
--- a/web/src/labs/marked/parser/Bold.ts
+++ b/web/src/labs/marked/parser/Bold.ts
@@ -1,6 +1,5 @@
-import { escape } from "lodash";
 import { marked } from "..";
 import Link from "./Link";
 
 export const BOLD_REG = /\*\*(.+?)\*\*/;
 
@@ -13,11 +12,11 @@ const renderer = (rawStr: string): string => {
   const matchResult = matcher(rawStr);
   if (!matchResult) {
     return rawStr;
   }
 
-  const parsedContent = marked(escape(matchResult[1]), [], [Link]);
+  const parsedContent = marked(matchResult[1], [], [Link]);
   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 f434da5..0fdf37e 100644
--- a/web/src/labs/marked/parser/BoldEmphasis.ts
+++ b/web/src/labs/marked/parser/BoldEmphasis.ts
@@ -1,6 +1,5 @@
-import { escape } from "lodash";
 import { marked } from "..";
 import Link from "./Link";
 
 export const BOLD_EMPHASIS_REG = /\*\*\*(.+?)\*\*\*/;
 
@@ -13,11 +12,11 @@ const renderer = (rawStr: string): string => {
   const matchResult = matcher(rawStr);
   if (!matchResult) {
     return rawStr;
   }
 
-  const parsedContent = marked(escape(matchResult[1]), [], [Link]);
+  const parsedContent = marked(matchResult[1], [], [Link]);
   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 7dd12df..e339bf3 100644
--- a/web/src/labs/marked/parser/Emphasis.ts
+++ b/web/src/labs/marked/parser/Emphasis.ts
@@ -1,6 +1,5 @@
-import { escape } from "lodash";
 import { marked } from "..";
 import Link from "./Link";
 
 export const EMPHASIS_REG = /\*(.+?)\*/;
 
@@ -13,11 +12,11 @@ const renderer = (rawStr: string): string => {
   const matchResult = matcher(rawStr);
   if (!matchResult) {
     return rawStr;
   }
 
-  const parsedContent = marked(escape(matchResult[1]), [], [Link]);
+  const parsedContent = marked(matchResult[1], [], [Link]);
   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 6bfc0f8..7b9ac4f 100644
--- a/web/src/labs/marked/parser/Link.ts
+++ b/web/src/labs/marked/parser/Link.ts
@@ -15,11 +15,11 @@ const matcher = (rawStr: string) => {
 const renderer = (rawStr: string): string => {
   const matchResult = matcher(rawStr);
   if (!matchResult) {
     return rawStr;
   }
-  const parsedContent = marked(escape(matchResult[1]), [], [InlineCode, BoldEmphasis, Emphasis, Bold]);
+  const parsedContent = marked(matchResult[1], [], [InlineCode, BoldEmphasis, Emphasis, Bold]);
   return `<a class='link' target='_blank' rel='noreferrer' href='${escape(matchResult[2])}'>${parsedContent}</a>`;
 };
 
 export default {
   name: "link",
diff --git a/web/src/labs/marked/parser/Strikethrough.ts b/web/src/labs/marked/parser/Strikethrough.ts
index b58c922..7dc1897 100644
--- a/web/src/labs/marked/parser/Strikethrough.ts
+++ b/web/src/labs/marked/parser/Strikethrough.ts
@@ -1,6 +1,6 @@
-import { marked } from "..";
+import { escape } from "lodash";
 
 export const STRIKETHROUGH_REG = /~~(.+?)~~/;
 
 const matcher = (rawStr: string) => {
   const matchResult = rawStr.match(STRIKETHROUGH_REG);
@@ -11,12 +11,11 @@ const renderer = (rawStr: string): string => {
   const matchResult = matcher(rawStr);
   if (!matchResult) {
     return rawStr;
   }
 
-  const parsedContent = marked(matchResult[1], [], []);
-  return `<del>${parsedContent}</del>`;
+  return `<del>${escape(matchResult[1])}</del>`;
 };
 
 export default {
   name: "Strikethrough",
   regex: STRIKETHROUGH_REG,

Vulnerable code:       return prefixStr + matchedInlineParser.renderer(matchedStr) + marked(suffixStr, [], inlineParsers);
import { escape } from "lodash";
  const parsedContent = marked(escape(matchResult[1]), [], [Link]);
import { escape } from "lodash";
  const parsedContent = marked(escape(matchResult[1]), [], [Link]);
import { escape } from "lodash";
  const parsedContent = marked(escape(matchResult[1]), [], [Link]);
  const parsedContent = marked(escape(matchResult[1]), [], [InlineCode, BoldEmphasis, Emphasis, Bold]);
import { marked } from "..";
  const parsedContent = marked(matchResult[1], [], []);
  return `<del>${parsedContent}</del>`;
Fixed code:       return marked(prefixStr, [], inlineParsers) + matchedInlineParser.renderer(matchedStr) + marked(suffixStr, [], inlineParsers);
  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]);
import { escape } from "lodash";
  return `<del>${escape(matchResult[1])}</del>`;