CTCaliberTek
Course: Application Security: Step by Step · Lesson 3 of 7Log in to track your progress and earn a certificate when you finish the course.
Cybersecurity

Preventing SQL Injection and Cross-Site Scripting (XSS)

Published July 21, 2026·Updated October 29, 2026·10 min read
Like SaveLog in to like, save, and track progress.

Introduction

Injection flaws happen when an application mixes untrusted data into something that is interpreted as code: a SQL command or a web page. They are well understood and entirely preventable. This lesson shows the vulnerable pattern briefly, then focuses on the fixes recommended by the OWASP cheat sheets.

Prerequisites

  • Basic Python and SQL
  • Basic HTML and JavaScript

Step-by-Step Guide

Step 1: Understand SQL injection

If you build a query by concatenating user input into a string, a crafted input can change the structure of the query, for example by closing a quote and adding extra conditions, letting an attacker read or change data they should not. The cause is always the same: the data and the command are mixed. The fix is to keep them separate.

import sqlite3

conn = sqlite3.connect("app.db")
cur = conn.cursor()

# VULNERABLE: never build SQL by joining strings with user input
# cur.execute("SELECT * FROM users WHERE name = '" + name + "'")

# SAFE: the query is fixed and the value is passed separately
cur.execute("SELECT * FROM users WHERE name = ?", (name,))

Step 2: Make parameterized queries your default

OWASP's primary defense is prepared statements with parameterized queries, available in every language and database library. The database treats parameters as data only, whatever they contain. Stored procedures are safe only if they do not build SQL dynamically. Escaping input yourself is strongly discouraged. Also run your application's database account with the minimum privileges it needs.

// Java: prepared statement
String query = "SELECT account_balance FROM user_data WHERE user_name = ?";
PreparedStatement pstmt = connection.prepareStatement(query);
pstmt.setString(1, customerName);
ResultSet results = pstmt.executeQuery();

Step 3: Allow-list what cannot be parameterized

Table names, column names, and sort directions cannot be bound as parameters. Map the user's choice to a fixed value from a list you control, and reject anything else.

SORT_COLUMNS = {"name": "name", "date": "created_at"}

column = SORT_COLUMNS.get(sort_param, "name")      # unknown values fall back safely
direction = "DESC" if order_param == "desc" else "ASC"
cur.execute("SELECT * FROM posts ORDER BY " + column + " " + direction)

Step 4: Understand and prevent XSS

Cross-site scripting happens when untrusted data is placed into a page in a way the browser runs as script, letting an attacker act as the user. Modern frameworks escape output automatically, so the main rule is to stay on the safe path. Problems appear when you bypass it, for example React's dangerouslySetInnerHTML, Angular's bypass functions, Jinja's safe filter, or assigning to innerHTML. No single defense is enough, so combine them.

// UNSAFE: untrusted text interpreted as HTML
// element.innerHTML = userComment;

// SAFE: treated as plain text
element.textContent = userComment;

Step 5: Encode output for its context

Encoding must match where the data lands. In an HTML body, convert the characters for less-than, greater-than, ampersand, and quotes to entities. In attributes, always quote and encode. In URLs, percent-encode parameter values. Never place untrusted data directly inside script blocks, style blocks, HTML comments, or tag and attribute names. If users must submit rich HTML, sanitize it with a well-maintained library such as DOMPurify and keep it updated.

Step 6: Add defense in depth

A strict Content Security Policy limits what injected script can do, though OWASP stresses it is an extra layer and not the main defense. Trusted Types can lock down dangerous browser sinks, and HttpOnly cookies keep session cookies away from scripts. Web application firewalls are not recommended as a primary XSS defense. Validate input on the server with allow-lists too, but remember validation does not replace encoding or parameterization.

FAQs

Is input validation enough to stop injection?
No. Validation reduces risk and catches bad data, but safe handling at the point of use, parameterized queries and output encoding, is what actually prevents injection.
Do ORMs make SQL injection impossible?
They help because they parameterize by default, but raw query features and string-built fragments can reintroduce the flaw. Review any place where you pass user input into raw SQL.

Summary

Keep data and code apart: parameterized queries for SQL, allow-lists for identifiers, framework escaping and context-aware output encoding for HTML, safe APIs like textContent, and CSP and sanitization as extra layers.

Related tutorials

Comments

Log in or create a free account to join the discussion.

  • No comments yet. Be the first to comment.