Learning terminal: Cross-Site Scripting

visitor@learn:~$ cat introduction-to-xss.html

Cross-Site Scripting (XSS)

Cross-site scripting happens when untrusted text is placed into a web page in a way that the browser treats as HTML or JavaScript instructions instead of plain user data. The attacker is usually targeting users of a trusted site, not only the server itself.

URL example: https://site.example/search?q=<script>alert('XSS')</script> is a reflected XSS-style URL when the page prints q back into the response without encoding it.

01 // What is XSS?

01

Untrusted input arrives

A user profile, message, search term, URL value, cookie, or header reaches the application.

02

The page includes it

The application places that value into HTML, JavaScript, attributes, or the DOM.

03

The browser executes it

If the context is unsafe, the browser interprets attacker-controlled text as code.

response = "<p>Your search for " + searchTerm + " returned 8 records</p>"

02 // Traditional XSS attack flow

01

Attacker plants input

The attacker submits text that contains HTML or JavaScript into a vulnerable field.

02

Victim visits trusted site

The victim loads a page, link, or feature they believe belongs to the legitimate domain.

03

Server or page returns payload

The malicious value becomes part of the page that the browser receives or constructs.

04

Script runs with site trust

The browser gives the script access allowed to that origin, such as the page DOM and some cookies.

Modern cookie flags like HttpOnly, SameSite, and secure session design can reduce impact, but they do not make XSS harmless.

03 // Types of XSS

Reflected XSS

The poisoned delivery

The application reflects attacker-supplied input back in the immediate response, often through a URL parameter.

/search?q=<script>...</script>

Stored XSS

The landmine

The malicious value is saved by the application, then later served to victims from a database or content store.

comment = "<script>...</script>"

DOM-based XSS

The inside job

Client-side JavaScript reads untrusted browser data and writes it into an unsafe DOM sink.

location.hash -> innerHTML

Stored attribute example

A description field inserted directly into an image attribute can escape the attribute and add new markup.

<img src="file.jpg" alt="${user_description}" />

DOM fragment example

Fragments after # are not sent to the server, but page JavaScript can still read and mishandle them.

https://trusted-bank.example/help#<img src=x onerror="...">

04 // Reflected XSS via URL GET parameters

A common reflected XSS pattern appears when a search page reads a URL parameter and prints it back into the HTML response. The URL looks like it belongs to the trusted site, but the query string carries attacker-controlled text.

Normal request

https://site.example/search?q=eggs
<p>Your search for eggs returned 8 records</p>

The search term is displayed as normal page text.

Poisoned URL

https://site.example/search?q=<script>alert('XSS')</script>
<p>Your search for <script>alert('XSS')</script> returned 8 records</p>

If the app inserts the value as raw HTML, the browser may execute it instead of displaying it.

The fix is to encode the reflected value before placing it into the response: &lt;script&gt;alert('XSS')&lt;/script&gt;.

05 // Output encoding playground

simulation only - scripts are not executed

Unsafe rendering unsafe


              

Encoded rendering preferred


              

Encoding preserves the value as text for the current output context.

06 // XSS attack goals

Session theft

Scripts may try to read accessible cookies or session data and send them to an attacker-controlled endpoint.

Defacement

XSS code can rewrite the DOM, change visible page content, or damage trust in the site.

Phishing

A fake login, payment, or re-authentication prompt can appear inside a legitimate domain.

XSS can also be chained with browser, plugin, or application vulnerabilities. The impact can escalate beyond one page.

07 // Identifying vulnerable inputs

Any input that becomes output should be treated as untrusted. The risky moment is when data crosses into HTML, attributes, JavaScript, CSS, URLs, or DOM APIs.

Common sources

Forms, search bars, HTTP parameters, hidden fields, cookies, headers, profile values, filenames, and comments.

What to watch for

Input displayed as-is, partially stripped characters, broken layout, unexpected alerts, or HTML that appears to become markup.

Server and client

Do not trust client-side checks. Validate on the server, and use client validation only for quick feedback.

08 // Prevention

Input handling

Allow only necessary characters, enforce length limits, reject invalid formats, and handle dangerous requests deliberately.

const usernameWhitelist = /^[A-Za-z0-9_]+$/;
if (!usernameWhitelist.test(username)) {
  return res.status(400).send("Invalid username");
}

Output encoding

Encode user data for the exact context where it will be rendered. HTML body, attributes, URLs, CSS, and JavaScript need different treatment.

<script>alert(1);</script>
&lt;script&gt;alert(1);&lt;/script&gt;

Avoid brittle filters

Removing only <script> is bypassable. Attackers can use mixed casing, malformed tags, event handlers, encodings, or different contexts.

Use safer APIs

Prefer textContent over innerHTML. In React, normal JSX text rendering escapes values by default.

Store original data

Usually keep values in their original form and encode when outputting. The correct encoding depends on future use.

Remember: encode on output for the context. Validation and sanitization are useful layers, but safe rendering is what stops user input from becoming browser-executed code.
<- return to /learn