← All lessons
Cybersecurity from zero · Lesson 9 of 13

Web app security

Websites and apps are a big part of every company's attack surface, and the same few mistakes cause most of the trouble. OWASP, a non-profit, keeps a widely used Top 10 list of web app risks; broken access control sits at the top.

Broken access control means the app doesn't check whether this user may do this thing. If /invoices/1041 shows your invoice, can you change it to /invoices/1042 and see someone else's? The fix is that the server checks permission on every request, for every object, and never trusts the browser to hide what you shouldn't see.

Injection happens when user input is glued into a command, so the input becomes part of the command. SQL injection is the classic: a search box value pasted into a database query. The fix is parameterised queries: the query and the data are sent separately, and the database never runs the data as code.

import sqlite3
db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE users (name TEXT)")
db.executemany("INSERT INTO users VALUES (?)", [("ana",), ("ben",)])
name = "nobody' OR '1'='1"
# unsafe: the input is pasted into the query
print(db.execute(f"SELECT name FROM users WHERE name = '{name}'").fetchall())
# safe: ? keeps the input as data
print(db.execute("SELECT name FROM users WHERE name = ?", (name,)).fetchall())
Prints
[('ana',), ('ben',)]
[]

The unsafe line returns every user, because the input's quote mark closed the text and OR '1'='1' became part of the query. The safe line looks for a user literally called that, and finds none.

Cross-site scripting (XSS) is injection into a web page: someone posts a comment containing a script, and the site shows it to other visitors, whose browsers run it. The fix is to escape output, turning characters like < into harmless text, which modern web frameworks do by default. A Content-Security-Policy header adds a second layer by telling the browser which scripts are allowed.

import html
print(html.escape("<script>steal()</script>"))
Prints
&lt;script&gt;steal()&lt;/script&gt;

A few more habits: check input on the server (the browser's checks are for convenience only), keep secrets like API keys out of code, mark session cookies Secure and HttpOnly (sent only over HTTPS, hidden from page scripts), and keep libraries updated. Only test sites you own or have written permission to test.

Practice

Changing /orders/500 to /orders/501 in the address bar shows another customer's order. What's the flaw?

More info

Quick reference

Broken access controlThe server doesn't check if this user may do this.
SQL injectionInput becomes part of a query; fix with parameters (`?`).
XSSInput becomes script in other people's pages; fix by escaping output.
Server-side checksNever trust the browser to enforce rules.
Secure, HttpOnlyCookie only over HTTPS; hidden from page scripts.
OWASP Top 10A widely used list of the most common web app risks.

Related courses