What is XSS and how does it work? With examples

What is XSS and how does it work? With examples

If you have ever wondered what is XSS and how does it work, the short answer is this: cross site scripting happens when an app takes input from one user and hands it to another user’s browser as code instead of plain text. The browser runs that code with the same trust it gives the real site. That means an attacker can read cookies, change the page, or act as the victim.

What cross site scripting actually is

A web page mixes two kinds of content. There is the markup and script the site author wrote, and there is data, like a comment, a search term, or a username. The browser cannot tell them apart on its own. It trusts whatever the server sends. Cross site scripting is the bug where attacker data crosses over and becomes script.

Here is the core idea. A user types a comment. The app stores it. Later the app prints that comment back into the HTML of a page. If the app prints it raw, and the comment contains a <script> tag, the tag runs in the next reader’s browser. The attacker never touched that reader. The site delivered the payload for them.

The browser runs attacker text as code because the app never told it where the data ends and the markup begins.

Why it matters

Script that runs on the page runs as the logged in user. It can read document.cookie if the session cookie is not protected, submit forms, or quietly change account settings. The attacker does not need the victim’s password. They borrow the victim’s open session.

The three types of XSS, with simple examples

People sort cross site scripting into three buckets based on where the bad input lives and how it reaches the browser. The examples below use an invented app called Acme Notes, a small site where people post public notes and comments. None of these target a real system.

Stored XSS

Stored XSS means the payload is saved in the database and served to everyone who views the page. It is the worst of the three because one submission can hit many users. This is a clear stored xss example.

Imagine the comment box on Acme Notes. A visitor submits this in the comment field:

<script>alert('xss')</script>

The app saves the text as is. When the note page renders, it builds the HTML like this on the server:

<div class="comment">
  <script>alert('xss')</script>
</div>

Now every person who opens that note runs the script. The alert('xss') is harmless on its own. It only pops a box. But a real attacker would swap it for code that reads the session cookie and sends it to a server they control. Same hole, worse payload.

Reflected XSS

Reflected XSS means the payload is not stored. It rides in the request, usually in a URL, and the server reflects it straight back into the response. The victim has to open a crafted link. This is a plain reflected xss example.

Say Acme Notes has a search page that shows what you searched for:

https://acme-notes.example/search?q=hello

The page prints: You searched for: hello. If the app prints the q value raw, an attacker can build a link where q is a script:

https://acme-notes.example/search?q=<script>alert('xss')</script>

Anyone who clicks that link runs the script in their own browser, on the real Acme Notes domain. The attacker sends the link by email or chat. The bug is on the page, but the trigger is the click.

DOM based XSS

DOM based XSS happens fully in the browser. The server may send clean HTML, but client JavaScript reads attacker input and writes it into the page in an unsafe way. The dangerous step is in the script the site already ships.

Suppose Acme Notes shows a welcome banner using the part of the URL after the #:

const name = location.hash.slice(1);
document.getElementById('banner').innerHTML = 'Hi ' + name;

Now an attacker shares this link:

https://acme-notes.example/#<img src=x onerror=alert('xss')>

The innerHTML assignment turns the text into real elements. The broken image fires its onerror handler, and the script runs. The server never saw the payload, because the part after # never leaves the browser. That is why server side filters miss it.

What is XSS and how does it work under the hood

Every variant of cross site scripting comes from one root cause. The app treats untrusted input as trusted output. The fix is to keep data as data the whole way through. There are two layers that do most of the work.

Output encoding

Encode data for the exact spot where it lands. When you put user text inside HTML, convert the characters that have meaning in HTML so the browser shows them instead of running them:

  • < becomes &lt;
  • > becomes &gt;
  • & becomes &amp;
  • " becomes &quot;

After encoding, the earlier stored payload renders as visible text:

<div class="comment">
  &lt;script&gt;alert('xss')&lt;/script&gt;
</div>

The reader sees the literal characters and nothing runs. Most template engines do this for you if you use their normal output syntax instead of a raw or unescaped output. Encoding depends on context. HTML body, an HTML attribute, JavaScript, and a URL each need their own encoding rules, so use a library that knows the difference rather than rolling your own escapes.

Avoid the unsafe sinks

For DOM based bugs, stop feeding untrusted input into sinks that parse HTML or run code. Reach for safe ones instead:

  • Use textContent instead of innerHTML when you only need text.
  • Avoid eval, setTimeout with a string, and document.write on user input.
  • Set attributes with setAttribute rather than building HTML strings by hand.

Content Security Policy

A Content Security Policy is a response header that tells the browser which scripts are allowed to run. It is a second line of defense, not a replacement for encoding. A strict policy blocks inline scripts and scripts from domains you did not approve:

Content-Security-Policy: default-src 'self'; script-src 'self'

With that header, an injected inline <script> is refused even if it slips into the page. Pair it with the HttpOnly flag on session cookies so script cannot read them through document.cookie. Layered defenses mean one mistake does not hand over the account.

How to spot it before an attacker does

Finding cross site scripting is partly about knowing where input flows. Trace every place the app reads input, then follow it to every place that input is written back out. Comment fields, search boxes, profile names, URL parameters, and error messages that echo your input are common starting points. For a wider tour of input bugs, see our injection and input category.

The hard cases are the ones that depend on how the app assumes its own data behaves, like a field that is encoded in one view and printed raw in another. Those gaps show up when you understand what the app expects, not just when you throw a list of payloads at it. This is exactly the kind of bug an autonomous researcher that tests an app’s assumptions is built to find and then prove with real evidence. You can read more about that approach on our about page.