MarzleyTech Learn

Home / Learn / Advanced cybersecurity: defend, detect and respond like a professional / Secure web development: the OWASP Top 10, injection, XSS, secure coding and testing your own apps

Secure web development: the OWASP Top 10, injection, XSS, secure coding and testing your own apps

Websites and apps are the front door of almost every organisation. This lesson goes deeper than the web security basics: how the most common flaws happen in code, and how developers prevent them.

The OWASP Top 10

OWASP (Open Worldwide Application Security Project) is a free, community-run foundation. Its Top 10 lists the most critical web application risks. The 2021 edition:

#RiskIn plain words
A01Broken access controlUsers can see or do things they shouldn't
A02Cryptographic failuresSensitive data not encrypted, weak algorithms
A03InjectionUntrusted input treated as code (SQL, commands, XSS)
A04Insecure designSecurity not planned from the start
A05Security misconfigurationDefault passwords, debug mode on, open storage
A06Vulnerable and outdated componentsOld libraries and plugins with known flaws
A07Identification and authentication failuresWeak login, session problems
A08Software and data integrity failuresUntrusted updates, insecure pipelines
A09Security logging and monitoring failuresAttacks happen and nobody notices
A10Server-side request forgery (SSRF)Server tricked into fetching internal URLs

SQL injection: see why parameters matter (run it)

This lab uses a tiny local database in your browser. It shows how gluing user input into SQL changes the query's meaning, and how parameterised queries stop it.

Python · runs live in the interactive lesson
import sqlite3

db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE staff (username TEXT, salary INTEGER)")
db.executemany("INSERT INTO staff VALUES (?, ?)",
               [("wanjiku", 85000), ("otieno", 92000), ("akinyi", 78000)])

user_input = "nobody' OR '1'='1"

# UNSAFE: input glued into the SQL text
unsafe_sql = "SELECT username, salary FROM staff WHERE username = '" + user_input + "'"
print("Unsafe query:", unsafe_sql)
print("Rows returned:", len(db.execute(unsafe_sql).fetchall()))

# SAFE: a parameterised query treats the input only as data
safe_rows = db.execute("SELECT username, salary FROM staff WHERE username = ?",
                       (user_input,)).fetchall()
print("Rows returned with parameters:", len(safe_rows))

The unsafe version returns all 3 rows because OR '1'='1' is always true. The parameterised version returns 0 rows: the database looks for a user literally named nobody' OR '1'='1.

Prevention: parameterised queries or prepared statements (PDO in PHP, ? placeholders in Python), ORMs used correctly, least-privilege database accounts, and input validation.

Cross-site scripting (XSS)

XSS happens when a site shows user input as HTML, so an attacker's script runs in other users' browsers (stealing sessions, changing pages).

Python · runs live in the interactive lesson
import html

comment = '<script>alert("hacked")</script>Nice article!'
print("Raw (dangerous if put into HTML):", comment)
print("Escaped (safe to display):       ", html.escape(comment))

Prevention:

  • Output encoding/escaping for the context (HTML, attributes, JavaScript, URLs). Modern frameworks (React, Vue, Laravel Blade, Django templates) escape by default; avoid bypasses such as innerHTML or dangerouslySetInnerHTML with user data.
  • A Content Security Policy (CSP) header to limit which scripts can run.
  • HttpOnly cookies so scripts can't read session cookies.

Other common flaws

FlawPrevention
CSRF (forcing a logged-in user's browser to submit actions)CSRF tokens, SameSite cookies
Command injectionNever pass user input to shell commands; use safe library functions
Insecure file uploadsCheck type and size, rename files, store outside the web root, never execute uploads
SSRFAllow-list destinations, block internal addresses
Insecure direct object referencesCheck ownership on every request
Secrets in codeEnvironment variables or a secrets manager; scan repositories

Secure development lifecycle

  1. Plan: threat modelling ("what could go wrong?" STRIDE is a popular method: Spoofing, Tampering, Repudiation, Information disclosure, Denial of service, Elevation of privilege).
  2. Code: secure coding guidelines, code review, frameworks with safe defaults.
  3. Test: SAST (static analysis of code), DAST (scanning the running app), dependency scanning (Dependabot, npm audit, pip-audit).
  4. Deploy: security headers, HTTPS, no debug mode, secrets management.
  5. Operate: logging, monitoring, patching, a vulnerability disclosure contact (security.txt).

Security headers checklist

HeaderPurpose
Strict-Transport-SecurityAlways use HTTPS
Content-Security-PolicyLimit scripts and resources
X-Content-Type-Options: nosniffStop MIME sniffing
X-Frame-Options or CSP frame-ancestorsStop clickjacking
Referrer-PolicyLimit leaked URLs
Think about it: A developer says "we validate input in JavaScript before the form submits, so the server doesn't need to check it." What's wrong?Show answer

Client-side checks can be bypassed easily (by editing requests or calling the API directly). They help usability, but the server must validate input, use parameterised queries, encode output and check authorisation on every request.

Summary

  • The OWASP Top 10 lists the most critical web risks; broken access control tops the 2021 list.
  • Parameterised queries stop SQL injection; output encoding and CSP stop XSS.
  • Build security into every stage: threat modelling, secure code, SAST/DAST, dependency scanning, secure deployment.
  • Test only your own or authorised apps.

Check yourself

  1. What stops SQL injection? (one word: ___ queries)

    Show answer

    parameterised

  2. Which attack runs an attacker's script in other users' browsers? (acronym)

    Show answer

    xss

  3. Which header limits which scripts a page may run? (acronym)

    Show answer

    csp

Lesson 5 of 13 in Advanced cybersecurity: defend, detect and respond like a professional · Written by · Course notes