CVE-2023-2227: Improper Authorization (CWE-285)
value: Improper Authorization in GitHub repository modoboa/modoboa prior to 2.1.0.
CWE description: The product does not perform or incorrectly performs an authorization check when an actor attempts to access a resource or perform an action.
CVSS v3 base score: 9.1
Published: 2023-04-21T13:15Z
Repository: https://github.com/modoboa/modoboa
Language: Python
Files changed: modoboa/admin/api/v1/viewsets.py, modoboa/core/api/v2/views.py, modoboa/lib/permissions.py, modoboa/parameters/api/v2/viewsets.py
Commit date: 2023-02-27 11:12:21 +0100
Commit message: Added missing permissions on API endpoints

Diff: diff --git a/modoboa/admin/api/v1/viewsets.py b/modoboa/admin/api/v1/viewsets.py
index e79fa97fd8..2c60701ed9 100644
--- a/modoboa/admin/api/v1/viewsets.py
+++ b/modoboa/admin/api/v1/viewsets.py
@@ -91,11 +91,10 @@ class AccountViewSet(GetThrottleViewsetMixin, lib_viewsets.RevisionModelMixin, v
     def get_throttles(self):
 
         throttles = super().get_throttles()
         if self.action == "reset_password":
             throttles.append(PasswordResetRequestThrottle())
-        
         return throttles
 
     def get_serializer_class(self):
         """Return a serializer."""
         action_dict = {
diff --git a/modoboa/core/api/v2/views.py b/modoboa/core/api/v2/views.py
index c8c7ac8d81..32e07c5037 100644
--- a/modoboa/core/api/v2/views.py
+++ b/modoboa/core/api/v2/views.py
@@ -7,19 +7,23 @@ from django.utils.translation import ugettext as _
 from django.utils.datastructures import MultiValueDictKeyError
 
 from django.contrib.auth import login
 
 from drf_spectacular.utils import extend_schema
-from rest_framework import response, status
+from rest_framework import permissions, response, status
 from rest_framework.exceptions import AuthenticationFailed
 from rest_framework_simplejwt import views as jwt_views
 from rest_framework_simplejwt.exceptions import InvalidToken
 from rest_framework.views import APIView
 
 from modoboa.core.password_hashers import get_password_hasher
 from modoboa.core.utils import check_for_updates
-from modoboa.lib.throttle import UserLesserDdosUser, LoginThrottle, PasswordResetApplyThrottle, PasswordResetRequestThrottle, PasswordResetTotpThrottle
+from modoboa.lib.permissions import IsSuperUser
+from modoboa.lib.throttle import (
+    UserLesserDdosUser, LoginThrottle, PasswordResetApplyThrottle,
+    PasswordResetRequestThrottle, PasswordResetTotpThrottle
+)
 from modoboa.parameters import tools as param_tools
 
 from smtplib import SMTPException
 
 from . import serializers
@@ -190,10 +194,11 @@ class PasswordResetConfirmView(APIView):
 
 
 class ComponentsInformationAPIView(APIView):
     """Retrieve information about installed components."""
 
+    permission_classes = [permissions.IsAuthenticated, IsSuperUser]
     throttle_classes = [UserLesserDdosUser]
 
     @extend_schema(responses=serializers.ModoboaComponentSerializer(many=True))
     def get(self, request, *args, **kwargs):
         status, extensions = check_for_updates()
diff --git a/modoboa/lib/permissions.py b/modoboa/lib/permissions.py
index 5f7d27af67..2c5efb4bb9 100644
--- a/modoboa/lib/permissions.py
+++ b/modoboa/lib/permissions.py
@@ -1,10 +1,12 @@
 """Object level permissions."""
 
 from django.contrib.auth.models import Group, Permission
 from django.contrib.contenttypes.models import ContentType
 
+from rest_framework import permissions
+
 from modoboa.core import constants as core_constants, signals as core_signals
 from modoboa.core.models import ObjectAccess, User
 
 
 def get_account_roles(user, account=None):
@@ -148,5 +150,12 @@ def add_permissions_to_group(group, permissions):
                 content_type=ct, codename=permname).exists():
             continue
         group.permissions.add(
             Permission.objects.get(content_type=ct, codename=permname)
         )
+
+
+class IsSuperUser(permissions.BasePermission):
+    """Permission class to allow only super users."""
+
+    def has_permission(self, request, view):
+        return request.user.is_superuser
diff --git a/modoboa/parameters/api/v2/viewsets.py b/modoboa/parameters/api/v2/viewsets.py
index 84b0870299..3a670f02fb 100644
--- a/modoboa/parameters/api/v2/viewsets.py
+++ b/modoboa/parameters/api/v2/viewsets.py
@@ -1,21 +1,23 @@
 """Parameters viewsets."""
 
 from drf_spectacular.utils import extend_schema, OpenApiParameter
-from rest_framework import response, viewsets
+from rest_framework import permissions, response, viewsets
 from rest_framework.decorators import action
 
+from modoboa.lib.permissions import IsSuperUser
 from modoboa.lib.throttle import GetThrottleViewsetMixin
 
 from . import serializers
 from ... import tools
 
 
 class ParametersViewSet(GetThrottleViewsetMixin, viewsets.ViewSet):
     """Parameter viewset."""
 
     lookup_value_regex = r"\w+"
+    permission_classes = [permissions.IsAuthenticated, IsSuperUser]
     serializer_class = None
 
     @extend_schema(responses=serializers.ApplicationSerializer(many=True))
     @action(methods=["get"], detail=False)
     def applications(self, request):

Vulnerable code:         
from rest_framework import response, status
from modoboa.lib.throttle import UserLesserDdosUser, LoginThrottle, PasswordResetApplyThrottle, PasswordResetRequestThrottle, PasswordResetTotpThrottle
from rest_framework import response, viewsets
Fixed code: from rest_framework import permissions, response, status
from modoboa.lib.permissions import IsSuperUser
from modoboa.lib.throttle import (
    UserLesserDdosUser, LoginThrottle, PasswordResetApplyThrottle,
    PasswordResetRequestThrottle, PasswordResetTotpThrottle
)
    permission_classes = [permissions.IsAuthenticated, IsSuperUser]
from rest_framework import permissions



class IsSuperUser(permissions.BasePermission):
    """Permission class to allow only super users."""

    def has_permission(self, request, view):
        return request.user.is_superuser
from rest_framework import permissions, response, viewsets
from modoboa.lib.permissions import IsSuperUser
    permission_classes = [permissions.IsAuthenticated, IsSuperUser]