Skip to main content

Cross-Site Scripting XSS

Cross-Site Scripting, also called XSS, happens when an attacker injects malicious JavaScript into a website and makes the browser execute it.

The injected script can come from places like URLs, forms, input fields, or any user-controlled data that is inserted into the page unsafely.

XSS = Attacker-controlled script runs inside your website

One-line idea: XSS = Malicious JavaScript injection caused by unsafe user input rendering and prevented with safe rendering, sanitization, escaping, no eval, and CSP.

Core Concepts​

Why It Matters​

XSS is dangerous because the malicious script runs inside the user's browser as part of the trusted website.

This can allow attackers to perform unintended actions such as:

  • stealing session data
  • manipulating page content
  • performing unauthorized actions
  • capturing typed input
  • stealing sensitive page information
  • showing phishing forms
If user input becomes executable JavaScript, the page becomes unsafe.

Common Injection Sources​

Malicious scripts can enter the app through user-controlled inputs.

Common sources:

  • URL query parameters
  • forms
  • input fields
  • browser events
  • dynamic HTML content
  • data rendered directly into the DOM
Never trust user input.

Any data that comes from outside the application should be treated as unsafe until validated, sanitized, and rendered safely.

Practical Examples​

Vulnerable Pattern​

A common XSS issue happens when user input is inserted into the DOM using innerHTML.

<div>Welcome, <span id="username"></span>!</div>

<script>
const params = new URLSearchParams(window.location.search);
const name = params.get("name");

document.getElementById("username").innerHTML = name;
</script>

Problem:

URL input -> innerHTML -> browser treats input as HTML

If the value contains script-like HTML, the browser may execute it.

This is why directly placing user input into innerHTML is unsafe.

Safe Rendering​

Use textContent or innerText when rendering plain text.

<div>Welcome, <span id="username"></span>!</div>

<script>
const params = new URLSearchParams(window.location.search);
const name = params.get("name");

document.getElementById("username").textContent = name;
</script>

This treats the value as text, not executable HTML.

innerHTML -> parses HTML
textContent -> renders plain text

Vulnerability 1: Session Hijacking​

Session hijacking happens when an attacker tries to steal session-related data such as cookies.

If a malicious script can access cookies, it may send them to an attacker-controlled system.

Possible impact:

  • stolen user session
  • account takeover risk
  • unauthorized access
  • user impersonation
XSS + readable cookies = session theft risk

This is why sensitive session cookies should not be accessible through JavaScript.

Vulnerability 2: Unauthorized Activities​

XSS can make the browser perform actions the user did not intend.

Example idea:

User is logged in
Injected script runs in the page
Script triggers an action using the user's active session

Possible impact:

  • unwanted post creation
  • unwanted messages
  • unwanted account changes
  • actions sent with the user's credentials
The browser may send valid cookies with the unwanted request.

This makes XSS especially risky in authenticated applications.

Vulnerability 3: Capturing Keystrokes​

An injected script can listen to keyboard events and capture what the user types.

Possible impact:

  • stolen usernames
  • stolen passwords
  • captured search text
  • captured form input
  • privacy loss
XSS can turn the page into a keylogger.

This is why script injection must be prevented before user input reaches the DOM.

Vulnerability 4: Stealing Critical Information​

A malicious script can read page content and send sensitive information elsewhere.

Possible targets:

  • DOM content
  • HTML content
  • visible account details
  • transaction information
  • bank-related page content
  • critical page data
If the script runs in the page, it can read what the page can read.

Avoid putting sensitive secrets directly into frontend HTML or JavaScript.

Vulnerability 5: Phishing​

XSS can inject fake UI into a trusted website.

Example idea:

Injected script displays a fake login form
User trusts the website
User submits credentials to attacker-controlled location

Possible impact:

  • stolen username
  • stolen password
  • fake login prompt
  • fake payment form
  • user deception
XSS can make a trusted page show untrusted UI.

Dangerous Pattern: eval​

eval() executes a string as JavaScript code.

If user input reaches eval(), attackers may execute arbitrary JavaScript.

<input type="text" id="jsCode" />

<button onclick="executeCode()">Execute Code</button>

<script>
function executeCode() {
const jsCode = document.getElementById("jsCode").value;

eval(jsCode);
}
</script>

This is unsafe because the input becomes executable code.

User input + eval = code execution risk

Avoid eval() in frontend applications.

Mitigation 1: Identify All User Inputs​

First, list all possible places where user input enters the app.

Common sources:

  • URL parameters
  • forms
  • input fields
  • textareas
  • uploaded content
  • data from APIs
  • browser events
Security starts by knowing where input enters the system.

The rule is simple:

Trust your ex, but never trust user input.

Mitigation 2: Avoid innerHTML​

Do not use innerHTML with user-controlled data.

Unsafe:

document.getElementById("username").innerHTML = name;

Safer:

document.getElementById("username").textContent = name;

Use:

  • textContent
  • innerText

Avoid:

  • innerHTML
  • unsafe HTML injection
  • direct rendering of untrusted HTML

Mitigation 3: Escape User Input​

Escaping means representing special characters in a safe way so they are not interpreted as code or HTML.

Example idea:

< becomes safe text
" becomes escaped text
special characters do not become executable syntax

Escaping helps prevent user input from changing the meaning of the page.

Escape input before it can be interpreted as HTML or JavaScript.

Mitigation 4: Use Framework Safety​

Libraries like React help reduce XSS risk because they escape values before rendering them into the DOM.

Example:

function Welcome({ name }) {
return <h1>Hello {name}</h1>;
}

React treats name as text by default.

Important rule:

Do not bypass framework safety by dangerously injecting HTML.

Avoid unsafe patterns such as directly injecting untrusted HTML into the DOM.

Mitigation 5: Sanitize HTML​

If the application must render HTML from users or external sources, sanitize it first.

DOMPurify is an example of a sanitization library.

const cleanHTML = DOMPurify.sanitize(userInput);

document.getElementById("content").innerHTML = cleanHTML;

Sanitization removes unsafe parts from HTML before it reaches the DOM.

Sanitize before rendering user-controlled HTML.

Mitigation 6: Avoid eval​

Avoid using eval() because it executes strings as code.

Unsafe:

eval(userInput);

Better approach:

Do not execute user-controlled strings as JavaScript.

Use normal functions and controlled logic instead of dynamic code execution.

Mitigation 7: Content Security Policy​

Content Security Policy, also called CSP, is a browser security mechanism controlled through response headers.

CSP can decide:

  • what resources can load
  • where scripts can come from
  • which scripts are allowed to run
  • whether inline scripts are blocked
  • where violations can be reported
CSP = Browser rulebook for allowed scripts and resources

CSP helps reduce damage if an injection bug exists.

CSP: default-src​

default-src 'self' means resources should load only from the same origin by default.

app.use((req, res, next) => {
res.setHeader("Content-Security-Policy", "default-src 'self';");

next();
});

This blocks resources from unapproved external locations unless other directives allow them.

CSP: script-src​

script-src controls where scripts can load from.

app.use((req, res, next) => {
res.setHeader(
"Content-Security-Policy",
"default-src 'self';" + "script-src 'self' http://unsecure.com;",
);

next();
});

This means scripts can load from:

  • same origin
  • http://unsecure.com
script-src = allowed script locations

Only allow script sources that the application actually trusts.

CSP for Images, Styles, and Frames​

CSP can also define policies for other resource types.

Examples:

  • scripts
  • styles
  • images
  • iframes
  • fonts
  • media
CSP can control different resource types separately.

This gives the application better control over what the browser is allowed to load.

Inline Scripts and unsafe-inline​

Inline scripts are blocked if CSP does not allow them.

To allow inline scripts, CSP can use:

'unsafe-inline'

But this weakens CSP.

app.use((req, res, next) => {
res.setHeader(
"Content-Security-Policy",
"default-src 'self';" + "script-src 'self' 'unsafe-inline';",
);

next();
});

Avoid using 'unsafe-inline' unless there is no better option.

unsafe-inline reduces the security benefit of CSP.

CSP Nonce​

A nonce helps allow only specific trusted inline scripts.

HTML example:

<script nonce="randomKey">
console.log("My trusted code!");
</script>

<script>
console.log("My non-trusted code!");
</script>

CSP header example:

app.use((req, res, next) => {
res.setHeader(
"Content-Security-Policy",
"default-src 'self';" + "script-src 'self' 'nonce-randomKey';",
);

next();
});

Only the script with the matching nonce is allowed to run.

Nonce = Allow selected inline scripts without allowing all inline scripts

Rules for CSP Nonce​

A nonce should be:

  • generated randomly
  • unique per request
  • hard to guess
  • not reused carelessly

The PDF also notes that the nonce is not visible in the DOM element tab.

Nonce is used to distinguish trusted scripts from untrusted scripts.

CSP Report-Only Mode​

Report-only mode lets teams test CSP without blocking resources immediately.

If CSP errors happen, they can be reported to a configured endpoint.

Common reporting directives:

report-to default;
report-uri URL;

This is useful when introducing CSP gradually.

Report-only mode = observe CSP violations before enforcing blocking behavior

XSS Mitigation Table​

ProblemSafer Practice
User input in innerHTMLUse textContent or innerText
Untrusted HTMLSanitize with a library like DOMPurify
Dynamic code executionAvoid eval()
Special characters in inputEscape user input
Inline scriptsAvoid them or use CSP nonce
Unknown script sourcesRestrict with CSP
Sensitive cookiesAvoid JavaScript-readable session data
Unsafe user inputValidate and sanitize before use

Summary​

XSS = Malicious JavaScript injection caused by unsafe user input rendering and prevented with safe rendering, sanitization, escaping, no eval, and CSP.

Input enters app -> treat as unsafe
Render text -> use textContent
Render HTML -> sanitize first
Execute code -> never from user input
Load scripts -> control with CSP
Inline trusted script -> use nonce
Unknown script -> block it