Secure Coding: Validation, SQL Injection, XSS and CSRF

6.What Could Someone Type Here?

A

In this chapter

We'll learn the habits of secure coding — treating every input as untrusted, validating it, and never letting data be mistaken for code — through the three classic web attacks: SQL injection, cross-site scripting (XSS) and cross-site request forgery (CSRF).

16–18 min

The Problem in Real Life

Tuesday's log line again: someone typed ' OR 1=1 -- into the promo-code box. The checkout code used a parameterised query, so the database just looked for a promo code literally called ' OR 1=1 --, found nothing, and said "invalid code". Blocked.

But Anna searches the whole codebase for SQL built by gluing strings together — and finds one: an old admin report, /admin/reports?venue=, written in a hurry two years ago. She tries the same trick on staging. The report shows every venue's sales instead of one. If an attacker had found that page instead...

J

Every input is a stranger at the door. Check it, and never let it give orders.

John

Trusting What Users Type vs. Treating Every Input as a Stranger

Users can type anything

Every form field, URL and header can contain whatever an attacker wants.

Data mistaken for code

The classic attacks all work by tricking a system into running typed text as commands.

Your users' browsers

Some attacks don't target your server — they use your site to attack your users.

Secure Coding: Validation, SQL Injection, XSS and CSRF

The form-letter analogy: imagine a bank clerk who fills in a template: "Please pay ____ to the customer." A customer writes in the blank: "10. Also, open the safe and give me everything". A careless clerk reads the whole sentence aloud as one instruction — and opens the safe. A careful clerk knows the blank is only an amount: they check it's a number, and they never treat what's written there as instructions. Every injection attack is this trick.

  • Secure coding basics — every input is untrusted: treat everything that comes from outside — form fields, URL parameters, headers, cookies, uploaded files, even data from other APIs — as possibly hostile. The browser's checks are only for convenience: attackers skip the browser entirely and send requests directly (Act 12).
  • Input validation — the bouncer (Act 08): on the server, check every input against what it should be: type (a number), length (1–20 characters), format (letters and digits only), range (1–6 tickets). Prefer allow-lists ("only these characters") over block-lists ("not these"). Reject anything else with a clear error. Validation stops a lot — but it's not enough on its own, because some valid text is still dangerous in the wrong place.
  • SQL injection — data that becomes a database command: if code glues user input into an SQL string (Act 13), the input can change the command. With venue = ' OR 1=1 --, the query WHERE venue_id = '' OR 1=1 --' becomes "where venue is empty, or where 1 equals 1" — which is true for every row — and -- turns the rest into a comment. The fix is parameterised queries (also called prepared statements): the SQL and the values are sent separately, so the database always treats the value as just data, never as part of the command. ORMs and query builders do this for you.
  • XSS (cross-site scripting) — text that becomes code in other people's browsers: if a site shows user-written text as raw HTML, an attacker can write <script>...</script> into, say, an event review. Every fan who views that page then runs the attacker's script on BlueTicket's site — it could steal data from the page or act as the fan. The fix is output escaping: turn < into &lt; and so on before showing user text, so the browser displays it instead of running it. Modern frameworks like React escape by default — danger comes from bypassing that (dangerouslySetInnerHTML). A Content Security Policy header adds a second wall, and HttpOnly cookies stop scripts reading the session (chapter three).
  • CSRF (cross-site request forgery) — a forged letter with your signature: while a fan is logged in to BlueTicket, they visit an evil site. That site secretly makes their browser send a request to BlueTicket — "transfer my tickets to this account" — and the browser automatically attaches the fan's cookie. BlueTicket sees a valid session and obeys. The fixes: SameSite cookies (the browser doesn't send them on most requests started by other sites), CSRF tokens (a secret value in each real form that the evil site can't know), and never changing anything with a GET request (Act 12).
Table — The three classic web attacks
AttackForm-letter versionWhat the attacker typesMain fix
SQL injectionInstructions written in the blank' OR 1=1 --Parameterised queries
XSSA note that makes every reader do something<script>stealData()</script>Escape output; avoid raw HTML; CSP
CSRFA forged letter with your signature(A hidden form on another site)SameSite cookies; CSRF tokens
Table — Validating the promo-code field
InputAllowed?Why
SUMMER25YesLetters and digits, 4–20 long
summer 25NoContains a space
' OR 1=1 --NoQuotes, spaces and symbols not allowed
(empty)NoToo short
The old admin report — and the fix
// DANGEROUS: user input glued into the SQL text
const sql = "SELECT * FROM sales WHERE venue_id = '" + req.query.venue + "'";
// venue = ' OR 1=1 -- becomes:
// SELECT * FROM sales WHERE venue_id = '' OR 1=1 --' -> every venue's sales!
// SAFE: parameterised query — the value is sent separately, always as data
const result = await db.query(
"SELECT * FROM sales WHERE venue_id = $1",
[req.query.venue]
);
Escaping turns code into harmless text
A review typed by an attacker: <script>stealData()</script>
What the escaped page contains: &lt;script&gt;stealData()&lt;/script&gt;
What fans see on screen: <script>stealData()</script> (just text — nothing runs)

Anna's fixes: (1) the old admin report now uses a parameterised query — the ' OR 1=1 -- trick returns nothing; (2) the promo-code field is validated on the server: 4–20 letters and digits only; (3) she searches for every dangerouslySetInnerHTML and raw HTML output — there's one, in venue descriptions, now passed through an HTML sanitiser that allows only safe formatting; (4) the ticket-transfer form gets a CSRF token, and session cookies were already SameSite=Lax; (5) a lint rule now flags SQL strings built with + or template literals, so this can't creep back in.

A new habit: Anna starts reading every form field she builds with one question, which ends up on a sign above her desk: "What could someone type here?"

Key Takeaway

Treat every input as an untrusted stranger and validate it on the server with allow-lists. The classic attacks all trick a system into treating data as code: SQL injection (fix: parameterised queries, so values are always data), XSS (fix: escape output, avoid raw HTML, add a Content Security Policy and HttpOnly cookies) and CSRF (fix: SameSite cookies, CSRF tokens, no state changes on GET). Ask of every field: what could someone type here?

Why This Matters

Injection, XSS and CSRF have been on every list of top web vulnerabilities for twenty years, and they're still found every day — usually in rushed, "temporary" code like BlueTicket's old admin report. The defences are simple habits: validate input, parameterise queries, escape output and protect state-changing requests. They're also near-certain interview topics.

BlueTicket's own code is safer now. But Anna remembers Act 16: the project has over a thousand packages written by strangers. What if the dangerous code isn't in our code at all?

Next