XHack Logo
XHack
Home
Features
Services
BlogPricingContact
Sign upLogin
XHack Logo
XHackOffensive Security

Certified cybersecurity firm delivering enterprise-grade security solutions. VAPT, Red Teaming, SOC, and AI-powered security tools.

OSCP+OSCPC-AI/MLPenCASA
Services
  • VAPT Services
  • Red Teaming
  • SOC Services
  • Threat Intelligence
  • GDPR Compliance
  • Incident Response
Quick Links
  • About Us
  • VAPT
  • Services
  • XHack AI
  • Docs
  • Pricing
  • Services Pricing
  • Blog
  • Case Studies
  • Documents
  • Team
  • Certifications
Contact
support@xhack.io

24/7 SOC Operations

Under attack? Get help nowGet a Quote

© 2026 XHack. All rights reserved.

Security & TrustVulnerability DisclosurePrivacy PolicyTerms of ServiceRefund Policy
Back to Blog
Writeups

Stored XSS in Email Fields: The Bug Hiding in Plain Sight

XHack

XHack

Author
August 25, 2026
15 min read
Stored XSS in Email Fields: The Bug Hiding in Plain Sight

Table of Contents

16

Nobody Takes Email Fields Seriously, and That’s the Problem

Step One: Poisoning My Own Account

Step Two: Turning a Clean Invite Into a Weapon

The Moment It Actually Fired

Why This Was Worse Than a Standard Stored XSS

Three Layers of Defense, Three Failures

How I Actually Found This

The Real Lesson: Test the Boring Stuff

How XHack Catches What “Feels Safe” Gets Skipped

FAQ: Stored XSS in Email Fields, Questions Answered

What is plus addressing in email, and why does it matter for security?

Why does stored XSS in an email field matter more than in a normal text field?

How could an unauthenticated stored XSS vulnerability like this be exploited in the real world?

How do you actually prevent this class of vulnerability?

Would automated scanning tools have caught this on their own?

Conclusion

By Salman Khan, OSCP+, Founder of XHack, SRT (Synack Red Team member)

Read this in 30 seconds: Stored XSS in Email Fields, I was closing out a Synack engagement, already mentally packed up for the day, when I decided to test one more field: the email input on account settings. Everyone assumes email fields are safe because they’re “structured” and “validated.” They’re not, because most apps forget that the email spec allows a `+` character for subaddress tagging, and they store whatever comes after it without checking what’s actually in there.

I used that gap to plant an HTML payload inside my own email address, then delivered it to a victim through a completely clean invitation link with no visible payload anywhere. When the victim opened it, my profile rendered their email into the page unencoded, and the script fired the second their mouse crossed it. No login required. No warning. Stored XSS, unauthenticated, from a field nobody thinks to test.

Twenty minutes before quitting time, I almost skipped the one field that mattered.

I’d already gone through the usual suspects: file uploads, bio fields, display names, every input that practically begs for an XSS payload. Nothing. Standard engagement, mostly clean app, I was ready to write it up and go home.

Then I looked at the email field and thought, eh, might as well.

That “might as well” turned into the best finding of the engagement, and it’s the reason I’m writing this up for the XHack blog instead of just filing it and moving on. Not because the technique is groundbreaking (it isn’t, the trick is over two decades old), but because of what it says about where real vulnerabilities actually hide. It’s the same lesson I keep relearning every time I write up a broken-access-control bug like BOLA or IDOR: the field or endpoint that “obviously” isn’t the problem is usually exactly where the problem is. Everyone hammers the rich text editor. Nobody looks twice at the email field, because it feels safe. That assumption is exactly why this bug existed across three separate layers of defense at once.

Nobody Takes Email Fields Seriously, and That’s the Problem

Ask any pentester or developer where they’d expect to find XSS, and the email field is nowhere near the top of the list. It’s structured. It has a well-known format. There’s an unspoken assumption baked into how most people build and test software: an email address can’t really be dangerous, it’s just a string with an `@` in it.

I believed that too, right up until this engagement.

