Cross Site Scripting (XSS): Running Your Code in Someone Else's Browser
XSS lets an attacker run JavaScript in another user's browser session, in the context of a site they trust. Here are the three types, what they enable, and how to stop them.
You leave a comment on your favourite forum. "Great post, thanks!" A day later, someone else reads a comment on that same thread, and without clicking anything, without typing anything, their logged in account quietly starts following a stranger, or their session token is copied off to a server on the other side of the world. They did nothing wrong. They simply loaded a page. The trap was a comment left earlier by an attacker, a comment that was not really text at all but a small program in disguise, and the forum served it up to every visitor as if it were part of the site itself.
That is Cross Site Scripting, almost always shortened to XSS. It is one of the oldest and most persistent web vulnerabilities, and it still turns up constantly. The idea is deceptively simple: get your own code to run inside another person's browser, on a website they trust. Once your code runs there, you can act as them. This is a breakdown of how it happens and how it is stopped.
Scope
XSS is a well established vulnerability class, understood and catalogued by the security community for over two decades. This post explains how it works for defenders and learners, on applications you own or are authorised to test.
A quick word on the pieces
Two plain definitions will carry the whole article. JavaScript is the programming language that runs inside your web browser. It is what makes pages interactive: the menu that drops down, the like button that lights up, the form that warns you before you submit. Crucially, JavaScript on a page can read and change almost anything about that page, including your cookies and your logged in session. A cookie is a small piece of data the site stored in your browser, often the token that proves you are signed in. Hold those two facts together and the danger becomes obvious: if an attacker can run their own JavaScript on a page you trust, they can reach the same cookies and session that page can. XSS is the art of smuggling that JavaScript in.
The core idea
A web page mixes together two things: the site's own trusted content, and data that came from users. Your comment, your username, your search term. XSS happens when data from an attacker is treated as part of the page's code rather than as plain text to be displayed. The browser has no way to tell the difference. It receives a stream of HTML and script and simply runs whatever is marked as runnable, so it executes the attacker's script with all the trust and access of the real site.
The site meant to show: Hello, <username>
The attacker's username: <script>steal(document.cookie)</script>
The browser runs it as if the site wrote it.The site expected a name to slot into that greeting. Instead it got a <script> tag, which is the HTML instruction that means "run this code". Because the site pasted the username straight into the page, the browser sees a real script and obeys it. The word "steal" there is standing in for whatever the attacker wants: copy the cookie, redirect the user, rewrite the page. The mechanism is the same every time.
This is the identical mistake at the heart of SQL injection, where user input becomes part of a database command. Here it becomes part of the browser's code instead. Same root cause, different room in the house.
The three types
XSS comes in three flavours, and they are grouped by how the malicious script reaches the victim. The distinction matters because each one is delivered differently and defended against with slightly different emphasis.
Stored XSS
The attacker's script is saved on the server, tucked into a comment, a profile bio, a product review, a support ticket, anywhere the site stores text and later shows it to others. From then on, every user who views that content has the script run in their browser, automatically, just by loading the page. This is the most dangerous type, because it needs no trick to lure the victim and it can hit thousands of people from a single planted comment. The forum story at the top of this article is stored XSS.
Reflected XSS
Here the script is not saved anywhere. It rides along in a single request, most often tucked into part of a URL, and the server reflects it straight back into the response page without cleaning it. Because nothing is stored, the attacker has to get each victim to make that specific request, usually by tricking them into clicking a crafted link in an email or message. But once they click, the script runs.
https://example.com/search?q=<script>...</script>
The server echoes q back into the results page unescaped.The site takes your search term q and prints it back ("You searched for..."), but it prints the raw <script> along with it, and the browser runs it. The malicious link looks like an ordinary search on a site the victim trusts, which is exactly why people click.
DOM based XSS
This one is subtler because the server may never see the malicious data at all. The DOM (Document Object Model) is the browser's live, in memory model of the page, the thing JavaScript reads and rewrites as you interact. In DOM based XSS the vulnerability lives entirely in the page's own browser side JavaScript. The site's script takes something the attacker controls, such as text from the URL, and writes it into the page in an unsafe way, all within the browser. Because the dangerous step happens after the page has loaded, defences that only inspect what the server sends can miss it completely.
What XSS lets an attacker do
Running script in the victim's session is a lot of power, because that script inherits everything the trusted page can touch. In practice it can:
- Steal session cookies or tokens, then use them to log in as the victim from elsewhere, hijacking the account.
- Act as the victim directly, clicking buttons, posting content, changing the email on the account, or transferring settings, all from inside their own authenticated session so it looks entirely legitimate.
- Capture keystrokes typed on the page, including passwords or card numbers entered into a form.
- Rewrite the page to display a convincing fake login prompt, phishing for credentials that appear to belong to the real site because, in the browser's eyes, they do.
It runs with the site's trust
The script executes as if the legitimate site wrote it, so it inherits that site's access to cookies, storage, and the logged in session. Your browser's security model assumes code on a page came from that page's owner, and XSS breaks exactly that assumption. That is why a single XSS on a banking or admin page is so serious: the attacker is not knocking on the door, they are already inside, wearing the site's uniform.
Stopping it
The root cause is always the same, untrusted data ending up where the browser expects code, so every defence is about keeping that line clear. As with SQL injection, there is a primary fix and then supporting layers that catch what slips through.
- Encode output for its context. This is the primary fix. Encoding (also called escaping) means converting characters that have special meaning into a harmless display form before you place user data into the page. For example, turning a
<into the sequence<so the browser shows a literal less than sign instead of starting a tag. The catch is that "context" matters enormously: data going into ordinary HTML, into an HTML attribute, into a URL, or into a piece of JavaScript each needs a different encoding. Get the context right and the attacker's<script>shows up on screen as harmless text rather than running.
Attacker sends: <script>steal()</script>
Encoded output: <script>steal()</script>
The browser displays the text, it does not run it.- Use a framework that escapes by default. Modern front end frameworks such as the popular component based ones encode values automatically when you insert them into a page. As long as you do not deliberately reach for the "insert this as raw HTML" escape hatch that these frameworks provide, they prevent the vast majority of XSS for you.
- Set a Content Security Policy. A Content Security Policy (CSP) is a rule you send with the page that tells the browser which sources of script are allowed to run. With a strong policy in place, even a script the attacker manages to inject is often refused execution because it did not come from an approved source. It is a strong safety net rather than a first line, but it turns many would be disasters into non events.
- Protect cookies with HttpOnly. Marking a session cookie as
HttpOnlytells the browser that JavaScript is not allowed to read it at all. Cookie theft, the classic XSS payoff, is blunted even if a script does run, because the one thing the attacker most wants is now out of reach of their code. - Validate input, but treat it firmly as a supporting measure. Rejecting obviously malformed input is good hygiene, yet clever attackers find encodings and phrasings that pass a filter and still execute. Output encoding is what actually neutralises XSS. Validation just trims the noise at the door.
Encode on the way out, not just on the way in
A common trap is to clean data once, when it arrives, and assume it is safe forever. But the same stored value might later be dropped into HTML on one page and into a URL on another, each needing different treatment. The reliable habit is to encode at the moment of output, for the exact context it is going into. That way the data is made safe for the place it actually lands, every single time.
The takeaway
XSS is what happens when attacker data is treated as page code, letting a script run in a victim's browser with the trusted site's privileges. It comes in stored, reflected, and DOM based forms, but they all trace back to the same mistake and they all lead to the same danger: hijacked sessions and convincing impersonation of the site to its own users. The cure is disciplined, context aware output encoding, backed by a framework that escapes by default, a Content Security Policy, and HttpOnly cookies to limit the fallout.
Keep user data as text and it can never become code. That is the whole game, and it is the same principle that shuts down SQL injection on the database side. Learn to see the line between data and code, and you will start defending both instinctively.