An Introduction to Web Security: What Is the OWASP Top 10:2025 and How to Read It
Why security can't be a feature added later. We start with a small endpoint that returns another customer's order, then cover the CIA triad and the attack surface, what OWASP is, why the Top 10 is an awareness document rather than a standard, how the list is built from data and a survey, and the ten categories of the 2025 list.
Version note: This post follows the OWASP Top 10:2025; the code was tested with ASP.NET Core 10 (.NET 10).
Guess First
You are writing a bookstore app. You added this endpoint so customers can check the status of their order. The code compiles, the tests pass, and Alan can see his own order without any trouble:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
List<Order> orders =
[
new(1, "alan", "Clean Code", 1),
new(2, "grace", "The Pragmatic Programmer", 2)
];
// The "show my order" endpoint
app.MapGet("/orders/{id:int}", (int id) =>
{
Order? order = orders.FirstOrDefault(o => o.Id == id);
return order is null ? Results.NotFound() : Results.Ok(order);
});
app.Run();
record Order(int Id, string Customer, string Book, int Quantity);
Alan's order number is 1. What happens if Alan changes the 1 in the address bar to a 2? Think about it for a second, then look at the output:
$ curl -i http://localhost:5101/orders/1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Fri, 06 Nov 2026 18:58:10 GMT
Server: Kestrel
Transfer-Encoding: chunked
{"id":1,"customer":"alan","book":"Clean Code","quantity":1}
$ curl -i http://localhost:5101/orders/2
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Fri, 06 Nov 2026 18:58:10 GMT
Server: Kestrel
Transfer-Encoding: chunked
{"id":2,"customer":"grace","book":"The Pragmatic Programmer","quantity":2}
Grace's order came back. The app never cared who was asking. There is no syntax error in the code, no exception, no red line in the log. Functionally, the endpoint "works". From a security point of view, it has a vulnerability that fits the number one category of the OWASP list, Broken Access Control, exactly. This series exists so you can spot this kind of bug while you are writing the code.
Why Security Can't Be Added Later
There is a sentence you hear a lot on projects: "Let's finish the features first, we'll look at security before the release." That sentence treats security like a module you plug in afterwards. But the bug above is not a missing module. The endpoint's signature has no user identity, and the query in the data layer has no ownership condition. To fix it, you have to go back to that endpoint, that query and probably the client code that calls it. If fifty endpoints were written with the same pattern, you go back to fifty places.
Think of a building. Fire escapes, door widths and wiring are drawn at the design stage. Adding a fire escape after the building is finished is possible, but it is expensive, ugly and rarely fits well. In software, decisions about authentication, authorization, input validation and error handling are made together with the first endpoint. Added later, they either stay incomplete or get forgotten somewhere.
The CIA Triad and the Attack Surface
The classic answer to "what are we protecting?" in security is three words, known by their initials as the CIA triad. Confidentiality: only the people who should see the data can see it. Integrity: only the people who should change the data can change it, and only in allowed ways. Availability: the system works when it is needed.
The opening example breaks confidentiality: Alan sees Grace's order. If there were a PUT /orders/2 endpoint written the same way, integrity would go too; Alan could change the quantity on Grace's order. A search endpoint that accepts unlimited requests threatens availability; a single client can keep the database busy and make everyone else wait. Asking "which of the three does this break?" while reading about a vulnerability is the fastest way to understand how big the risk is.
The second core idea is the attack surface: every point where the application accepts data from the outside world or gives data to it. Every endpoint, every query string parameter, every header, every cookie, every uploaded file and every response read from an external service is part of that surface. The first and cheapest step in security is shrinking the surface: close what you don't use, remove it, or never open it.
OWASP and the Top 10: Not a Standard, an Awareness Document
OWASP is a nonprofit foundation, the OWASP Foundation, that runs open source projects on application security. Its documents, tools and guides are free for everyone. Its best-known project is the OWASP Top 10, a list of the most critical risk categories in web applications. Next to it are projects such as the Cheat Sheet Series (practical guides topic by topic), ASVS (Application Security Verification Standard) and the API Security Top 10; we will look at those separately in the last post of the series.
The most important thing to know about the Top 10 is what it isn't. OWASP itself describes the Top 10 as an awareness document. There is no "Top 10 compliant" certificate, and a vulnerability that is not on the list is not missing because it is harmless. The list exists to put the most common and most damaging categories in front of developers. If you want to verify an application systematically, the document OWASP recommends is ASVS; its current stable version, ASVS 5.0.0, was published on 30 May 2025.
How the List Is Made
The Top 10 is not the result of a poll or a few experts' intuition. The 2025 edition is the eighth installment of the OWASP Top 10. Behind it is testing data donated by security firms and organizations; the 2025 dataset covers more than 2.8 million applications. Findings are classified with CWE (Common Weakness Enumeration). CWE is the shared dictionary of weakness types maintained by MITRE: CWE-89 is SQL injection, CWE-79 is XSS, and CWE-639 is the kind from the opening, bypassing authorization through a user-controlled key.
Each Top 10 category groups several CWEs. In 2025, the ten categories cover 248 CWEs in total; A01 Broken Access Control alone has 40. Eight of the categories come from the data. The other two, A03 Software Supply Chain Failures and A09 Security Logging and Alerting Failures, were picked by the community survey. The reason for the survey is simple: testing data shows what automated tools can easily find today. Topics like supply chain attacks or missing logging are risks that the data doesn't capture well yet but that hurt in the field.
Since 2021, categories are named after the root cause wherever possible, not the symptom. "Sensitive data exposure" is an outcome; the root cause behind it is usually cryptography that is wrong or missing. That is why 2017's Sensitive Data Exposure was renamed Cryptographic Failures in 2021. We will talk about this evolution separately in the last block of the series.
A Bird's-Eye View of the OWASP Top 10:2025
The 2025 list became final at the end of December 2025. We will open up each category in its own post; for now, one sentence each:
A01:2025 Broken Access Control: a user reaching data or functions they are not allowed to. The opening example is exactly this. In 2025, SSRF joined this category too.
A02:2025 Security Misconfiguration: detailed error pages left on in Production, default settings, missing security headers. XXE lives here as well.
A03:2025 Software Supply Chain Failures: risks that come in through the packages you use and your build and delivery pipeline.
A04:2025 Cryptographic Failures: weak algorithms, plain text passwords, predictable random numbers, data carried without encryption.
A05:2025 Injection: user data leaking into a language such as SQL, a shell command or HTML as code. SQL injection and XSS are in this category.
A06:2025 Insecure Design: flaws that come from the design itself even when the code is written correctly, such as orders with a negative quantity or unlimited attempts.
A07:2025 Authentication Failures: logins open to brute force, weak password rules, badly managed sessions and tokens.
A08:2025 Software or Data Integrity Failures: code and data trusted without verification; insecure deserialization, mass assignment.
A09:2025 Security Logging and Alerting Failures: missing logs or alerts that would let you notice an attack, or the wrong things being written to the log.
A10:2025 Mishandling of Exceptional Conditions: the new category of 2025. Code that "fails open" on errors, leaks stack traces or doesn't roll back an operation left half done.
The Ethical Line and How the Series Works
This series is about defense. I will show every attack only against the lab application running on your own machine, against localhost, with the smallest example that proves the problem, and each demonstration will be followed right away by its fix. Only try these techniques on systems you own or have written permission to test. Trying them on someone else's system without permission is a crime in most countries, however "innocent" the ' OR 1=1 -- may be. The passwords and keys in the examples will be obviously fake too: values starting with dev-only-....
The first block of the series, posts 1–17, is the backbone. In the next post we set up our lab application, a small "Bookstore API" written with ASP.NET Core 10: a minimal API on SQLite with users, books, orders and reviews tables. Then, for every 2025 category, we follow the same pattern: vulnerable code, how the attack works, fixed code, the defense ASP.NET Core or the browser offers, and a short checklist. Post 17 puts it all together. The second block, posts 18–21, is about how the list evolved: the 2017 list, the moves from 2017 to 2021 and from 2021 to 2025, and OWASP's other projects.
The topics will touch other series on the blog here and there. For SQL injection we will look back at the raw SQL and parameter posts in the EF Core and Dapper series, for leaked secrets at the Git & GitHub series, and for authorization and CORS at the ASP.NET Core Web API series. There we saw "how to do it"; here we focus on "how to do it safely".
Common Pitfalls
The first pitfall is thinking "who would target our app anyway?" Most attacks don't come from someone who knows you but from automated tools that try every address on the internet.
The second is leaving confidentiality to IDs being hard to guess. Using a Guid instead of a sequential int makes guessing harder, but it doesn't replace authorization. An ID leaks in a log, an email or a shared link, and the moment it leaks the door is open again. The check always happens on the server, with the question "can this user see this record?"
The third is taking a check in the frontend for security. Hiding the admin menu in Angular is user experience, not security. curl doesn't look at menus; it sends the request straight to the endpoint.
The fourth is treating the Top 10 as a complete list. A risk that is not on the list can hit you too. The Top 10 tells you where to start; it doesn't tell you where to stop.
Checklist
When you write a new endpoint, ask yourself four questions. Who can call this endpoint, and where on the server is that rule written? What is the source and limit of every input it receives (route, query string, body, header, cookie)? Which part of CIA can this endpoint break: can it show someone else's data, change it, or keep the system busy? When something fails, what goes back to the client and what goes into the log?
At the project level, review the attack surface regularly as well: remove unused endpoints, development tools left open in Production, and packages that sit in the project without being used.
A Small Experiment
★☆☆ Create the small project above yourself with dotnet new web, run it with dotnet run --urls http://localhost:5101 and repeat the two curl -i requests. Then add a third order to the list and read it through another customer's eyes too.
★★☆ Pick three endpoints from a project you work on. For each one, write a sentence on which part of the CIA triad it could break and which OWASP Top 10:2025 category it comes closest to.
★★★ Map the attack surface of the same project: write the list of routes, the headers and cookies it reads, the file types it accepts and the external services it calls on one page. We will open that page again at the end of the series.
In the next post we set up the lab: we bring up the Bookstore API on SQLite, practice reading HTTP through a security lens (method, status code, header, cookie) with curl -i, and look at the difference between the Development and Production environments and at the HTTPS development certificate.