Skip to main content

Análise #02 ATO — verify-email/verify-phone

Data: 2026-07-07
Fonte: pentest.md, recover.md, update-contact.md, recover-anl.md
Codebase: coezzion-service-auth, coezzion_vendas_app


1. O Chain do ATO

POST /api/auth/verify-email {cpf:"vitima", email:"atacante@x.com"}
├─ Auth: x-api-key apenas (sem JWT)
├─ Email do body → destinatário do OTP (sem DB-lookup)
└─ OTP enviado para atacante@x.com ✓

POST /api/auth/verify-code {cpf:"vitima", email:"atacante@x.com", verificationCode:"123456"}
├─ Auth: x-api-key apenas
├─ Valida OTP no cache
└─ Gera token → salva em VerificationCodeModelInCache-{cpf} ← MESMA chave do recover

POST /api/auth/change-password {cpf:"vitima", token, password:"novaSenha1"}
├─ Auth: x-api-key apenas
├─ Lê token de VerificationCodeModelInCache-{cpf}
└─ Senha da vítima alterada ✓ → ATO completo

2. Endpoints Mapeados

Arquivo central: src/Auth.API/Controllers/PasswordController.cs

Fluxo de Update-Contact (pós-login)

EndpointAuth BackendAuth AppComportamento
POST verify-phone[UseApiKey]JWT (Authorization Bearer)Envia OTP para cellPhone do body
POST verify-email[UseApiKey]JWT (Authorization Bearer)Envia OTP para email do body
POST verify-code[UseApiKey]JWT (Authorization Bearer)Valida OTP, gera token
POST change-cellphone[Authorize(JwtBearer)] + [UseApiKey]JWTAtualiza telefone
POST change-email[Authorize(JwtBearer)] + [UseApiKey]JWTAtualiza email

Fluxo de Recover (sem login)

EndpointAuthComportamento
POST recover-methods/{cpf}[UseApiKey]Lista contatos mascarados (DB-lookup)
POST verify-recover-method[UseApiKey]Envia OTP para contato cadastrado (DB-lookup)
POST verify-code-recover[UseApiKey]Valida OTP, gera token (DB-lookup do contato)
POST change-password[UseApiKey]Troca senha com token do cache

Recover Sub-Flow (sem login, update-contact durante recover) → REMOVIDO

EndpointAuthProblema
POST verify-phone[UseApiKey]Mesmo endpoint do update-contact, sem JWT
POST verify-email[UseApiKey]Mesmo endpoint do update-contact, sem JWT
POST verify-code[UseApiKey]Mesmo endpoint do update-contact, sem JWT
POST change-cellphone[Authorize(JwtBearer)]Exige JWT que o sub-flow não tem ← inconsistente

3. Causa Raiz Detalhada

3.1 verify-email/verify-phone aceitam contato arbitrário

Arquivo: src/Auth.API/Application/Services/Implementations/VerifyCodeService.cs

Método VerifyEmailAsyncVerifyContact(ContactType.Email, input.Cpf, input.Email):

  • input.Email vem do body da request
  • É usado diretamente como destinatário do OTP
  • O DB é consultado apenas para obter user.Name (personalização do template)
  • Não verifica se o email pertence ao CPF

Comparação com verify-recover-method (seguro):

  • VerifyRecoverMethodAsyncGetContactData(type, cpf)_userRepository.GetEmail(cpf)
  • Busca o contato cadastrado no banco
  • Só envia para o contato registrado

3.2 Sem exigência de JWT

O PasswordController aplica [UseApiKey("SendMessage")] nos endpoints verify-phone, verify-email, verify-code. Nenhum deles tem [Authorize].

O app Flutter envia Authorization: Bearer no fluxo update-contact (customAuthorizationToken), mas o backend ignora por não exigir.

3.3 Cache key compartilhada

Arquivo: src/Auth.API/Application/Services/Implementations/VerifyCodeService.cs

VerifyVerificationCodeAsync (linha ~201):

var key = $"VerificationCodeModelInCache-{input.Cpf}";

UserService.ChangePassword lê da mesma chave:

var key = $"VerificationCodeModelInCache-{cpf}";

Token de qualquer fonte (verify-code ou verify-code-recover) é aceito por change-password.

4. Fluxo de Dados — verify-email

Request: POST /api/auth/verify-email
Body: { "cpf": "12345678900", "email": "atacante@x.com" }
Header: x-api-key: <recover_methods_token>

PasswordController.VerifyEmail()
→ input.Email = "atacante@x.com" (do body)
→ IVerifyCodeService.VerifyEmailAsync(input)

VerifyCodeService.VerifyEmailAsync(input)
→ VerifyContact(ContactType.Email, input.Cpf, input.Email)
→ contact = "atacante@x.com" (direto do input)

VerifyCodeService.VerifyContact(contactType, cpf, contact)
→ user = _userRepository.GetCpfAuthAsync(cpf) // busca user.Name apenas
→ data = CreateData(ContactType.Email, "atacante@x.com", user)
→ data.Contact = "atacante@x.com"
→ data.Arguments = { name: user.Name }
→ SaveData(data, key) // cache
→ _verificationCodeService.SendCodeAsync(data)

VerificationCodeService.SendEmail(model)
→ SendEmailDTO { EmailTo = "atacante@x.com", ... }
→ IMessageService.SendEmail(dto)
→ Email enviado para atacante@x.com ✓

5. Contextos de Uso dos Endpoints (antes do fix)

ContextoJWT?verify-emailverify-codechange-*
Update-contact (pós-login)Sim (enviado)Aceita (não exige)Aceita (não exige)Exige JWT
Recover principalNão❌ (não usa)❌ (usa verify-code-recover)x-api-key
Recover sub-flow (update contact)NãoAceita (não exige)Aceita (não exige)Exige JWT ← inconsistente

6. Arquivos Relevantes no Backend

CamadaArquivoResponsabilidade
Controllersrc/Auth.API/Controllers/PasswordController.csTodos endpoints de verify/change/recover
Servicesrc/Auth.API/Application/Services/Implementations/VerifyCodeService.csOTP, validação, cache keys
Servicesrc/Auth.API/Application/Services/Implementations/UserService.csrecover-methods, change-password, change-cellphone, change-email
Servicesrc/Auth.API/Application/Services/Implementations/VerificationCodeService.csGeração e envio de OTP (SMS/Email)
Filtersrc/Auth.API/Filters/UseApiKeyAttribute.csValidação x-api-key
Integrationsrc/Auth.API/Integration/MessageService.csSMS e e-mail
Integrationsrc/Auth.API/Integration/AdminService.csPersistência de senha/telefone/email
InputModelsrc/Auth.API/Application/InputModels/VerifyEmailInputModel.cs{ Cpf, Email }
InputModelsrc/Auth.API/Application/InputModels/VerifyPhoneInputModel.cs{ Cpf, CellPhone }
InputModelsrc/Auth.API/Application/InputModels/VerifyCodeInputModel.cs{ VerificationCode, Cpf, CellPhone, Email }
Entitysrc/Auth.API/Data/Entities/VerificationCodeModel.csModelos de cache

7. Arquivos Relevantes no App Flutter

CamadaArquivoResponsabilidade
Controllerlib/controllers/code_verification/by_method/code_verification_by_method_controller.dartSub-fluxo de recover (a remover)
Controllerlib/controllers/code_verification/phone/code_verification_by_phone_controller.dartverify-phone + verify-code + change-cellphone
Controllerlib/controllers/code_verification/email/code_verification_by_email_controller.dartverify-email + verify-code + change-email
Controllerlib/controllers/update_phone/update_phone_controller.dartEntrada de novo telefone
Controllerlib/controllers/update_email/update_email_controller.dartEntrada de novo email
Interceptorlib/middlewares/http/recover_methods_interceptor.dartInjeta x-api-key
Interceptorlib/middlewares/http/change_password.dartInjeta Authorization
Servicelib/services/firebase_remote_config_service.dartTokens de Remote Config
Utilslib/shared/utils/api_utils.dartConstantes de endpoints