Here’s the part almost nobody remembers: RFC 5321, the actual email specification, allows a `+` character in the local part of an address for subaddress tagging. You’ve used this yourself without thinking about it. Send something to `you+newsletters@gmail.com` and it still lands in your inbox, exactly the way `you@gmail.com` would. Gmail, Outlook, and most major providers support this natively.

The catch is what happens on the receiving end, inside the application. Most apps store the full address, including everything after the `+`, exactly as submitted. Then, somewhere down the line, they render that string back onto a page without stopping to ask what’s actually hiding inside it. The validation logic checks the shape of an email address. It doesn’t check the content riding along inside a technically-valid subaddress tag. That gap is the entire vulnerability.

Step One: Poisoning My Own Account

The target was a content platform where artists manage their profiles, releases, and account settings, the kind of app where an email field feels like the last place worth testing.

Under account settings, there’s an email field with an edit button. I clicked edit, typed in a valid-looking address, flipped on Burp intercept, and hit save. Once the request hit Burp, I swapped both the email and confirmation parameters for something the front-end would never have allowed through on its own:

“`

salman+<h1/onmouseover=alert(origin)>synacktest@gmail.com

“`

Burp Suite request showing an HTML payload injected into the email and email_confirmation parameters via subaddress tagging
Intercepted request: the payload rides inside a technically valid email subaddress, past client-side validation entirely

The front-end validation had already done its job and stepped out of the way by the time this request reached Burp, because the browser only checks input before it leaves the browser. Once you’re editing the raw HTTP request, that check is meaningless. The server accepted the payload without complaint. My account’s email field now permanently contained a live XSS trigger, and as far as the application was concerned, it was just another valid subaddress.

Step Two: Turning a Clean Invite Into a Weapon

A poisoned field sitting in your own account doesn’t prove anything on its own. I needed to show it could actually hit someone else, without them doing anything unusual.

Since my profile email was already poisoned, I went looking for any feature that would render it somewhere a second user could see. I found one fast: a “request another artist to connect” option, which asks for an email address to send an invitation to.

Modal asking for an email address to send a social media connection invitation, with a clean unpoisoned test email typed in
The invitation flow: a completely clean email address goes in, with zero indication of what’s waiting on the other end

I entered a normal, clean email address. No payload, nothing suspicious, just a regular inbox I controlled for testing. Hit send. The invited address received a completely ordinary-looking invitation email with a link containing an ID and a key parameter. Nothing in that email would raise a single eyebrow.

But the moment that link is clicked, the invitation page renders the inviting user’s profile, which is to say, my profile, complete with my poisoned email sitting in the page HTML, completely unencoded. The victim sees what looks like a normal profile page. Then their cursor drifts over the email area, and the script fires.

The Moment It Actually Fired

I opened the invitation link in an incognito window to rule out any session or caching weirdness on my end. The alert popped the instant my mouse passed over the email region, no click required, no warning, no login prompt anywhere in the flow.

Browser alert box firing with alert(origin) the moment the cursor hovers over the rendered email field on an unauthenticated invitation page
Confirmed: alert(origin) executes on hover, on a page that requires no login, no session, and no prior interaction

Stored XSS, delivered from an email field, on a page that needed absolutely nothing from the victim except opening a link that looked completely legitimate.

Why This Was Worse Than a Standard Stored XSS

A stored XSS behind a login wall is bad. This one skipped the login wall entirely. The invitation page required no authentication, no account, no active session. Just a URL that anyone on the internet could open. And because the invitation could be sent to any valid email address, the attack surface wasn’t scoped to one email provider or one type of user. Gmail, Outlook, Yahoo, a corporate mailbox, it didn’t matter. Whoever opened the link got hit.

Walk the full chain end to end: I create an account, inject a payload into my own email field, build out an artist profile, then use the “connect” feature to send perfectly clean invitation emails to any address I choose. The victim gets a normal-looking invite. They click it. The page renders my profile, payload and all. They’re compromised, and their own email never touched the payload at any point, so they have zero warning until their browser is already running my JavaScript.

