VNX-256: Plaintext Storage of a Password
Reports a password taken from the request and stored unhashed (a model's password field, setPassword, a create/insert call, an SQL INSERT/UPDATE), and any password written to web storage or a cookie.
| Field | Value |
|---|---|
| Rule ID | VNX-256 |
| Severity | High |
| SARIF level | error |
| Kind | sast |
| Languages | Go, Java, JavaScript / TypeScript, PHP, Python, Ruby |
| CWE | CWE-256 |
| CAPEC | CAPEC-66 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:H/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:H/DI:H/EX:H/EC:N/P:H |
| Tags | password-storage plaintext cwe-256 |
| Source | internal/sast/rules/vnx-256.rego |
Overview
A password stored as it arrived is disclosed to anyone who can read the database, a backup or a log of it, and is reusable on every other site where the user chose the same one. This maps to CWE-256: Plaintext Storage of a Password.
What Gets Flagged
A request value (Flask/Django request.form/POST/GET…, Express req.body, servlet getParameter() or a form/DTO getPassword(), PHP $_POST/$request->input(), Go FormValue/PostFormValue, gin c.PostForm, Rails params[...]), used directly or through a variable assigned from one up to 15 lines above, that is written to:
- an object’s password field (
user.password = ...,$user->password = ...,u.Password = ...); - a
setPassword(...)setter; - the password key of a create/insert call (
Model.create,objects.create,insertOne,new Model({...}),Model(...).save(), or a Pythonobj = Model(..., password=pw)saved withobj.save()within three lines); - an SQL
INSERTorUPDATEthat names a password column.
Regardless of the source, a password written to localStorage/sessionStorage, a response cookie or document.cookie under a key named password/passwd/pwd/pass is also reported.
newpassword = request.form["newpassword"]
u.password = newpassword # flagged
User.objects.create(username=u, password=request.POST["password"]) # flagged
const user = new User({ email: req.body.email, password: req.body.password }); // flagged
localStorage.setItem("password", password); // flagged
Not flagged: a value passed through a hash (bcrypt, argon2, scrypt, PBKDF2, password_hash, Hash::make, make_password, generate_password_hash, PasswordEncoder.encode, set_password, create_user) or re-assigned from one first; lookups and comparisons; projects that hash in a model hook or setter (Mongoose pre('save'), Sequelize/TypeORM hooks, Rails has_secure_password/Devise/before_save, Laravel setPasswordAttribute or a hashed cast, a Python @password.setter); hard-coded literals (VNX-SEC-014, VNX-259); comments, strings and test code.
Remediation
from werkzeug.security import generate_password_hash
u.password = generate_password_hash(request.form["newpassword"])
const user = new User({ email, password: await bcrypt.hash(req.body.password, 12) });
Keep passwords out of the browser entirely: authenticate once and store a session token.
References
Suppressing this rule
Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):
# nosec vnx-256
// nosec vnx-256
Or record an ignore rule: vulnetix ignore add --rule vnx-256 --reason "...". See Suppressing findings.