Web application security: the OWASP Top 10:2025
Lesson 3 of 4
3Web application security: the OWASP Top 10:2025
The big pictureA web app is like a receptionist who does whatever is written on a form. If nobody checks what is written, a scammer can write "and give me every customer's file" and it gets done. And the most common problems are not clever: a page that shows another customer's data when you change a number in the link.
What the picture shows: The same request with one number changed. If the server does not ask "is this invoice yours?" every time, any user sees everyone's invoices. This is number one on the list.
Map of this part
The concepts in order, click any one to jump to it:
Two ways into the same idea
The golden rule: trust nothing that comes from the user. The number in the link, the text in the box, the uploaded file can all be traps. The server must check for itself every time: who you are, whether you are allowed, and whether what you typed is innocent data or a disguised command.
OWASP Top 10:2025: A01 Broken Access Control, A02 Security Misconfiguration, A03 Software Supply Chain Failures (new and wider), A04 Cryptographic Failures, A05 Injection (including XSS), A06 Insecure Design, A07 Authentication Failures, A08 Software or Data Integrity Failures, A09 Security Logging and Alerting Failures, A10 Mishandling of Exceptional Conditions (new). Defences: server-side authorisation with deny by default, parameterised queries, context-aware output encoding and CSP, SBOMs and pinned versions, threat modelling, and failing closed. In Darb, the "Application security" part of stage 3 goes deeper into code.
Before You Start
🌐Servers and APIs (stage 1)
You know what a request, a response and a database are.
🔑Identity and passwords
Authentication and sessions.
The Core Idea
1The full list, one example each
The OWASP list is the best-known reference for the ten most serious categories in web apps, refreshed every few years from real testing data. Do not memorise the numbers; understand the idea and the defence behind each one.
| Code | Category | Quick example | Core defence |
|---|---|---|---|
| A01 | Broken Access Control | Changing /invoice/1001 to 1002 shows someone else's | Check permission on the server per request; deny by default |
| A02 | Security Misconfiguration | An exposed admin panel or a public storage bucket | Hardened standard settings and automated review |
| A03 | Software Supply Chain Failures | A compromised npm package enters your project | An SBOM, pinned versions, dependency scanning |
| A04 | Cryptographic Failures | Passwords stored with MD5 | Modern algorithms and proper key management |
| A05 | Injection | SQL, OS command or XSS injection | Parameterised queries and output encoding |
| A06 | Insecure Design | Password recovery via a question whose answer is public | Threat modelling before building |
| A07 | Authentication Failures | No limit on sign-in attempts | MFA, rate limits and safe sessions |
| A08 | Software or Data Integrity Failures | An update installed without checking its signature | Signing and verifying in the pipeline |
| A09 | Security Logging and Alerting Failures | An attack ran for months because nobody watched the logs | Central logs and actionable alerts |
| A10 | Mishandling of Exceptional Conditions | An error leaks database details, or a system "opens" when it fails | Uniform error handling and failing closed |
2A01: broken access control
This is number one because it is the most common and the easiest to exploit: change a number and you are in. The usual mistake is hiding the button in the interface and assuming users will never reach the page. But attackers do not use your interface; they send the request directly. The check belongs on the server, on every request.
3A05: injection, and try it yourself
Injection happens when user text goes into a command that gets executed, so part of it turns into commands. The rule for every kind: keep commands apart from data.
The unsafe query returned every row; the parameterised one returned nothing because the text stayed text. Change typed to Sara's email and run.
import sqlite3
db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE users (email TEXT, secret TEXT)")
db.executemany("INSERT INTO users VALUES (?, ?)",
[("sara@example.com", "salary 18,000"), ("omar@example.com", "salary 21,500")])
typed = "' OR '1'='1" # what an attacker types in the email box
unsafe = "SELECT * FROM users WHERE email = '" + typed + "'"
print("unsafe:", db.execute(unsafe).fetchall())
safe = db.execute("SELECT * FROM users WHERE email = ?", (typed,)).fetchall()
print("safe: ", safe)And XSS is the same injection but in the browser: user text gets shown on the page as if it were JavaScript. The defence is encoding output for where it is displayed, plus a Content Security Policy (CSP).
4New in 2025: the supply chain and failing safe
A modern project is often 80% or more code written by others: npm and pip packages. A03 says one compromised package is enough. Defence: know what you use (an SBOM), pin versions, and scan dependencies automatically. The new A10 is about errors: a system that "opens" when it breaks, like an electronic door that unlocks in a power cut, is dangerous. Fail closed.
Worked example
The task: in a shop app, the order link is /orders/5521. Design a check that prevents A01.
The answer: on the server, after identifying the user from the session (not the link), fetch the order and compare its owner with the current user: if they differ, return 404 rather than 403 so you do not reveal it exists. Put this check in one place every route passes through, and write an automated test that tries to open another user's order and expects a refusal.
Myth vs Reality
| Myth | Reality |
|---|---|
| Hiding the button in the UI is enough to block access. | Attackers send requests directly without your UI; the check belongs on the server. |
| A modern framework protects me from everything. | It blocks some injection by default, but it does not know who owns which order, and it cannot stop a bad setting. |
Where you meet it at work
🧑💻Developer
Writes ownership checks and parameterised queries from day one, and scans dependencies in CI.
🐞Bug hunter
Tries changing IDs and inputs in authorised bug bounty programmes.
Test your understanding
Changing the invoice number in the link to see another customer's invoice is an example of:
The server did not check that the invoice belongs to the current user. It is A01 in OWASP 2025.
The best defence against SQL injection:
A parameterised query separates command from data at the root. The rest are helper layers.
Which category is new in 2025 and concerns how a system behaves on errors?
A10 covers errors that leak details or make a system fail open.
What is the core defence against SQL injection?
Data travels separate from the command so it never runs.
A user changes the order number in the URL and sees someone else's order. Which category?
This is IDOR: the server did not check ownership.
Quick review cards
Check Your Understanding
Why return 404, not 403, when a user asks for someone else's order?
So you do not confirm to the attacker that it exists, and they cannot tell which numbers are real.
What rule do all kinds of injection share?
Keep commands apart from data, validate input on the server, and encode output for where it is shown.
Why is hiding the "delete" button from normal users not enough?
Because an attacker sends the request directly without the interface. Permission is checked on the server with every request.
Resources for this lesson
Grouped by type. Start with the official docs for precision, or an article or video for the explanation.
Done with this lesson?
Sign in to save your progress.