Lab 2: Stored XSS into HTML context with nothing encoded

Summary of the Lab (Conceptual, Safe)

The referenced PortSwigger lab demonstrates a stored (persistent) Cross-Site Scripting (XSS) vulnerability in an HTML context where no characters are encoded, meaning user input is placed directly into the page.

What the lab tries to teach

  • Stored XSS occurs when malicious input is saved by the application (e.g., in a comment, profile, or post).

  • When other users view that page, the malicious JavaScript runs in their browser.

  • Lack of HTML encoding/escaping makes this possible.

  • Proper output encoding and input handling prevent it.


Analogy (Simple Example)

Imagine a guestbook in a hotel lobby.
Anyone can write a message. The hotel displays messages exactly as written.

If a guest writes:

“Hello World!”

…it shows normally.

But if someone writes:

“Whenever someone reads this, switch off the lobby lights.”

…and the hotel staff blindly obeys whatever the message says, the entire lobby would react.

That’s similar to stored XSS—the system treats user input as commands, not text, and those commands activate for every viewer.


🔍 Conceptual Steps of the Exploitation (No technical how-to)

  1. The attacker submits a comment/message/profile field containing script-like content.

  2. The application stores this content without sanitizing or encoding it.

  3. When another user opens the page, the browser receives the stored content as part of HTML.

  4. The browser interprets it as active code, not text.

  5. The attacker’s code executes using the victim’s session context.

Again: The process above is high-level and avoids any actionable exploitation details.


⚠️ Sample Vulnerable Code (Safe, Illustrative Only)

Below is a simplified conceptual example showing why the vulnerability occurs.

❌ Vulnerable (no HTML escaping)

// Imagine this is rendering a stored comment directly into HTML
const comment = database.getComment();  // untrusted
document.getElementById("comments").innerHTML += "<p>" + comment + "</p>";

Problem:

  • The untrusted content is injected into HTML as-is.

  • Browsers treat it as active HTML/JavaScript, not plain text.


🛡️ How to Fix the Issue (Safe, Defensive)

✔️ 1. Output Encode (Escape) User Input

Output it as text, not HTML:

const comment = database.getComment(); // untrusted
const p = document.createElement("p");
p.textContent = comment;  // safely escapes everything
document.getElementById("comments").appendChild(p);

This ensures:

  • <script> becomes harmless text like &lt;script&gt;

  • No browser interpretation as code


✔️ 2. Use Server-Side Escaping (Example: Node.js/Express + Template Engine)

Using EJS/Handlebars/Pug (auto-escaping)

<p>{{comment}}</p>

If manual encoding is required

function escapeHTML(str) {
  return str
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;");
}

res.send(`<p>${escapeHTML(comment)}</p>`);

✔️ 3. Implement Content Security Policy (Defense-in-Depth)

Limit execution of inline scripts:

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

CSP does not replace encoding but greatly reduces impact if encoding fails.


🟢 Final Takeaway

Stored XSS happens when a website stores untrusted input and later renders it directly into HTML without encoding.
Fixes rely on ensuring user data is always treated as text, not code.


References:

https://portswigger.net/web-security/cross-site-scripting/stored/lab-html-context-nothing-encoded


Comments

Popular posts from this blog

SQL Injection Attacks | Shahul Hameed

To use emulator(Using NOX emulator): Open Appie Application | Shahul Hameed

Pentest - Web Application Vulnerability Scanner | Shahul Hameed