What You See IS NOT What You Get: Pwning Electron-based Markdown Note-taking Apps

Li Jiantao (Security Researcher · STAR Labs)

HITCON Community 2023 (Hacks in Taiwan Conference) · Day 2

Overview

In this compelling talk at HITCON CMT, Li Jiantao, a distinguished security researcher from STAR Labs, unveils a series of critical vulnerabilities discovered in popular Electron-based markdown note-taking applications: Obsidian, Typora, and Mark Text. The presentation, titled "What You See IS NOT What You Get," powerfully illustrates how seemingly benign actions—such as opening a markdown file or even copying text from a webpage—can be weaponized to achieve local file disclosure (LFD) and, in some cases, remote code execution (RCE) on a user's machine.

Watch on YouTube

Visual summary for What You See IS NOT What You Get: Pwning Electron-based Markdown Note-taking Apps by Li Jiantao
Visual summary for What You See IS NOT What You Get: Pwning Electron-based Markdown Note-taking Apps by Li Jiantao

Key moments

  1. 4:00 Why Electron apps are vulnerable to RCE
  2. 7:15 Obsidian local file disclosure vulnerability
  3. 12:00 Typora's incomplete fix bypassed by path traversal
  4. 16:20 Demo: Typora XSS escalated to remote code execution
  5. 17:00 Understanding the copy-paste XSS attack vector
  6. 20:00 Hiding payloads with invisible markdown links

What You See IS NOT What You Get: Pwning Electron-based Markdown Note-taking Apps

Speakers: Li Jiantao, Security Researcher, STAR Labs

Conference: HITCON CMT

YouTube: https://www.youtube.com/watch?v=upaf04C0FdQ

Overview

In this compelling talk at HITCON CMT, Li Jiantao, a distinguished security researcher from STAR Labs, unveils a series of critical vulnerabilities discovered in popular Electron-based markdown note-taking applications: Obsidian, Typora, and Mark Text. The presentation, titled "What You See IS NOT What You Get," powerfully illustrates how seemingly benign actions—such as opening a markdown file or even copying text from a webpage—can be weaponized to achieve local file disclosure (LFD) and, in some cases, remote code execution (RCE) on a user's machine.

Jiantao's research delves into the intricacies of how these applications handle raw HTML content, custom URL protocols, and clipboard data, exposing flaws that bypass established security mechanisms like DOMPurify. The core message challenges the fundamental assumption of WYSIWYG (What You See Is What You Get) editors, demonstrating that malicious payloads can be covertly embedded and rendered, deceiving users and leading to severe system compromises. This talk serves as a vital wake-up call for both developers of Electron applications and their users, highlighting the persistent and evolving threat landscape in desktop applications built on web technologies.

Background

▶ Watch: Why Electron apps are vulnerable to RCE (4:00)

Markdown, a lightweight plaintext formatting syntax created in 2004, has become ubiquitous for its simplicity, readability, and portability. It is widely used for documentation, bug reports, and general note-taking. While markdown files can be edited in any basic text editor, their true utility shines when rendered into structured documents. Markdown editors commonly offer two rendering modes: a split-screen approach (like VS Code), displaying raw markdown alongside its rendered output, and the immersive WYSIWYG mode, where content is rendered in real-time as it's typed.

A key feature contributing to markdown's flexibility is its ability to incorporate raw HTML code. This allows users to extend markdown's capabilities, embedding elements like <a> tags for links, <img> tags for images, or even more complex structures. However, this flexibility introduces a significant security caveat: if applications fail to adequately sanitize raw HTML inputs, they become susceptible to Cross-Site Scripting (XSS) attacks, where malicious JavaScript can be injected and executed within the application's context.

