← Back to all reports

Stored XSS via SVG Upload on Bug Bounty Platform, Zero-Click Session Theft

Reported Apr 7, 2026
Severity Critical
Platform Web
Vulnerability Class Stored XSS via SVG (CWE-79, CWE-434)
Target Type Bug Bounty Platform
Impact Zero-click data exfiltration on report viewers

The Risk

A bug bounty platform's report editor allowed a researcher to upload a small image that secretly contained code. The moment anyone opened that report, including triage staff and program managers, the code ran in their browser and silently sent their email, login token, wallet addresses, and recent report activity to an outside server. No click, no warning, no visible change on the page. The platform's own upload form already blocked this kind of file, but the underlying server check was missing.

The Vulnerability

The internal attachments API on a bug bounty platform's dashboard accepted SVG file uploads and served them with the SVG content type, even though the upload UI explicitly blocked SVG with a "not a valid format" error. The restriction was implemented client-side only. Any attacker with a platform account could call the API directly, upload a JavaScript-bearing SVG, and have it hosted under the dashboard's own origin.

Because the file lived on the dashboard origin, JavaScript inside the SVG executed with full access to that origin. The report editor permitted <object> tags in report descriptions, which gave the attacker a direct embedding path: any logged-in user who opened the report would load the SVG and run the script.

Two delivery vectors

Report description (zero-click). Embedding <object data="/attachments/{id}" type="image/svg+xml"> in a submitted report caused the SVG to load and execute the moment anyone opened it. No click required. This automatically hit the submitting researcher, triage staff, and program managers.

Direct URL (one-click). The attachment URL could be shared on chat, email, or in report comments. Any logged-in user who opened the link triggered the script. No report context needed.

The Attack

The proof-of-concept SVG ran a small async function that built a list of internal API paths at runtime (to avoid trivial pattern matching), grabbed the parent document's CSRF token, and called each endpoint with the user's logged-in session. The response data was POSTed to an attacker-controlled collector.

The payload adapted to the victim's role. For ordinary researcher sessions, it pulled basic profile, navigation, wallets, balances, and recent reports. For manager and triage sessions, it additionally swept program listings, pending and triaged report queues, and recent notifications.

Bypassing the UI block

The platform UI rejected SVG uploads at the file picker with "not a valid format". The same file uploaded via a direct API call to the internal attachments endpoint, with the standard CSRF and session headers, returned HTTP 201 with an attachment ID. The server then served the SVG with the SVG content type, executing as expected when loaded.

A short snippet pasted into the dashboard's own DevTools console was enough to perform the upload using the legitimate session and receive back a hosted attachment URL that could be embedded in any report.

The Impact

Confirmed against test accounts only. For each account that opened a malicious report or the direct URL, the following data was captured server-side within two seconds:

ActionStatusNotes
Read email, role, wallet addressesConfirmedLive on monitor
Steal CSRF tokenConfirmedFrom parent page meta tag
Read currency balances and submitted reportsConfirmed
Read manager programs and report queuesConfirmed (static analysis)Fires automatically on manager sessions
Read manager notificationsConfirmed (static analysis)
Direct URL trigger without a reportConfirmedWorks as soon as the user is logged in

The blast radius was anyone who opened a malicious report. On a bug bounty platform that includes triage staff and program managers who routinely view dozens of reports per day. With the captured CSRF token and session, an attacker could pivot to authenticated state-changing actions on each victim's behalf, including manager-only actions on their session.

Remediation

  • Add a server-side MIME and extension check on the internal attachments endpoint that rejects SVG. The UI already blocks it. One backend check closes both vectors
  • If SVG attachments must be supported, serve them from a separate sandboxed origin with a restrictive Content-Security-Policy that disables script execution, or sanitize SVG content server-side before storage
  • Strip <object>, <embed>, and <iframe> tags from the report description sanitizer, or restrict their type and data attributes to a strict allowlist
  • Audit other upload endpoints for the same client-side-only restriction pattern