Bug BountyIntermediate8 min read2025-11-10

From Self-XSS to Reflected XSS: A CSRF Escalation Story

From Self-XSS to Reflected XSS: A CSRF Escalation Story
#XSS#CSRF#Vulnerability Chaining#Bug Bounty#Escalation#Web Security

root@thebughunter:~$ cat vulnerability_escalation.txt

Target: Card Generation Feature
Initial Finding: Self-XSS (Low Impact)
Escalation Vector: CSRF Vulnerability
Final Impact: Reflected XSS (Medium Impact)

The Initial Discovery

I was testing a card generation feature on a bug bounty program when I found what looked like a self-XSS vulnerability. Nothing special at first glance, just another input field that reflected user data without proper sanitization.

The application let users create personalized cards by entering their name and other details.

INITIAL FINDING

Vulnerability: Self-XSS in user input field
Impact: Limited to attacker's session
Severity: Low (typically gets rejected)

The vulnerability was in the user_name parameter. I was able to escape the html context with a payload like this:

user_name=test"onmouseover="alert('XSS')"style="position:absolute;width:100%;height:100%;top:0;left:0;"payload

The application reflected it back unsanitized, creating an invisible overlay that triggered XSS on mouseover. Cool, but useless since it only affected my own session. Most programs won't even look at self-XSS reports.

Finding the Missing Piece

But then I noticed something interesting while poking around the application - the card generation endpoint had no CSRF protection whatsoever.

SECURITY WEAKNESS IDENTIFIED

× No CSRF tokens implemented
× Cookies were not needed
× Cross-origin requests accepted
POST request vulnerable to CSRF

If I could craft a malicious page that automatically submitted the XSS payload, I could turn this useless self-XSS into a legitimate reflected XSS vulnerability.

Chaining the Vulnerabilities

Here's how I put it all together:

Step 1: Building the CSRF PoC

I created a simple HTML file that would auto-submit a POST request with my XSS payload:

<html>
  <body>
    <script>history.pushState('', '', '/');</script>
    <form action="https://example-target.com/generate-card" method="POST">
      <input type="hidden" name="first_name" value="John" />
      <input type="hidden" name="last_name" value="Doe" />
      <input type="hidden" name="card_type" value="standard" />
      <input type="hidden" name="user_name" value="test&quot;onmouseover=&quot;alert('XSS')&quot;style=&quot;position:absolute;width:100%;height:100%;top:0;left:0;&quot;payload" />
      <input type="hidden" name="template_id" value="12345" />
      <input type="submit" value="Submit request" />
    </form>
    <script>
      document.forms[0].submit();
    </script>
  </body>
</html>

Step 2: Understanding the Response

When the form gets submitted, the payload reflects in the download button:

<div class="download-container">
  <a href="#" 
     id="download_link" 
     download="card_by_test"onmouseover="alert('XSS')"style="position:absolute;width:100%;height:100%;top:0;left:0;"payload.png">
    <img src="/icons/download.svg" alt="Download">
    Download Card
  </a>
</div>

Step 3: The Attack Flow

When a victim visits my malicious page, the form auto-submits to the vulnerable endpoint. The response comes back with my XSS payload embedded in it. As soon as they move their mouse (which happens pretty much immediately), the JavaScript executes in their browser context.

The Impact Difference

BEFORE (Self-XSS)

• Only affects the attacker
• No real-world impact
• Gets marked informational
• No bounty

AFTER (Reflected XSS via CSRF)

• Affects any visitor to malicious page
• Session hijacking possible
• Can steal sensitive data
• Higher severity
  • Before: I can only attack myself. Useless.
  • After: I can attack anyone who visits a page I control. They don't even need to be logged into the target site when they visit my page.

Now we're talking about real impact - session hijacking, data theft, phishing attacks...

Breaking Down the Payload

Let's look at what makes this XSS payload work:

test"onmouseover="alert('XSS')"style="position:absolute;width:100%;height:100%;top:0;left:0;"payload

How it works:

  • test" - Breaks out of the HTML attribute context
  • onmouseover="alert('XSS')" - Our event handler that executes the XSS
  • style="position:absolute;width:100%;height:100%;top:0;left:0;" - Creates an invisible element covering the entire page
  • payload - Completes the injection without breaking the HTML

The invisible overlay is key here. It guarantees that any mouse movement will trigger our payload. The victim never sees anything suspicious, but the moment they move their cursor, game over.

The Complete Attack Chain

ATTACK CHAIN BREAKDOWN

1.Attacker hosts malicious CSRF page
2.Victim visits attacker's page (via phishing, social engineering, etc.)
3.CSRF auto-submits XSS payload to vulnerable endpoint
4.Target application reflects payload in response
5.Victim moves mouse and XSS executes in their context

What I Learned

KEY TAKEAWAYS

1. Don't write off self-XSS immediately - look for ways to weaponize it
2. Always test for CSRF protection on state-changing operations
3. Think about how vulnerabilities can chain together
4. Test different contexts and attack vectors
5. Document your findings clearly to show the full impact

For bug hunters:

When you find a self-XSS, don't just close the tab and move on. Take a few minutes to check:

  • Is there CSRF protection on the vulnerable endpoint?
  • Can you trigger the payload in someone else's browser context?
  • Are there other vulnerabilities you can chain it with?

Sometimes the difference between a rejected report and a critical finding is just a little extra digging.

For developers:

Defense in depth matters. This vulnerability chain worked because of two separate security failures:

  • No input sanitization/output encoding
  • No CSRF protection

If either one had been implemented properly, the attack wouldn't have worked.

Mitigation Strategies

RECOMMENDED FIXES

Input Validation & Output Encoding

• HTML encode all user input before reflecting it in responses
• Implement Content Security Policy (CSP) headers
• Use context-aware output encoding
• Validate input on both client and server side

CSRF Protection

• Add CSRF tokens to all state-changing forms
• Implement SameSite cookie attribute (Strict or Lax)
• Verify Origin and Referer headers

The Outcome

This escalation technique turned what would have been an instant rejection into a valid medium-severity finding:

  • Initial Assessment: Self-XSS → Rejected/Informational
  • After Escalation: Reflected XSS via CSRF → Medium
  • Real Impact: Account takeover, data theft, session hijacking

The program accepted the report and it was eventually fixed. The key was demonstrating that the vulnerability could actually affect real users, not just myself.

Final Thoughts

This case shows why creative thinking matters in bug bounty hunting. Security vulnerabilities rarely exist in isolation. What looks like a dead end might actually be part of a more serious attack chain.

The self-XSS was useless on its own. The missing CSRF protection wasn't immediately exploitable either. But together? They created a valid vulnerability.

Always look for the bigger picture. Test how different weaknesses might combine. And most importantly, don't dismiss findings too quickly just because they seem low-impact at first glance.

One last thing - responsible disclosure is non-negotiable. Always report through proper channels and give the program time to fix issues before publishing any details.


[NEXT_STEPS]

Want to learn about vulnerabilities or how to get started in bug hunting?
IDOR Analysis: 200+ Real-World Cases
My BSCP Certification Journey

[SHARE_THIS_POST]
Help spread knowledge in the cybersecurity community