Swap `alert(origin)` for something with actual teeth: session hijacking, credential harvesting, a convincing phishing overlay sitting on top of a page the victim already trusts. At that point you’ve built a watering hole attack out of a profile settings page. The attacker controls both the payload and how it gets distributed. The victim’s only job is clicking one link that looks like every other invitation email they’ve ever gotten.

Three Layers of Defense, Three Failures

What makes this finding sting is that it should never have survived a single review pass, let alone three.

Client-side validation checked the email format, and stopped there. It only runs in the browser, which means Burp bypasses it in about two seconds flat. This isn’t security, and it never was. I genuinely don’t know why applications are still leaning on browser-side validation as a control in 2026.

Server-side validation checked structure, not content. Does the string have an `@`? Does the domain look plausible? Fine, ship it. It never inspected what was actually sitting inside the subaddress tag. My payload showed up as `+<h1/onmouseover=alert(origin)>`, and the regex saw a technically valid `+` tag and waved it through. The `+` character genuinely is valid per spec. The HTML payload hitching a ride on it was never something that regex was built to catch.

Output encoding didn’t exist at all. The application pulled the raw email string straight out of the database and dumped it into the page HTML, letting the browser interpret whatever was inside. OWASP’s XSS Prevention Cheat Sheet has covered this exact fix for years: encode `<` as `&lt;`, `>` as `&gt;`, and every other HTML-significant character at the point of output. That’s the whole fix. One line, in one template.

How I Actually Found This

I want to be straight about this part, because I think it matters more than the technical mechanics. I didn’t find this through some clever methodology or a deep dive into email RFCs mid-engagement. I found it because I was bored, had about twenty minutes left, and decided to poke at a field that felt like a waste of time.

I’d read something months earlier about edge cases in email parsing (honestly can’t tell you where anymore) and the plus-addressing trick was just sitting somewhere in the back of my head, unused. My first standard XSS attempt in the email field got shut down by front-end validation, and that’s usually where most people move on to the next field. I almost did exactly that. Then I thought: what if I use the plus trick specifically to get past the client-side check with Burp?

Two minutes later, I had stored XSS on an unauthenticated page.

The Real Lesson: Test the Boring Stuff

I almost didn’t test that field. I almost called it a day. Twenty minutes of “might as well try it” turned into a high-severity finding that reached every single visitor to an unauthenticated page, not just logged-in users, not just one email provider, everyone.

If there’s one takeaway worth carrying into your own testing or your own application’s threat model, it’s this: the boring fields are where the good bugs live. Everyone pounds on the rich text editor and the file upload because they look dangerous. Nobody looks twice at the email field because it feels safe. Test it anyway. Especially when it feels safe.

How XHack Catches What “Feels Safe” Gets Skipped

So yeah, here’s where I talk about what XHack brings to the table, and this finding is a genuinely good example of why.

This bug survived three layers of review because every layer assumed the email field was low-risk and treated it as an afterthought. That’s a human judgment failure as much as a technical one, and it’s exactly the kind of blind spot XHack is built to close. The autonomous XHack AI agent doesn’t get bored twenty minutes before quitting time. It enumerates every input on every form, including the ones a rushed manual review or a checkbox scanner would skip, and it doesn’t assume a field is safe just because it looks structured.

But the autonomous side alone wouldn’t have caught the full impact here either. Finding that the `+` trick bypasses validation is one thing. Realizing that the poisoned email then gets rendered, unauthenticated, on an invitation page reachable by anyone, and chaining that into a real watering-hole scenario, that’s the kind of multi-step business logic reasoning that still needs a human thinking like an attacker, not just a tool flagging a pattern. That’s the human-led half of what XHack does: real offensive experience connecting a technical quirk to an actual, provable impact.

Findings like this also don’t just disappear into a PDF once the engagement ends. They feed into XHack’s ongoing monitoring, so a regression, someone reintroducing unencoded output six months later during a refactor, doesn’t sit undetected for another year until the next scheduled test.

