← All write-ups
December 3, 2025HIGHCLIENT FINDING

Stored XSS through healthtech file upload validation

2025 · Client D · Healthtech & medical devices

Sanitized finding from Client D. The original upload endpoint, payload and identities are omitted.

Context

The application allowed authenticated users to upload files used in a document and analysis workflow. The browser later displayed uploaded content in a trusted application origin.

Finding

The validation layer relied on filename and extension checks that could be bypassed. An SVG carrying active markup was accepted and stored. When another user opened the resulting resource, the browser interpreted the content in the application context.

Technical workflow

The upload path was tested with a benign SVG fixture and content-type variations. The original active payload is intentionally omitted; the point of the retest is to prove the server-side policy, not to publish a copy-paste payload:

# Authorized test account and inert fixture only
curl --fail-with-body --max-time 10 \
  -H 'Authorization: Bearer TEST_TOKEN' \
  -F 'file=@fixtures/benign.svg;type=image/svg+xml' \
  'https://AUTHORIZED_HOST/api/upload'

A safe validation policy rejects active formats unless a dedicated sanitizer and origin are used:

INERT_MIME = {"image/png", "image/jpeg", "application/pdf"}

def accept_upload(*, detected_mime: str, extension: str) -> bool:
    if detected_mime not in INERT_MIME:
        return False
    return extension.lower() in {".png", ".jpg", ".jpeg", ".pdf"}

The server must derive detected_mime from the file bytes, normalize the filename, store outside the application origin and return Content-Disposition: attachment where inline rendering is not required. CSP is a backstop, not a substitute for rejecting active content.

Impact

The stored execution could target users with access to the same workspace, including privileged roles. Depending on browser protections and session design, the impact included unauthorized actions, data access and workflow manipulation.

Remediation

Validation

Re-test renamed active files, mismatched MIME types and SVG variants. Uploaded content must remain inert and isolated for every user role.

Expected result: rejected upload or inert download from an isolated origin, with no script execution in the application origin and no access to another user’s session context.