VNX-539: Use of Persistent Cookies Containing Sensitive Information
Detects persistent cookies that carry a credential or personal secret, and session or authentication cookies given a literal lifetime longer than 31 days, in JavaScript/TypeScript (Express, Koa), Python, Java (Servlet Cookie, Spring ResponseCookie), PHP and Ruby on Rails.
| Field | Value |
|---|---|
| Rule ID | VNX-539 |
| Severity | Medium |
| SARIF level | warning |
| Kind | sast |
| Languages | JavaScript / TypeScript, Python, Java, PHP, Ruby |
| CWE | CWE-539 |
| CAPEC | CAPEC-66 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:M/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C |
| Tags | cookie-persistent cwe-539 |
| Source | internal/sast/rules/vnx-539.rego |
Overview
VNX-539 maps to CWE-539: Use of Persistent Cookies Containing Sensitive Information. A cookie with a lifetime is written to the browser’s disk and survives the session, so anyone with access to the machine, its backups or malware on it can read it later. This rule reports two shapes:
- A credential in a persistent cookie. A cookie named for a password, secret, SSN, credit card, CVV, API key or private key, whose value is not a constant, set with any positive lifetime or an expiry that cannot be read statically: Express
res.cookie(..., { maxAge | expires }), Koactx.cookies.set(...), Pythonset_cookie(..., max_age= | expires=), JavaCookie.setMaxAge(n)and SpringResponseCookie.from(...).maxAge(...), PHPsetcookie(name, value, expiry), Railscookies.permanent[...] = vandcookies[...] = { value:, expires: N.days.from_now }. - A long-lived session. A session or authentication cookie (
session,sid,auth,token,jwt,userid) or the framework session cookie (express-session/cookie-sessioncookie.maxAge, DjangoSESSION_COOKIE_AGE, FlaskPERMANENT_SESSION_LIFETIME) with a literal lifetime over 31 days.
What Gets Flagged
// FLAGGED: the password is stored in the browser for 15 minutes past the session
res.cookie('password', req.body.password, { maxAge: 900000, httpOnly: true });
# FLAGGED: an identity cookie that lasts almost a year
response.set_cookie('userid', user.userid, max_age=31449600)
// FLAGGED
Cookie remember = new Cookie("password", password);
remember.setMaxAge(60 * 60 * 24 * 30);
What Is Not Flagged
- Cookies with no lifetime (browser-session cookies) and deletions (
maxAge 0,setMaxAge(0), an expiry in the past). - Constant values and preference cookies (
theme,lang). - Session lifetimes of 31 days or less (Flask’s and Django’s defaults are within that), lifetimes computed at run time, and remember-me cookies.
- Comments, string literals and test files.
Remediation
Never put credentials or personal data in cookies; keep them on the server and refer to them by an opaque session id. Give session cookies no maxAge/Expires, or a short one, and implement “remember me” as a separate, revocable, rotating token.
References
- CWE-539: Use of Persistent Cookies Containing Sensitive Information
- OWASP Session Management Cheat Sheet
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-539
// nosec vnx-539
Or record an ignore rule: vulnetix ignore add --rule vnx-539 --reason "...". See Suppressing findings.