Skip to content

[Security] ZFile Markdown preview stored XSS vulnerability #827

Description

@insung186

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions