Skip to main content

Cross-Site Scripting XSS

What It Is

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

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.


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

Basic Checklist

List all places where user input enters the app
Never trust URL params, forms, or input fields directly
Avoid using innerHTML with user-controlled data
Use textContent or innerText for plain text
Escape user input before rendering
Use framework rendering safety properly
Avoid dangerously injecting HTML into the DOM
Sanitize HTML with DOMPurify when HTML rendering is required
Avoid eval completely
Use CSP headers to control script and resource loading
Prefer CSP nonces over unsafe-inline
Use report-only mode before enforcing strict CSP
Avoid storing sensitive data where JavaScript can read it

Interview Style Answer

Cross-Site Scripting, or XSS, happens when an attacker injects malicious JavaScript into a web application and the browser executes it as part of the trusted page. The script can enter through URLs, forms, input fields, or any user-controlled data rendered unsafely into the DOM. XSS can lead to session hijacking, unauthorized activities, keystroke capture, stealing critical page information, and phishing attacks. A common vulnerable pattern is placing query parameter data into innerHTML. To prevent XSS, identify all user inputs, avoid innerHTML, use textContent or innerText, escape and sanitize data, use libraries like React safely, sanitize HTML with DOMPurify when needed, avoid eval(), and configure CSP headers with allowed sources, script nonces, and report-only mode.


One-Line Summary

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

Final Mental Model

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