The focus of Jiantao's research is specifically on applications built using the Electron framework. Electron combines Chromium (for rendering the user interface) with Node.js (for providing access to system-level APIs). This unique architecture allows web technologies (HTML, CSS, JavaScript) to be used for desktop application development. Crucially, if an XSS vulnerability is found in an Electron application with Node.js integration enabled, it can often be escalated to RCE. This is because the executed JavaScript, running in the renderer process, gains access to Node.js APIs that can interact with the file system, create new processes, and perform other privileged operations, effectively turning a web vulnerability into a desktop system compromise. This attack path—malicious markdown leading to raw HTML rendering, triggering XSS, abusing Electron APIs, and culminating in RCE—forms the bedrock of the vulnerabilities explored in the talk.

Key Findings

▶ Watch: Typora's incomplete fix bypassed by path traversal (12:00)

Li Jiantao's research uncovered a series of significant vulnerabilities across three popular Electron-based markdown editors, demonstrating various attack vectors from local file disclosure to remote code execution.

  1. Obsidian Local File Disclosure (LFD):
  • Vulnerability: Obsidian's custom app:// protocol, used for loading local resources, was susceptible to a path traversal vulnerability. An iframe loaded from an attacker's domain could bypass Obsidian's referrer check and Content Security Policy (CSP) due to the protocol being registered as privileged and a change in Chrome's default referrer behavior. This allowed the iframe to make XMLHttpRequest requests to app://local/ paths, disclosing arbitrary local files.
  • Impact: Attackers could read sensitive user files from the local filesystem. This was reported and fixed within a week.
  1. Typora Local File Disclosure (LFD) and Bypass:
  • Vulnerability (Initial): Similar to Obsidian, Typora also registered a custom protocol (typora-app://). Its getRealPath function, responsible for resolving paths, incorrectly treated segments after typora-app:// as absolute paths, enabling local file disclosure via an embedded iframe.
  • Initial Fix: Typora attempted to patch this by requiring paths to start with user data or type mark.
  • Bypass: Jiantao discovered that a path traversal payload (e.g., typora-app://typemark/../../../../) could bypass this fix, allowing an attacker to navigate outside the intended typemark or user data directories and still read arbitrary files from the root of the C drive. This incomplete fix necessitates a new CVE.
  1. Typora XSS to Remote Code Execution (RCE):
  • Vulnerability: Typora was found to be using an outdated version of DOMPurify and, critically, allowed the embed tag to be rendered directly without conversion to a secure webview. An XSS vulnerability was identified in update.html (a file loaded from Typora's custom protocol) where user-controlled strings from location.search were unsafely written into the innerHTML of DOM elements.
  • Impact: By embedding the vulnerable update.html with an XSS payload in an embed tag, and leveraging Typora's nodeIntegration: true setting and exposed require() function, an attacker could achieve full RCE on the user's system.
  1. Mark Text Copy-Paste XSS to RCE:
  • Vulnerability: Mark Text, despite using DOMPurify and prohibiting iframe/embed tags, was vulnerable to a copy-paste XSS. An attacker could craft a malicious webpage that, upon a user copying text, would inject an XSS payload into the clipboard using clipboardData.setData(). When this content was pasted into Mark Text, its hyperlink handling logic failed to sanitize the text content of <a> tags if getPageTitle (used to fetch titles from URLs) failed.
  • Impact: This allowed an XSS payload to be written to innerHTML, and combined with nodeIntegration, led to RCE. The talk also demonstrated how to make these malicious links invisible in WYSIWYG mode using special Unicode characters or whitespace within the link's title, enhancing stealth.

These findings underscore the complex security challenges in Electron applications, where the interplay of web technologies, system-level access, and application-specific logic can create unexpected and severe vulnerabilities.

Technical Deep Dive

▶ Watch: Demo: Typora XSS escalated to remote code execution (16:20)

The technical depth of Jiantao's research highlights specific implementation flaws that led to these critical vulnerabilities.

Obsidian Local File Disclosure (LFD)

Obsidian, while employing DOMPurify for HTML sanitization (though not the latest version, no direct bypasses for its configuration were found), crucially allowed the iframe tag. This permitted an attacker to embed an iframe pointing to a malicious web page under their control.

The key to the vulnerability lay in Obsidian's custom app:// protocol, which it used to load local resources, rendering paths like app://local/path/to/image.png. Jiantao discovered that an XMLHttpRequest (XHR) from within the attacker-controlled iframe could successfully request app://local/ URLs, even though the fetch API was blocked for this scheme.

Further investigation into Obsidian's source code revealed that the app protocol was registered via Electron's protocol.registerPrivileged API. This designation is critical, as it allows requests to the app protocol to bypass the Content Security Policy (CSP) for resources and enables requests from HTTP or HTTPS origins. The protocol's handler function contained two routing rules:

  1. One for loading static files, which included checks for path traversal and was deemed safe.
  2. A second, problematic rule for app://local/, which treated anything after this prefix as an absolute path.

Developers had attempted to secure this by checking the Referer header to prevent cross-origin iframes from reading local files. However, this check failed because Chrome has changed its default behavior to not send a Referer header for cross-origin requests, leaving the field empty and the check ineffective. Compounding this, Electron conveniently adds a header for requests to file protocols, effectively bypassing Cross-Origin Resource Sharing (CORS) policies implemented by Chromium. This combination of factors allowed an attacker to read arbitrary local files from their embedded iframe.

Typora Local File Disclosure (LFD) and Bypass

Typora exhibited a similar custom protocol vulnerability, where its typora-app:// scheme was used to serve local resources. The getRealPath function, responsible for resolving these URLs, would decode the URL string, remove the first 13 characters (typora-app://), perform some regular expression-based match and replace operations, and then treat the remaining string as a path. The initial vulnerability was that any string after the typora-app:// prefix that didn't match a specific regex would be appended unmodified, allowing for direct absolute path access.

Typora's initial patch attempted to mitigate this by enforcing that the path after typora-app:// must start with either user data or type mark. These prefixes would then be replaced with their respective absolute paths. While this blocked Jiantao's original proof of concept, he quickly identified a bypass using path traversal techniques. By crafting a URL like typora-app://typemark/../../../../Windows/win.ini, an attacker could prepend typemark/ to satisfy the new rule, then use ../../ sequences to navigate upwards from the typemark base directory to the root of the C drive, and then access any desired file. This demonstrated an incomplete fix, leading to a new, distinct vulnerability requiring a separate CVE ID.

Typora XSS to Remote Code Execution (RCE)

The escalation to RCE in Typora stemmed from a combination of factors: an outdated DOMPurify version (released almost three and a half years prior), and a critical misconfiguration regarding the embed tag. Unlike iframe tags, which were converted to secure webview elements in Typora to isolate their content, embed tags were rendered as-is. This meant any content loaded via an embed tag ran within the same origin and context as the top frame of the application.

Jiantao then searched for an XSS vulnerability within Typora's own application files that were loaded via its custom protocol. He identified update.html, which was loaded from typora-app://. This HTML file contained vulnerable code where a variable named labels, extracted directly from location.search (e.g., ?labels=<XSS_PAYLOAD>), was unsafely assigned to the innerHTML property of several DOM elements. These were intended for localization strings like "skip the update" but were instead a direct XSS vector.

By embedding an embed tag with a specially crafted src attribute—for example, embed src="typora-app://update.html?labels=<img src=x onerror=alert(1)>"—an attacker could trigger XSS in the top frame. Given that Typora had nodeIntegration set to true and exposed the require() function, this XSS could be immediately escalated to RCE using Node.js APIs, such as require('child_process').exec('calc.exe'), to execute arbitrary system commands.

Mark Text Copy-Paste XSS to RCE

Mark Text presented a different, yet equally potent, attack vector: copy-paste XSS. This technique, popularized by research from Maciej B. Bąkowski, exploits the ability of a malicious webpage to overwrite the user's clipboard content using clipboardData.setData() when a copy event occurs. A user might believe they've copied benign text, but the clipboard secretly holds an exploit.

Mark Text, despite using DOMPurify and prohibiting iframe and embed tags, was vulnerable in its handling of pasted data. Jiantao discovered a flaw in the code that processes hyperlinks (<a> tags) in pasted content. This code iterates through <a> tags, attempts to fetch their titles using a getPageTitle function based on their href attribute, and then writes the title back to the innerHTML of a DOM element. The critical vulnerability was that if getPageTitle failed (e.g., due to an unresolvable URL like http://0.0), the original text content of the <a> tag was written back to innerHTML without sanitization.

An attacker could craft a payload like [<img src=x onerror=alert(1)>](http://0.0/#<img src=x onerror=alert(1)>) on a malicious webpage. When copied and pasted into Mark Text, the getPageTitle function would fail for http://0.0, causing the XSS payload within the <a> tag's text content to be injected directly into the DOM, leading to XSS. With Node.js integration, this again escalated to RCE.

To make this attack even more stealthy in WYSIWYG editors, Jiantao demonstrated how to hide the malicious link. By placing a whitespace or a zero-width Unicode character within the square brackets of a markdown link (e.g., [ ](malicious-link)), the link becomes effectively invisible in the rendered output, making it extremely difficult for a user to detect the presence of the payload.

Demo / Proof of Concept

▶ Watch: Understanding the copy-paste XSS attack vector (17:00)

Li Jiantao provided clear demonstrations for each class of vulnerability, concretely illustrating their impact.

For the Obsidian and Typora local file disclosure vulnerabilities, the proof of concept involved opening a specially crafted markdown file within the respective application. This file contained an embedded iframe that, upon loading, would trigger an XMLHttpRequest to a local file path (e.g., app://local/Windows/win.ini or typora-app://typemark/../../../../Windows/win.ini). The demonstration showed a popup displaying the content of the targeted local file (e.g., window.II or win.ini), confirming the unauthorized data access. Jiantao emphasized that in a real-world attack, this content would be silently exfiltrated to a remote attacker-controlled server rather than displayed to the user.

The Typora XSS to RCE demonstration was particularly impactful. After opening a malicious markdown file containing the embed tag pointing to the vulnerable update.html with an XSS payload, the system's calculator application (calc.exe) immediately popped up. This visually confirmed the successful remote code execution, showcasing the direct escalation from an XSS vulnerability to full system compromise due to Node.js integration.

Finally, the Mark Text copy-paste XSS to RCE was demonstrated by having the audience imagine copying text from a malicious webpage. When this manipulated clipboard content was pasted into Mark Text, the calculator again popped up, proving the RCE. The demonstration also included a crucial visual element: switching Mark Text to source code mode after pasting. This revealed the presence of an "invisible link" at the very beginning of the pasted text, containing the hidden XSS payload, highlighting how WYSIWYG editors can deceptively conceal malicious content through CSS or Unicode characters.

Defensive Implications

▶ Watch: Hiding payloads with invisible markdown links (20:00)

The vulnerabilities uncovered in this research carry significant implications for both developers of Electron applications and their users. Robust defensive strategies are paramount to mitigate these types of sophisticated attacks.

For Developers of Electron Applications:

  1. Rigorous Input Sanitization:
  • Beyond DOMPurify: While libraries like DOMPurify are essential, they are not a silver bullet. Developers must understand that XSS can manifest in other areas, such as paste handlers or custom protocol handlers.
  • Contextual Sanitization: Always use innerText or textContent instead of innerHTML when dealing with user-controlled strings that are not intended to be parsed as HTML.
  • Paste Event Handling: Implement strict sanitization for all content pasted into the application, as clipboard data can be maliciously manipulated.
  1. Secure Custom Protocol Implementation:
  • Path Validation: Treat all paths provided via custom protocols with extreme suspicion. Implement robust path sanitization and canonicalization to prevent path traversal (../) and ensure resources are loaded only from intended, restricted directories.
  • Origin Checks: Thoroughly validate the origin of requests to custom protocols. Do not rely solely on referrer headers, as these can be absent or spoofed.
  • Privileged Protocols: Use protocol.registerPrivileged sparingly and only when absolutely necessary, understanding that it bypasses critical security mechanisms like CSP.
  1. Electron Security Best Practices:
  • Disable nodeIntegration: If your application does not require Node.js APIs in the renderer process, disable nodeIntegration entirely. This is the most effective way to prevent XSS from escalating to RCE.
  • Context Isolation: If nodeIntegration is necessary, enable context isolation to prevent the renderer process from directly accessing Node.js APIs or modifying Node.js globals. Expose only necessary APIs through a secure preload script.
  • Update Electron and Dependencies: Regularly update the Electron framework and all third-party libraries (like DOMPurify) to patch known vulnerabilities.
  1. Careful with HTML Tags:
  • iframe vs. webview vs. embed: Understand the security implications of each. webview offers better isolation for external content than iframe. Avoid embed tags for untrusted content, especially if they run in the top frame's context.
  1. Comprehensive Patching and Testing:
  • Bypass Awareness: When fixing vulnerabilities, consider all possible bypasses (e.g., path traversal, alternative encoding).
  • Researcher Engagement: Engage with security researchers for re-testing fixes to ensure they are complete and robust.

For Users of Markdown Editors and Electron Apps:

  1. Be Wary of Copied Content: Do not blindly paste content copied from untrusted websites into any application, especially markdown editors or rich text editors. Assume clipboard data can be malicious.
  2. Keep Applications Updated: Always update your markdown editors and other Electron-based applications to their latest versions. Security patches are crucial for protection against newly discovered vulnerabilities.
  3. Question WYSIWYG: Understand that "What You See Is Not What You Get" can be a reality. Malicious links or payloads can be hidden using CSS or special Unicode characters, making it difficult to detect them visually. If something feels off, inspect the source or be cautious.

Key Takeaways

  • DOMPurify is Not a Silver Bullet: Even well-known HTML sanitization libraries like DOMPurify cannot guarantee complete security if other parts of the application, such as paste handlers or custom protocol implementations, introduce new vulnerabilities.
  • Copy-Paste is an Attack Vector: The clipboard can be weaponized. Malicious actors can manipulate clipboard content using clipboardData.setData(), turning a seemingly innocuous copy-paste action into a remote code execution event.
  • WYSIWYG Deception: What you see in a rendered markdown editor might not be what's truly there. Malicious payloads can be discreetly hidden using CSS styling or special Unicode characters, making attacks stealthy and difficult to detect visually.
  • Electron XSS to RCE is a Persistent Threat: Electron applications with nodeIntegration enabled remain highly susceptible to XSS vulnerabilities escalating to full remote code execution if proper security measures and context isolation are not meticulously implemented.
  • Custom Protocols Require Extreme Scrutiny: The implementation of custom URL protocols is a common source of local file disclosure and other vulnerabilities if not handled with robust path validation, origin checks, and an understanding of Electron's privileged protocol implications.

About the Speaker(s)

Li Jiantao is a distinguished Security Researcher at STAR Labs, a Singapore-based security company. His expertise and prowess in identifying and exploiting critical vulnerabilities are evident through his impressive track record. He was part of the team that won Pwn2Own Vancouver in the previous year and secured second place in the most recent competition. Beyond professional engagements, Jiantao is also an active participant in the competitive hacking scene, playing CTF (Capture The Flag) with team r3kapig and having made it to the DEF CON CTF finals six years in a row. His presentation at HITCON CMT reflects his deep understanding of modern application security and his commitment to uncovering and addressing complex vulnerabilities.

Reviews

Dr. Zero (Offensive Security Researcher) — STRONG ACCEPT

Solid, practical vuln research on Electron markdown apps with three distinct chains from markdown/paste to LFD and RCE. Not groundbreaking technique-wise—Electron+nodeIntegration RCE is a well-worn path—but the copy-paste XSS vector and the invisible-link stealth trick are nice touches that make this more than a retread.

Heather Calloway (CISO) — SOLID

Solid offensive research on a class of desktop applications many organizations tolerate in their environment. The strategic takeaway is narrow but real: Electron apps with Node.js integration are a persistent RCE vector, and your endpoint security posture should account for them.

→ Top-rated talks at HITCON Community 2023 (Hacks in Taiwan Conference)

All talks from HITCON Community 2023 (Hacks in Taiwan Conference)