The 2025 OWASP Top 10 and IdentityServer

Christian Wenz |

When the first OWASP Top 10 list was released in 2003, no one knew that subsequent editions of the top 10 security risks for web applications would become a de facto standard, used and referenced around the world. A Google Scholar search for references to the list yields tens of thousands of results, and helped establish standards such as PCI DSS (Payment Card Industry Data Security Standard), while several national security bodies reference it directly.

After the initial 2003 version, the next edition came one year later. Then the project first switched to a three-year cadence (2007, 2010, 2013) and later to a four-year cadence (2017, 2021). If you do the math, you may have already guessed that a new edition of the OWASP Top 10 was due for 2025, and the project team delivered! Just in time, you might say, since the final version was not released before Dec 24, 2025, but now we have the 2025 OWASP Top 10. Let's have a look at how the list has been assembled, its relevance today, and of course, what's in the list!

Making the OWASP Top 10

The OWASP project list contains over two dozen Top Tens. The OWASP Top 10 — actually, the OWASP Top Ten Web Application Security Risks — stands out. Not only is it by far the most popular of those lists, but it's also the one that's most data-driven. Whenever a new list is being prepared, the project team asks practitioners — companies and individuals conducting security audits — for their findings. Not the actual reports, of course, but a compiled list of which vulnerabilities were found how often, in how many applications.

For this, OWASP is relying on the CWE numbering system. The acronym stands for Common Weakness Enumeration. Each known weakness — that is, an issue that might turn into a vulnerability — has a number assigned to it. For instance, SQL injection is CWE #89, whereas the rather recent SSRF (server-side request forgery) is CWE #918. Some CWEs are even specific to a certain technology, for instance #12 is called "ASP.NET Misconfiguration: Missing Custom Error Page", and yes, that refers to old, .NET Framework-based ASP.NET.

So OWASP eventually receives a lot of numbers: which CWEs, how often, and then the number-crunching can begin. The data is normalized, and the project team then calculates the incidence rate of found weaknesses. This is due to the common situation where, when a vulnerability occurs in an application, it often does so all over the place, so relying on frequency alone would overemphasize this weakness.

Another data point the OWASP Top 10 team is using to create the list is a survey asking practitioners to rate the importance of several upcoming security risks. The audit data OWASP receives is always a look at the past, so upcoming trends are not well reflected in it; that's why the survey is in place to complement the data.

Of course, it does not make sense to just pick the ten CWEs with the highest numbers to create the list. So many weaknesses would be overlooked! Therefore, the hardest part in creating the list is finding category names, or umbrella terms, to group CWEs. There are individual list items that include over 40 CWEs! That's why some of the categories have very generic names, although I personally wish some of them were more focused.

Despite the rather scientific approach of the OWASP Top 10, the list is still somewhat subjective — for instance, due to the introduction of categories and a potential bias in the data submitted to the project. Also, depending on the software stack you are using, some risks may be more relevant, while others are less so. We will focus on ASP.NET Core in this post, but also aim to make coverage of risks as generic as possible.

In the end, the list is all about awareness. Knowing about potential risks is a key ingredient in improving an application's security posture. It doesn't matter where a potential vulnerability ranks in the OWASP Top 10; it's just important that it's there.

But enough about how the list came about — let's look at each item! But before we do, one important caveat. Due to the vast number of CWEs per category, we can't possibly cover them all. So for each list item, we picked a few typical examples.

Item #1: Broken Access Control

"Broken access control" sounds like the epitome of a security risk: someone can access data or functionality that they shouldn't. A mitigation for this potential vulnerability sounds easy: access control everywhere. If only it were that simple.

In general, having access control is a good practice. Every endpoint that returns data needs to have proper authorization. The same goes for all endpoints that trigger an action in the application (usually, those that do not use HTTP GET). But there are some special kinds of weaknesses that look less obvious, so let's talk about them.

The first is called "mass assignment", a vulnerability that was famously exploited on the GitHub site in 2012. It allowed the attacker to create a GitHub issue in the future (great Scott)! While GitHub is implemented in Ruby on Rails, here is a (simplified) C# version of what happened.

GitHub was using model classes, and the one for an Issue might have looked something like this:

public class Issue
{
    public Issue()
    {
        this.CreationDate = DateTime.UtcNow;
    }

    public string Title { get; set; } = string.Empty;
    public string Description { get; set; } = string.Empty;
    public DateTime CreationDate { get; set; }
}

The endpoint for creating an issue was using model binding. In ASP.NET, it could look like this:

[HttpPost]
public async Task<ActionResult<Issue>> PostIssue(Issue issue) { /* ... */ }

At first glance, it looks legit: when an issue is created, its creation date is set to the current point in time in the model class's constructor. The issue itself is constructed from the data in the HTTP request, typical model binding. But what happens if there is a field called "CreationDate" in the HTTP request, as well? The model binding process would then overwrite the CreationDate property, catapulting the issue into the future. That's mass assignment; Microsoft sometimes calls the attack "overposting". Mitigation is easy: either create an allowlist of properties that may be assigned through model binding, or make the CreationDate property non-public. Here is an example for the former:

[HttpPost]
public async Task<ActionResult<Issue>> PostIssue([Bind("Title,Description")] Issue issue) { /* ... */ }

Another typical part of the "Broken Access Control" category is cross-site request forgery (CSRF), once a Top 10 list item in its own right! We have covered this attack thoroughly in both a blog post and in our web application security video series, so refer to those sources for more details.

One of the upcoming and rather novel attacks is called SSRF — server-side request forgery. The idea is this: an attacker would like to send an HTTP request to a target system, but fails to do so because the system is unreachable (to the attacker) or they lack the required privileges to perform an action in the app. But conveniently, there is an intermediary system that happily forwards the attacker's request to the target. A typical example is using the BFF pattern to run API calls through an intermediary that automatically attaches access tokens to each new request.

flowchart
    subgraph Attack_Flow["Attacker Flow to Target Website"]
        direction LR
        A["Attacker with Laptop"]
        I["Intermediary Server"]
        T["Target Website Server"]
    end
    A -- "1. Sends request" --> I
    I -- "2. Forwards request" --> I
    I -- "3. Request on behalf of attacker" --> T

Item #2: Security Misconfiguration

Item #2, "Security Misconfiguration", climbed four places in the 2025 edition of the OWASP Top 10. One of many reasons: some configuration settings are very easy to test during a security audit, so the likelihood of finding issues there is quite high.

Obviously, configuration options depend heavily on the technology stack being used. However, one "easy win" is to leverage the security HTTP headers. They activate security features in web browsers, and their omission is easy to detect for auditors, so if you don't use them, this will end up in the report.

All official Duende IdentityServer templates include the file Pages/SecurityHeaderAttributes.cs that adds a couple of security headers. We have covered most of them in our web security video series over on YouTube, and are planning to add even more! Here is a list of headers currently present in that file:

Header name Description Video
Content-Security-Policy Restricts which resources a page may load, including JavaScript https://www.youtube.com/watch?v=B0Rz_qiQAWo, https://www.youtube.com/watch?v=DAourQuPDTE, https://www.youtube.com/watch?v=apoRlEq5PAs
Referrer-Policy Restricts or completely removes the Referer HTTP header https://www.youtube.com/watch?v=ez77PZb9wfU
X-Content-Type-Options Can prevent (older) browsers from guessing a file's type based on its content instead of its Content-Type HTTP header https://www.youtube.com/watch?v=OztgrdMQG94
X-FRAME-OPTIONS Restricts pages being put in an iframe, can prevent clickjacking attacks Coming soon!

Item #3: Software Supply Chain Failures

Software supply chain attacks have been dominating security news lately. Especially the Node ecosystem, with the NPM package management system hit several times by waves of malware-laden package updates. Keeping dependencies up to date is not as easy as it seems. Of course, it makes a lot of sense to install updates and new versions as quickly as possible. In the event of such an attack, with the latest packages being dangerous, this strategy will be futile. On the other hand, delaying patching leaves the system vulnerable if the update closes a security hole.

In my experience, you need to tackle this issue from two sides. First, install the new packages as soon as possible. But also invest heavily in automated testing so you can be confident the application still works as expected after the update. And of course: the fewer dependencies you are using, the less likely you are to be prone to attacks, either by packages gone rogue, or by insecure dependencies.

For NuGet, the dominant package management system in the .NET space, Duende's own Maarten Balliauw has researched a few potential attack vectors, including Unicode characters in package names. According to his LinkedIn post, a fix for that is rather unlikely.

Item #4: Cryptographic Failures

Cryptography is one of those areas where standing on the shoulders of giants is the right way to do it. For security-critical aspects, use reference implementations of algorithms and standards. That's one of the reasons why you are using Duende IdentityServer anyway, right? 😉

