Finding broken access controls through source code in .NET applications

Pavel Shabarkin
7 min readMay 25, 2022

Introduction

Working as a security consultant on many time-bound projects, I found that the most reliable way to achieve a higher rate of successfully identified vulnerabilities is to review the source code. Source code review is a far more effective way than black-box testing if you know the tactics and methods. In addition, mapping each case you find and preparing a black-box method solution to identify vulnerabilities can help you create your own methodology to search for the issues.

This time I will explain how to look for broken access controls and Insecure Direct Object References (IDORs) in .NET applications by walking through the source code.

This blog post is based on the research made by Yurii Sanin

Access Controls in .NET applications

The basics of the MVC architecture

Most of .NET applications have the Model-View-Controller (MVC) architecture design. MVC is a design pattern used to decouple user-interface (view), data (model), and application logic (controller). As a user of the web application you directly communicate with the controllers by executing GET, POST, PUT… HTTP request:

Where the /Account is the name of the controller and /UpdateInfo is the public action of the controller (class). All public functions within the controller are accessible for an authenticated user from the web application (except naming with prefixed underscore such as: _updateInfo).

Here is a pseudocode to demonstrate how the information from the HTTP request (relates to the code in the application) correlates with the code in the application:

For more details, you can check the official .NET documentation:

Access Control [authorization] Model

The authorization model in the .NET framework is quite a big topic to cover in one article. The .NET framework develops several different ways to implement the authorization (today we focus on the simple authorization model):

  1. Simple authorization
  2. Role-based authorization
  3. Claims-based authorization
  4. Policy-based authorization

For more details, you can check the official .NET documentation:

Simple authorization, [Authorize] and [AllowAnonymous] attributes

During a recent security source code review, I found that “Simple authorization” is implemented with the [Authorize] attribute on the top of the controller class. By defining this attribute on the top of the class, the developer ensures that “embedded” (user’s session cookie verification is handled automatically by .NET) authentication is implemented for all controller actions within that controller (All actions simply inherit the [Authorize] attribute):

However, the [AllowAnonymous] attribute on top of the action can override the defined permissions on top of the controller, what means that action will be available to the public without any authentication:

(No cookies needed to request the controller’s action):

Accordingly to the official documentation:

[AllowAnonymous] bypasses all authorization statements. If you combine [AllowAnonymous] and any [Authorize] attribute, the [Authorize] attributes are ignored. For example, if you apply [AllowAnonymous] at the controller level, any [Authorize] attributes on the same controller (or on any action within it) will be ignored.

In this case, if we have controller which does not require the authorization in general, and we define the [AllowAnonymous] attribute on top of the controller, but there is only one function which requires (by application business logic) to have the authentication or authorization, the [Authorize] attribute on top of the action will be ignored.

Example of the request:

Most of the time, you could observe this issue when developers debug their code, and simply forget to put the authentication back before pushing to production.

Role issues in the “Simple Authorization”

By implementing the [Authorize] attribute, the developer only ensures that the authentication is in place, but not actually the authorization. Several ways how the authorization can be implemented.

Sometimes, developers implement the authorization mechanisms within each action of the controllers by defining the business logic. If the application is large, it’s very difficult and time-consuming to maintain, refactor and scale such architecture without producing new vulnerabilities in the authorization mechanisms:

Basic coverage of the Role-based authorization

Another way, it’s “embedded” Role-based authorization maintained by .NET framework.

It basically means we can define desired permissions in the backing store of the authorization process and use them on top of the controllers or their actions:

According to the official documentation:

When an identity is created it may belong to one or more roles. For example, Tracy may belong to the Administrator and User roles while Scott may only belong to the User role. How these roles are created and managed depends on the backing store of the authorization process. Roles are exposed to the developer through the IsInRole method on the ClaimsPrincipal class. AddRoles must be added to Role services.

Role based authorization checks:

  • Are declarative and specify roles which the current user must be a member of to access the requested resource.
  • Are applied to Razor Pages, controllers, or actions within a controller.
  • Can not be applied at the Razor Page handler level, they must be applied to the Page.

For more details see the official documentation:

There is the example how those Roles can be defined in the code:

But developers commonly miss attaching the defined Roles properly to the controllers or their actions.

If the controller or the action should be accessed by several different users (with different Roles assigned), the developer should specify Roles separated by comma (it makes the action Function1 accessible for the Student or Teacher role):

But when multiple attributes are applied, an accessing user must be a member of the all specified roles.

Also, developers can specify the role based authorization on the controller level, but limit the access to specific actions on the action level:

That’s why in some cases, when multiple roles are defined at the controller level, and some actions need to have scoped permissions (roles assigned) at the action level, the application can have misconfigured access controls. All actions should be reviewed in order to find the action which was not scoped to the specific permissions (roles assigned), if the app business logic requires this granular authorization to be in place.

Hunting methodology through source code

I look for the controllers, their actions and applied authorization attributes in order to find all implemented authorization mechanisms.

  1. Walk through all the files within the Controllers folder and identify all implemented controllers:

public class {Name}Controller : Controller → identify all controller + 3 lines before and after (to check the attributes)

grep -HanriEB3 "public class [a-z0-9_]+Controller : Controller"
  1. Walk through all the files within the Controllers folder and identify all public functions (actions):

public {ReturnedType} → identify all actions + 3 lines before and after (to check the attributes)

grep -HanriEB3 "public [a-z0-9_]+ [a-z0-9_]+\\("
  1. Walk through all the files within the Controllers folder and identify all [AllowAnonymous] attributes at the endpoint/function (action) or even controller (class) level. The controller at the Class level can have [Authorize] attribute, what means all the endpoints will require authorization. However the [AllowAnonymous] attribute at the function/action level will overwrite this permission.
grep -HanriEA3 "\[AllowAnonymous\]"
  1. Walk through all the files within the Controllers folder and identify all [Authorize(Roles = "{Role}")] attributes at the action (function) and controller (class) level.

Then, manually review each controller and action according to this article:

grep -HanriEA3 "\[Authorize\(Roles = \"[a-z0-9_]+\"\)\]"grep -HanriEA3 "\[Authorize\(Roles = \"[a-z0-9_]+\,[a-z0-9_]+\"\)\]"

Summary

Authorization issues can lead to significant problems. Hidden authorization issues require manual review and deep understanding of the context. You may find actions even without applied access controls. Build the map of the roles and determine permissions that users really need. Never forget to review your access controls and always remove the [AllowAnonymous] attribute after debugging before pushing to the production)

The complete methodology on how to review .NET applications through source code will be added soon in the following Github repository) please star and fork my repo):

References

  1. https://dotnet.microsoft.com/en-us/apps/aspnet/mvc
  2. https://docs.microsoft.com/en-us/aspnet/core/security/authorization/simple?view=aspnetcore-6.0

--

--