agentggagentgg
Back to all findings
MEDIUMconfirmedweak-password-policyweak-password-policybeaae65e46a7

User password setter hashes any input with no length or strength check

The centralized Sequelize `password` setter hashes the raw plaintext with no length/complexity/common-password validation, and every upstream route (register, change-password, reset-password) relies on it without adding checks.

Filemodels/user.ts
Lines7479
Confidence
90%
File statusvalidated
Details

All password mutations funnel through the User model's password setter:

// models/user.ts:74-79
password: {
  type: DataTypes.STRING,
  set (clearTextPassword: string) {
    this.setDataValue('password', security.hash(clearTextPassword))
  }
},

The setter accepts any string — there is no min-length check, no equality-with-email check, and no common-password deny-list. Tracing upstream paths:

  • routes/changePassword.ts:14-51 reads query.new and calls user.update({ password: newPasswordInString }). The only checks are !newPassword || newPassword === 'undefined' and the repeat equality; nothing enforces a minimum length.
  • routes/resetPassword.ts:20-44 reads body.new and calls user.update({ password: newPassword }) after only verifying the security answer and new === repeat.
  • Registration via Sequelize User.create({ password }) flows through the same setter.

The Angular client forms (frontend/src/app/change-password/change-password.component.ts:64-65 and frontend/src/app/forgot-password/forgot-password.component.ts:41) use Validators.minLength(5), which (a) is below the brief's bare minimum of 8 and (b) is purely client-side UX — trivially bypassed by calling the REST endpoints directly.

Result: a user (or attacker) can set a password of length 1 or to a common value like 123 or password. This matches the brief's pattern "Model setter that hashes but doesn't validate".

Proof of concept

Authenticate as any user, then issue:

GET /rest/user/change-password?current=<current>&new=a&repeat=a
Authorization: Bearer <token>

The response is 200 OK and the user's password is now the single character a. The same works through POST /rest/user/reset-password with { "email":..., "answer":..., "new":"a", "repeat":"a" }. No length, complexity, or common-password check rejects the input on the server side.

Impact

Any user can set a trivially weak password, and there is no server-side floor on registration/reset/change flows. Attackers can: (1) brute-force accounts with very small password spaces; (2) coerce or social-engineer users into 1-char/common passwords; (3) bypass any client-only minLength hint by calling the REST endpoints directly. Affects all authentication-bearing functionality.

Validation
confirmed

The password setter at lines 74-79 calls security.hash(clearTextPassword) directly with zero validation — no min length, no complexity, no deny-list, and no validate block on the field (compare to role which has isIn validation). Since every write path (register, change-password, reset-password) routes through this setter and the model has no Sequelize-level validators, the server accepts arbitrarily weak passwords like "a". This matches the weak-password-policy pattern exactly, and the PoC against /rest/user/change-password is a known Juice Shop flow that exercises this same setter.

CVSS 3.1
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N
Base score: 5.4 · MEDIUM

The password setter in models/user.ts:74-79 hashes any string without length/complexity checks, and the upstream routes/changePassword.ts and routes/resetPassword.ts flows expose this over HTTP REST endpoints (AV:N) with no race or special preconditions (AC:L). The most directly demonstrated PoC (GET /rest/user/change-password) requires a valid Bearer token, so PR:L; no victim interaction is needed (UI:N) and the impact remains within the auth component (S:U). The weakness itself does not directly disclose or alter data — its impact is enabling trivial brute-forcing of accounts that opt for 1-char/common passwords, yielding limited and indirect Confidentiality/Integrity impact (L/L) and no availability consequence (A:N).

References