Skip to content

Conversation

@Maffooch
Copy link
Contributor

Introduce a base class for related object permissions to streamline permission checks for engagements, risk acceptances, and findings. Update existing permission classes to utilize the new structure, enhancing code maintainability and clarity.

@Maffooch Maffooch requested a review from mtesauro as a code owner January 23, 2026 17:11
@github-actions github-actions bot added the apiv2 label Jan 23, 2026
@dryrunsecurity
Copy link

dryrunsecurity bot commented Jan 23, 2026

DryRun Security

This pull request introduces object-level authorization gaps allowing any authenticated user to manipulate and view sensitive findings and questionnaire answers: the set_finding_as_original action bypasses proper permission checks enabling IDOR (an attacker can mark arbitrary findings as duplicates within a product), and the QuestionnaireAnswerViewSet exposes all answers to any authenticated user by returning Answer.objects.all() without per-user filtering.

Insecure Direct Object Reference (IDOR) in dojo/api_v2/views.py
Vulnerability Insecure Direct Object Reference (IDOR)
Description The set_finding_as_original action in FindingViewSet is vulnerable to IDOR (Broken Object Level Authorization). The action is decorated with UserHasFindingRelatedObjectPermission, which relies on object-level permission checks performed during self.get_object() or self.check_object_permissions(). However, the action fails to call either of these methods. Instead, it directly calls set_finding_as_original_internal, which uses get_object_or_404 to retrieve findings without any authorization check beyond ensuring they belong to the same product. Since UserHasFindingRelatedObjectPermission always returns True for the general has_permission check, any authenticated user can call this endpoint to manipulate findings across the entire system, even if they have no permissions for the relevant product. Specifically, an attacker can mark any finding as a duplicate of another in the same product, which results in the target finding being deactivated and closed.

def set_finding_as_original(self, request, pk, new_fid):
success = set_finding_as_original_internal(request.user, pk, new_fid)

Broken Object Level Authorization (BOLA) in Questionnaire Answer ViewSet in dojo/api_v2/views.py
Vulnerability Broken Object Level Authorization (BOLA) in Questionnaire Answer ViewSet
Description The QuestionnaireAnswerViewSet (and other questionnaire-related viewsets) allows any authenticated user to list all answers in the database. This is because the viewset's get_queryset method returns all objects (Answer.objects.all()) without any user-based filtering. While it uses DjangoModelPermissions and UserHasEngagementRelatedObjectPermission, these do not effectively restrict GET requests for the list action: DjangoModelPermissions allows GET by default for authenticated users, and UserHasEngagementRelatedObjectPermission's has_permission method explicitly returns True. This allows any user to access sensitive security questionnaire answers across the entire system, regardless of their assigned products or engagements.

queryset = Answer.objects.none()
filter_backends = (DjangoFilterBackend,)
permission_classes = (
permissions.UserHasEngagementRelatedObjectPermission,
DjangoModelPermissions,
)


All finding details can be found in the DryRun Security Dashboard.

…ermission, UserHasRegulationPermission, and UserHasSLAPermission; update views accordingly
@Maffooch Maffooch added this to the 2.54.3 milestone Jan 23, 2026
Copy link
Contributor

@mtesauro mtesauro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved

)
@action(detail=True, methods=["get"])
def download_proof(self, request, pk=None):
def download_proof(self, request, pk=None, permission_classes=(IsAuthenticated, permissions.UserHasRiskAcceptanceRelatedObjectPermission)):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there are reason why the permission_classes are passed as a parameter instead of in the decorator? Are they picked up like this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, this is supposed to be in the action decorator

Copy link
Member

@valentijnscholten valentijnscholten left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment. Also as far as I can see there are no tests covering this new mechanism? Would be good to at least have one to prove it's working. Would this be something you could add and then release in 2.55.0 in the dev branch?

@Maffooch Maffooch modified the milestones: 2.54.3, 2.55.0 Jan 26, 2026
@Maffooch
Copy link
Contributor Author

See comment. Also as far as I can see there are no tests covering this new mechanism? Would be good to at least have one to prove it's working. Would this be something you could add and then release in 2.55.0 in the dev branch?

Agreed - the passing of existing DRF tests felt like enough, but I will create some more tests to cover these new classes explicitly

@Maffooch
Copy link
Contributor Author

@valentijnscholten I have added new tests to cover the API endpoints that use these new base classes. This approach felt like a two-for-one deal to get more endpoint coverage while exercising the base classes do what the intend

@Maffooch Maffooch merged commit 2a9a747 into bugfix Jan 27, 2026
92 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants