Improper access control is a basic web application vulnerability that still leads to compromises. Small oversights or simply not thinking things through can lead to big problems, such as account takeover or sensitive data being stolen.
Let’s take a look at what improper access control looks like in a Django application. If you’re a Django developer, keep reading to make sure your application isn’t the next victim of this vulnerability.
How data flows within a Django application
Django follows the common MVC style of web development, although Django refers to the “controller” as the “view” and the “view” as a “template.” Not sure why, but that’s what it is. When a user requests a URL, the application processes the request, validates the data, and returns HTML to the user.
Let’s check out what that looks like visually:
After the request is validated, data is gathered from a database or REST service, added to the template, and then rendered into HTML and passed back to the browser. In this post, we’ll concentrate on what is happening in the “validation” phase of this process.
What Improper Access Control looks like
Let’s zoom in on the “validation” step of the data flow.
Validation of the request seeks to answer several questions. For example, is the user logged in? If a form was submitted, are the required fields present? Did the user enter improper data into an input field?
When validation fails, generally the user is returned to the page from which they came and a message is displayed telling the user why their request was rejected. The user can then fix the error and send another request, which will flow through the same validation step.
There is a question missing from the validation phase as it stands. To illustrate, let’s look at the possible code for an administrator dashboard in Django (this code is from the DjanGoat project).
1
2
3
4
5
|
@require_http_methods ([ "GET" ]) @user_is_authenticated def admin_dashboard(request, selected_id): current_user = utils.current_user(request) return render(request, 'admin/dashboard.html' , { 'current_user' : current_user}) |
The decorator here is called user_is_authenticated. It checks to see if the user has logged in before returning the admin_dashboard to the user. At first, this may make sense to the developer writing it, and it is a good first step. You definitely don’t want administrator functions exposed to unauthenticated users. However, a subtle problem exists.
Is every user of the application an administrator? Most likely not. This code only checks if the current user has logged in. It doesn’t check to see if the current user has permission to view the admin dashboard. All a user has to do is log into the application and they are given access to the admin dashboard. That’s probably not what the developer intended.
So the key question the validation phase must answer is: Is the user authorized? Proper access control can be used to protect sensitive data, such as personally identifiable information (PII) stored within your application in addition to protected views such as an admin dashboard or control panel. Users, whether internal or external, should only have access to what is needed to do their job or complete a given task. Anything more will open your application up to a data breach or compromise.
How to fix Improper Access Control in Django
If you’ve found this type of problem in your application, how do you fix it?
Python decorators are powerful tools and can go a long way toward making your code easier to read and understand. It also makes code easier to write, and the proper decorators make your code easier to secure. Improper access control is a good example of a problem decorators solve well.
A decorator is a function that can be used to check conditions before executing the function it decorates. Decorators allow you to write code with an Aspect-Oriented Programming (AOP) style, creating reusable functions for cross-cutting concerns like logging or security.
Using the model for Access Control
One way to tackle the improper access control in this example is to use the user model to determine if the user is an administrator or not. We can add a function to the user model which returns true if the user is an admin and false if the user is not. Then, a decorator can be created which allows access to the admin_dashboard only if the user is an administrator.
We can add the following code to the model to create the decorator function:
1
2
3
4
5
6
7
8
9
10
11
|
def is_admin( self ): if str ( self .user_type) = = 'Administrator' : return True else : return False admin_required = user_passes_test( lambda u: True if u.is_admin else False , login_url = '/' ) def admin_required(view_func): decorated_view_func = login_required(admin_required(view_func), login_url = '/' ) return decorated_view_func |
With the decorator in place, we can add it to the admin_dashboard function to make sure only administrators can see this page.
1
2
3
4
5
6
|
@require_http_methods ([ "GET" ]) @user_is_authenticated @admin_required def admin_dashboard(request, selected_id): current_user = utils.current_user(request) return render(request, 'admin/dashboard.html' , { 'current_user' : current_user}) |
Using role-based Access Control
For internal business applications with larger user bases, role-based access control (RBAC) is a good solution. You’ll create multiple roles that your users can occupy at any given moment. For example, your customer service employees have different roles than your accounts payable employees because the each need certain access to do their job. Using roles simplifies access control since you set up each role once and simply associate employees with their given role to give them the access they need.
This can be accomplished in Django using a model meta attribute. You can add permissions to the user meta attribute or to the specific models you need to access. It would look like this:
1
2
3
4
5
6
7
8
|
Class User(models.Model): … Class Meta: permissions = ( ( "view_admin_dashboard" , "Can view the admin dashboard" ), ( "edit_admin_dashboard" , "Can make changes to admin dashboard" ), ) |
The User object provides a has_perm method which determines if the user has a given permission.
1
|
user.has_perm("view_admin_dashboard”) |
This code can be turned into a custom decorator or you can use the permission_required decorator to check the permission before executing a function. It takes the permission to check, a URL to redirect the user to if the permission check fails, and a boolean indicating whether or not to raise an exception, which defaults to false.
1
|
@permission_required ( "user.view_admin_dashboard" , "/login/" ) |
Control your access
Improper access control is still on the OWASP Top 10 list for a reason. Exploits happen every day because of access controls that are lacking or nonexistent. Don’t let your application be next.
Use the power of decorators and meta attributes to implement proper access control for your application. Validate that your user is authenticated and authorized for the functions they are trying to access. Then pat your self on the back for a job well done. You’ve protected your Django app from improper access control.
Security Labs can help you implement secure coding techniques like this by allowing you to practice in a safe environment. Practice will help you do it right when the time comes. Get in touch if you’re interested in learning more about secure coding.