If you want a cheap one-time scan that checks the obvious fields and calls it a day, we’re genuinely not the right fit. If you want full VAPT testing that actually looks at the field nobody else bothers with, that’s the whole point. Free consultations are on the table, even if it turns out XHack isn’t the right fit for what you need. Brutal honesty is kind of our thing.

FAQ: Stored XSS in Email Fields, Questions Answered

What is plus addressing in email, and why does it matter for security?

Plus addressing is a feature defined in RFC 5321 that lets you append `+anything` to the local part of an email address (before the `@`) for subaddress tagging, commonly used for filtering or tracking where an email came from. It matters for security because most applications store this “anything” portion without inspecting its content, assuming that if a string looks like a technically valid email, it’s safe to store and later display. Attackers can hide an XSS payload, a SQL fragment, or other malicious content inside that tag, and it will pass most format-only email validation without issue.

Why does stored XSS in an email field matter more than in a normal text field?

Because email fields are almost never treated as untrusted input. Developers and testers alike assume the format validation an email address requires makes it inherently safer than a free-text bio or comment field, so output encoding often gets skipped specifically for email display. That assumption creates a blind spot at exactly the layer meant to stop this class of bug, which is how this finding survived client-side validation, server-side regex validation, and the output layer simultaneously.

How could an unauthenticated stored XSS vulnerability like this be exploited in the real world?

An attacker plants a payload in their own account’s email field, then uses any feature that sends a legitimate-looking invitation, notification, or shared link containing that profile to another user. The victim’s own inbox never sees the payload, only a clean, ordinary-looking message. When the victim opens the resulting page and their browser renders the attacker’s profile, the payload executes in the victim’s browser session, with the potential to hijack sessions, harvest credentials, or serve a convincing phishing overlay, all without the victim ever logging in or taking any unusual action.

How do you actually prevent this class of vulnerability?

Output encoding is the fix that matters most: encode `<`, `>`, and other HTML-significant characters at the point where any user-controlled string, including email addresses, gets rendered into a page. Never trust format-only validation to double as a security control, since a string can be a perfectly valid email address per RFC 5321 while still carrying an executable payload inside a subaddress tag. Server-side validation should also flag unusual characters inside the local part specifically, not just confirm the overall shape of the address.

Would automated scanning tools have caught this on their own?

Most signature-based and pattern-matching scanners would likely miss this, because the payload doesn’t look like a typical XSS attempt in a typical field, it’s hiding inside a technically valid email subaddress, in a field scanners often deprioritize. This is a case where the multi-step reasoning behind a genuinely autonomous AI hacking agent, actually enumerating overlooked fields and testing edge-case encodings, matters more than a scanner running a fixed payload list against the “usual” inputs.

Conclusion

The whole chain came down to one detail almost everyone gets wrong: a field that looks safe because it’s structured isn’t automatically a field that’s actually validated. RFC 5321 gave this bug its opening, three broken layers of defense let it through, and twenty bored minutes at the end of an engagement are the only reason it got found at all.

If there’s a pattern worth taking from this beyond the specific bug, it’s that the interesting vulnerabilities rarely live in the places everyone’s already staring at. They live in the field everyone assumes is fine. Test that one. Especially that one.


Categories
Writeups
Previous Post
CVE-2026-72898: Unauthenticated SQL Injection Hands Over Metabase Admin
Next Post
Autonomous AI Hacking Agents: Top 7 in 2026

On This Page

Nobody Takes Email Fields Seriously, and That’s the Problem

Step One: Poisoning My Own Account

Step Two: Turning a Clean Invite Into a Weapon

The Moment It Actually Fired

Why This Was Worse Than a Standard Stored XSS

Three Layers of Defense, Three Failures

How I Actually Found This

The Real Lesson: Test the Boring Stuff

How XHack Catches What “Feels Safe” Gets Skipped

FAQ: Stored XSS in Email Fields, Questions Answered

What is plus addressing in email, and why does it matter for security?

Why does stored XSS in an email field matter more than in a normal text field?

How could an unauthenticated stored XSS vulnerability like this be exploited in the real world?

How do you actually prevent this class of vulnerability?

Would automated scanning tools have caught this on their own?

Conclusion