Skip to main content

Overview

Missing Authorization is a critical vulnerability where a sensitive function, endpoint, or resource lacks any check to verify if the user is allowed to access it. This often happens when a developer assumes an endpoint is “hidden” or “obscure” and can’t be found, so they don’t add authentication or authorization checks. An attacker can simply discover and call this endpoint directly.

Business Impact

This is one of the most direct and severe vulnerabilities. It can allow any anonymous or low-privilege user to perform high-privilege actions, such as deleting users, changing site-wide settings, or gaining full administrative access, leading to a complete system compromise.

Reference Details

CWE ID: CWE-862 OWASP Top 10 (2021): A01:2021 - Broken Access Control Severity: Critical

Framework-Specific Analysis and Remediation

This is a flaw of omission. The developer simply forgot to add a security control. The fix is to add the appropriate framework-level protection (middleware, decorator, filter, attribute) to the vulnerable endpoint, enforcing a “deny by default” policy.
  • Python
  • Java
  • .NET(C#)
  • PHP
  • Node.js
  • Ruby

Framework Context

A developer adds a new view in views.py and a new path in urls.py but forgets to add the @login_required or @permission_required decorator.

Vulnerable Scenario 1: A “Hidden” Admin Action

# myapp/views.py
def delete_user_view(request, user_id):
    # DANGEROUS: There are NO checks here. Any anonymous user
    # who finds /admin/delete-user/5/ can delete a user.
    user = User.objects.get(pk=user_id)
    user.delete()
    return redirect('/admin/users')
    
# myproject/urls.py
urlpatterns = [
    path('admin/delete-user/<int:user_id>/', views.delete_user_view),
]

Vulnerable Scenario 2: A DRF ViewSet with AllowAny

A developer creates a ViewSet for managing Project models and accidentally sets the default permission to AllowAny.
# api/views.py
from rest_framework import viewsets
from rest_framework.permissions import AllowAny

class ProjectViewSet(viewsets.ModelViewSet):
    queryset = Project.objects.all()
    serializer_class = ProjectSerializer
    # DANGEROUS: Any anonymous user can now send
    # POST, PUT, DELETE requests to /api/projects/
    permission_classes = [AllowAny] 

Mitigation and Best Practices

Apply the necessary decorators (e.g., @permission_required) to the view. For DRF, set a restrictive default permission policy (like IsAuthenticated) and be explicit with permission_classes = [IsAdminUser].

Secure Code Example

# myapp/views.py (Secure)
from django.contrib.auth.decorators import login_required, permission_required

@login_required
@permission_required('auth.delete_user', raise_exception=True)
def delete_user_view(request, user_id):
    # SECURE: This view is now protected.
    user = User.objects.get(pk=user_id)
    user.delete()
    return redirect('/admin/users')
    
# api/views.py (Secure)
from rest_framework.permissions import IsAdminUser

class ProjectViewSet(viewsets.ModelViewSet):
    # ...
    # SECURE: Only admin users can access this endpoint.
    permission_classes = [IsAdminUser]

Testing Strategy

Write an integration test. As an anonymous client, POST to the /admin/delete-user/5/ URL. Assert that the user count has not changed and that the response was a redirect to the login page.
# myapp/tests.py
def test_anonymous_user_cannot_delete_users(self):
    user_to_delete = User.objects.create_user('test')
    
    response = self.client.post(reverse('delete-user', args=[user_to_delete.id]))
    
    self.assertTrue(User.objects.filter(pk=user_to_delete.id).exists())
    self.assertRedirects(response, f'/login/?next=/admin/delete-user/{user_to_delete.id}/')