zfile-dev / zfile-dev/zfile

[Security] ZFile Markdown preview stored XSS vulnerability

Open
#827 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Java
Stars
10.8k
Forks
2k
PR merge metrics
No merged PRs in 30d

Description

Summary

The Markdown preview feature in ZFile 4.5.0 passes the contents of .md files to marked for conversion into HTML, and then directly inserts the result into the page through v-html / innerHTML. Because no HTML security sanitization is performed before rendering, an attacker can upload a Markdown file containing event handlers, causing JavaScript to execute when other users preview the file. This issue has been reproduced in a local Docker environment.

Details

The vulnerability trigger chain is as follows:

  1. A user writes a .md file through the upload API, and the file content is saved as-is:
    • src/main/java/im/zhaojun/zfile/module/storage/controller/proxy/ProxyUploadController.java:26-47
    • src/main/java/im/zhaojun/zfile/module/storage/service/impl/LocalServiceImpl.java:151-166
  2. The file list API returns file items and their download URLs. When the frontend previews a Markdown file, it reads the raw text from that URL:
    • src/main/java/im/zhaojun/zfile/module/storage/controller/file/FileController.java:70-95
    • src/main/java/im/zhaojun/zfile/module/storage/service/impl/LocalServiceImpl.java:270-280
  3. The frontend Markdown preview component passes the file content to marked for conversion into HTML, and directly writes the result into innerHTML:
    • codebase/zfile-release/4.5.0/extracted/static/assets/MarkdownViewer-a089cbe3.js:1

The equivalent key logic is as follows:

getFileTextReq(fileUrl, "text").then(res => {
  fileContent.value = res.data
})

markdownHtml = marked(fileContent.value, {
  highlight: ...
})

<div class="dialog-scroll markdown-body" v-html="markdownHtml"></div>

In the built artifacts, the default configuration of marked contains sanitize:false, and the component does not call DOMPurify or an equivalent sanitizer between marked(...) and innerHTML. Therefore, the following Markdown content will be preserved as a real <img> tag. After src=x fails to load, the onerror handler is triggered, executing the JavaScript inside it:

<img src=x onerror="alert('zfile_xss')">
PoC
BASE=http://localhost:35731
COOKIE=/tmp/zfile_xss.cookie
POC=/tmp/zfile_xss_poc.md

cat > "$POC" <<'EOF'
<img src=x onerror="alert('zfile_xss')">
EOF

curl -sS -c "$COOKIE" \
  -H 'Content-Type: application/json' \
  --data '{"username":"admin","password":"zfile123"}' \
  "$BASE/user/login"

curl -sS -b "$COOKIE" -X PUT \
  -F "file=@$POC;type=text/markdown" \
  "$BASE/file/upload/local/tmp_zfile_dir_xss?filename=zfile_xss_poc.md"

curl -sS -b "$COOKIE" -X POST "$BASE/api/storage/files" \
  -H 'Content-Type: application/json' \
  --data '{"storageKey":"local","path":"/tmp_zfile_dir_xss/"}'

The upload and file list response results are shown below:

Image

Then open the browser:

  1. Visit http://localhost:35731/login and log in with admin / zfile123.
  2. Open http://localhost:35731/local/tmp_zfile_dir_xss.
  3. Double-click zfile_xss_poc.md.
  4. After the Markdown preview dialog opens, the browser displays an alert saying zfile_xss.
Image
Impact

This is a stored XSS vulnerability. An attacker who has upload permissions, or who can write files to a configured storage source, can place a malicious Markdown file. Any user who has permission to access and preview that file will execute the attacker’s script in the same-origin page of ZFile.

If an administrator previews the malicious file, the script can call same-origin APIs in the administrator’s browser context, perform actions within the administrator’s privileges, or read data accessible from the page. This verification confirmed that an alert can be triggered when a logged-in administrator opens the frontend Markdown file.

Suggested Fix

It is recommended to apply whitelist-based HTML sanitization to the output of marked in the Markdown preview chain before passing it to v-html, for example by using DOMPurify, and to explicitly remove event attributes such as onerror and onclick, as well as javascript: URLs and dangerous data: URLs.

A more robust approach is to disable raw Markdown HTML by default in file preview scenarios: customize the marked renderer/tokenizer to escape or discard HTML tokens, while retaining only basic Markdown formatting. Since ZFile is used for file browsing and previewing, there is usually no need to allow user-uploaded Markdown files to execute arbitrary HTML.

Additionally, do not rely only on filtering at the upload API, because files may also come from existing local directories, mounted storage, or third-party storage sources. The fix should be placed at a unified output-processing point before preview rendering.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the upload and file-reading paths in ProxyUploadController.java, LocalServiceImpl.java, and FileController.java, then inspect MarkdownViewer-a089cbe3.js for the marked-to-innerHTML flow. Verify the behavior with the provided Markdown PoC and browser steps; done means previewing that file no longer executes its event handler while normal Markdown rendering remains available.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, javascript
Domain
backend, frontend, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.