The most common type of cryptography with web applications is the encryption of data at transport — in other words, HTTPS instead of HTTP, and less commonly, WebSocket Secure instead of the WebSocket protocol. Let's stick with HTTP for a minute. In this day and age, it should be a well-known fact that encrypting data in transit is mandatory. If you use HTTPS throughout, an auditor won't complain, but the actual task is to eliminate plain HTTP entirely.

Of course, you may just disable that protocol on the server — and for an internal application, you may want that — but it's not always feasible to do that for a public site. Several web browsers try HTTPS first when a user enters a domain name in the address bar, but still, react to an HTTP request; if it's just a redirect to HTTPS, that sounds like a pragmatic approach.

The problem is with the specific HTTP request. It's not encrypted, so hypothetically and depending on circumstances, an attacker might listen in. There are a few aspects to that:

  • Make sure that you have that redirect in place, for each request that comes in (in ASP.NET Core, the HttpsRedirection middleware takes care of that, and is even part of all project templates).
  • Cookies where the secure flag is set will not be sent through unencrypted HTTP. So use that flag consistently for all your cookies.
  • Prefix the cookie name with __Secure- so it can't easily be overwritten by a third party.

If you want to go one step further, employ HSTS (HTTP Strict Transport Security). This browser feature enforces HTTPS throughout. By setting the Strict-Transport-Security HTTP header like this, you instruct the browser to use nothing else but HTTPS for the current Host and all its subdomains, and remember the decision for a year (365 days):

Strict-Transport-Security: max-age=31536000; includeSubDomains

The ASP.NET Core web app templates automatically enable HSTS if you are not in development mode:

if (!app.Environment.IsDevelopment())
{
    // ...
    app.UseHsts();
}

By default, max-age is set to 30 days; you may want to increase it for production systems.

Item #5: Injection

When the first OWASP Top Ten came out in 2003, SQL Injection was at the top spot, and held its place for the next five editions. That's a little bit depressing, isn't it? SQL injection is often called an ancient attack that usually happens when strings are carelessly concatenated into SQL statements executed on a database server. If part of that string comes from the user, and if that substring happens to be an SQL command, the vulnerability has been exploited.

In 2026, there is really no excuse for still falling for that weakness. Using prepared statements separates commands from data, preventing SQL injection in most cases. Relying on an object-relational mapper (O/R mapper), such as Entity Framework Core, Hibernate, or Doctrine, provides an API rather than raw SQL, making SQL injection virtually impossible. And yes, there are still edge cases where it may still be possible to inject malicious SQL, for instance, when using one of the O/R mapper's API to send code as-is, but that is rare (and requires extra diligence). Apart from that, SQL injection sounds like a solved problem.

So why the #5 spot? Because the "Injection" category also includes cross-site scripting (XSS), since that attack usually occurs through injecting (!) JavaScript code. XSS has always been a category of its own, but was merged with SQL injection (and other, more exotic, injections) in the 2021 list.

Injected JavaScript code has the same privileges as our own, so we absolutely must avoid it. The usual mistake that causes XSS is outputting user input verbatim into HTML, which could include a <script> tag. Depending on the stack, this may be easier or harder! In ASP.NET Core, for instance, the @ character automatically applies HTML-escaping to the value provided.

We have covered XSS quite a bit in the web application video series. Also, modern browsers come with additional safeguards that can help mitigate XSS vulnerabilities, mostly these two:

  • Content Security Policy (CSP) is a mechanism where an HTTP header (Content-Security-Policy) sends a policy to the client, essentially restricting which resources may be loaded, including JavaScript files! We have covered CSP in our video series starting from this video, and the two following ones.
  • Trusted Types API is a browser feature where it is not possible any longer to write verbatim strings directly into a DOM element's innerHTML property, mitigating several attack vectors, especially against single-page applications.

Item #6: Insecure Design

After all these rather specific technical details about security risks, it's time to look at an application from a higher level. If the app's design was not created with security in mind, that's obviously a risk.

Security must be part of the full development lifecycle, starting with the design phase. It sounds a bit like a self-fulfilling prophecy, but you might want to start with the OWASP Top Ten…

But since this list category is not as actionable as most of the others, let's just move on.

Item #7: Authentication Failures

Authentication is hard, since so many things can go wrong. Weak credentials, insecure password storage, session ID reuse, lack of multi-factor authentication, and much more. This category was even more relevant in previous lists, since authentication relied heavily on user-generated code back then. Today, this is often handled by the software stack or framework being used. For instance, ASP.NET Core Identity uses sane defaults. Here at Duende, we take the utmost care that our software is configured securely from the very start.

One interesting aspect that's often under debate is password complexity. Don't you also like the prerequisites many web applications use? Eight characters, but you need a mix of uppercase and lowercase letters, numbers, and special characters? You would be shocked how many passwords use a number and a special character (or vice versa) at the last two positions…

The National Institute of Standards and Technology, the United States' standards and technologies body, states that "[p]assword length is a primary factor in characterizing password strength" (source: NIST Special Publication 800-63B). The Cybersecurity and Infrastructure Security Agency (CISA) recommends passwords to be "long—at least 16 characters long (even longer is better)" (source: Require Strong Passwords | CISA). So if you are fine-tuning the password complexity requirements, consider starting with the length. When using ASP.NET Core Identity, here is how you can tweak the default behavior:

builder.Services.AddDefaultIdentity<IdentityUser>(options =>
{
    options.Password.RequiredLength = 16;
});

Item #8: Software or Data Integrity Failures

Whether you are working with code or data, its integrity must be ensured at all times. This is what the #8 category in the 2025 OWASP Top Ten is all about. Especially when using code, there are a variety of risks involved with modern web applications.

When relying on assets from a CDN, always remember: CDN essentially means "someone else's infrastructure". If there is a security compromise, and the assets you want are replaced with malicious content, you might run into problems.

For CSS and JavaScript, there is a solution in modern browsers: sub-resource integrity (SRI). When loading a third-party library using <link> (for CSS) or <script> (for JavaScript), you can provide the SHA256/384/512 hash of the expected file in the integrity HTML attribute.

For instance, here is how the Vue.js documentation recommends loading the JavaScript framework from the unpkg.com CDN:

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

Trusted old jQuery is providing the expected hashes directly on their CDN page:

jQuery CDN page showing SRI hashes for each release

Another typical risk from this category is deserialization. In .NET, for instance, the BinaryFormatter was considered a security risk and removed in version 9. In general, when deserializing data, make sure you know the specific type you expect, and deserialize into that only. No guessing, no magic conversions. The less permissive you are, the better the security posture of your deserialization.

Item #9: Security Logging and Alerting Failures

The penultimate item on the 2025 OWASP Top 10 sounds a little bit like a truism. Do log, and do have a process that monitors logs for anomalies and then raises an alert when issues are detected. One good example is the use of a Content Security Policy (see list item #5): if there is a policy violation, we want to know about it, because it could be an XSS attack in progress. Both aspects, the logging and the monitoring/alerting, have to go hand in hand. It's a little bit like having a backup and restoring from it. Only if you have the latter can you be sure of the former.

In Duende IdentityServer, the ASP.NET Core logging mechanism is used by default, with no extra steps required on your part. Here is a detailed description of how logging is used, and we recently blogged about implementing security event auditing. You can even export logs into OpenTelemetry.

As with some of the other list items, this one shows that not only do you need to make your code secure, but you also need a security-focused process to mitigate potential issues as they occur.

Item #10: Mishandling of Exceptional Conditions

The final item of the 2025 OWASP Top 10 was introduced in that edition of the list, so it's brand new. Its main focus: proper exception handling. In a nutshell: do take care of exceptions. They need to be caught (because an unhandled exception in turn could create an error page that might contain revealing information), logged, and potentially alerted — remember item #9.

Apart from just catching and logging the exception, a cleanup is necessary, such as closing open resources and rolling back transactions. Sometimes this is automatically provided by the framework or library, but to stick with the example of open resources: if you close them explicitly, they become available to others earlier than if you have to wait for the framework mechanism to run.

And although it is more of a category #2, Security Misconfiguration, the exception page should never contain a stacktrace or other technical details. Those might be helpful for developers, but even more so to attackers. Here is an excerpt from the default ASP.NET Core template, setting up a custom exception page with no details, if we are not in development mode:

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    // ...
}

Conclusion

As you have seen, the ten items of the 2025 OWASP Top 10 cover the most relevant aspects of web application security — technical details, process-related requirements, and generally raise awareness for security risks in web applications. While certainly all-encompassing, they are a good primer or refresher on web app security and hopefully support your apps' security posture.


Thanks for stopping by!

We hope this post helped you on your identity and security journey. If you need a hand with implementation, our docs are always open. For everything else, come hang out with the team and other developers on GitHub.

If you want to get early access to new features and products while collaborating with experts in security and identity standards, join us in our Duende Product Insiders program. And if you prefer your tech content in video form, our YouTube channel is the place to be. Don't forget to like and subscribe!

Questions? Comments? Just want to say hi? Leave a comment below and let's start a conversation.