User model hashes password with no length or strength check
The Sequelize User model's password setter calls security.hash() directly on whatever string the user supplies, with no minimum length, complexity, common-password, or username-equality check — and there is no upstream validator on the registration or reset-password paths to compensate.
models/user.ts lines 76–81 define the password column's setter:
password: {
type: DataTypes.STRING,
set (clearTextPassword: string) {
this.setDataValue('password', security.hash(clearTextPassword))
}
}
No validate block, no length check, no refine/isStrongPassword/zxcvbn call, no comparison against email/username. The setter is the centralised choke point reached by both writeable code paths:
POST /api/Users(registration) — wired inserver.ts:402-416. The pre-handler only trims and rejects empty email/password (line 404:if (req.body.email.length !== 0 && req.body.password.length !== 0)). No further constraints exist beforefinale.resourceinvokesUser.create.POST /rest/user/reset-password—routes/resetPassword.ts:26-44. The handler only checks!newPassword || newPassword === 'undefined'andnewPassword !== repeatPassword, then callsuser.update({ password: newPassword })which goes through the same setter.
There is no Zod/Joi/class-validator schema and no validatePassword* helper in this chain — the candidate models/challenge.ts only enumerates challenge keys (it's not a validator). So a one-character password like a is accepted and stored. (Compounding the issue, security.hash is MD5 — crypto.createHash('md5') in lib/insecurity.ts:43 — but that's a separate brief.)
curl -X POST http://target/api/Users \ -H 'Content-Type: application/json' \ -d '{"email":"victim@example.com","password":"a","passwordRepeat":"a"}'
Response: 201 Created — the account is registered with the single-character password a. The same trivially-weak password can also be set via POST /rest/user/reset-password.
Users (including users who exist in the seed corpus and any newly-registered accounts) can choose arbitrarily weak passwords, including 1-character strings, dictionary words, the username, or top-100 passwords. This makes credential-stuffing and online password guessing trivially successful and combines with the 2FA brute-force above for full account takeover. Affects every registration and password-reset on the instance.
The password column definition at lines 76–81 has only a set hook that calls security.hash(clearTextPassword) with no validate block, length minimum, or complexity rule — unlike sibling fields like role which uses validate: { isIn: ... }. Since both registration (POST /api/Users via finale.resource) and password reset funnel through this setter, a one-character or trivially-weak password is accepted and stored. The detector's description of the unsafe element and exploit path matches the code exactly. (This is OWASP Juice Shop, which is intentionally vulnerable, but the finding accurately characterizes the code.)
The password setter at models/user.ts:76–81 calls security.hash() with zero validation, and neither POST /api/Users (server.ts:402-416, only checks non-empty) nor POST /rest/user/reset-password (routes/resetPassword.ts:26-44, only checks repeat match) imposes a length/strength rule, so users — including admins — can register or reset to 1-character or top-100 passwords. An unauthenticated remote attacker can therefore mount online guessing/credential-stuffing against the login endpoint with no preconditions (AV:N, AC:L, PR:N, UI:N). Successful guess yields full account takeover within the application's authority (S:U) giving full read and write of the victim's data and orders (C:H/I:H); availability is rated L since the attacker can lock out an individual victim by rotating their password but cannot down the service overall.
- CWE-521
- OWASP ASVS V2.1 Password Security
- NIST SP 800-63B §5.1.1.2