VNX-294: Authentication Bypass by Capture-replay
Detects signed tokens accepted without their age being enforced: JWT expiry verification disabled (ignoreExpiration, verify_exp False), a clock leeway of a day or more, and itsdangerous/Django timestamped signatures loaded without max_age.
| Field | Value |
|---|---|
| Rule ID | VNX-294 |
| Severity | High |
| SARIF level | error |
| Kind | sast |
| Languages | Java, JavaScript / TypeScript, Python |
| CWE | CWE-294 |
| 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 | auth replay token-expiry cwe-294 |
| Source | internal/sast/rules/vnx-294.rego |
Overview
VNX-294 maps to CWE-294: Authentication Bypass by Capture-replay. A token that is never too old is a token that can be replayed: once it appears in a log, a proxy, browser history or a forwarded e-mail, whoever holds it is authenticated.
The rule reports:
- Expiry verification switched off: jsonwebtoken
{ ignoreExpiration: true }; PyJWT/python-joseoptions={"verify_exp": False}(inline or in an options dict) and the oldverify_expiration=False. - A leeway of a day or more: jsonwebtoken/jose
clockTolerance, PyJWTleeway=, jjwtsetAllowedClockSkewSeconds/clockSkewSeconds, Auth0 java-jwtacceptLeeway/acceptExpiresAtwith a literal of 86400 or more,Long.MAX_VALUEorInteger.MAX_VALUE. - A timestamped signature read without a maximum age: itsdangerous
URLSafeTimedSerializer/TimedSerializer/TimestampSigner.loads()/.unsign()and Djangosigning.loads()/TimestampSigner().unsign()withoutmax_age.
What Gets Flagged
return jwt.verify(token, process.env.JWT_KEY, { ignoreExpiration: true }); // FLAGGED
serializer = URLSafeTimedSerializer(app.config["SECRET_KEY"])
email = serializer.loads(token, salt="password-reset") # FLAGGED: never expires
.setAllowedClockSkewSeconds(Long.MAX_VALUE) // FLAGGED
What Is Not Flagged
verify_exp: True,ignoreExpiration: false, and a leeway under a day.loads(..., max_age=3600)andsigning.loads(..., max_age=...).- Untimed serializers and signers,
json.loads/pickle.loads, comments and string literals.
Tokens issued without an exp claim are reported by VNX-JWT-002. Nonce or jti replay caches are not judged.
Remediation
Keep expiry verification on and tokens short-lived, refresh them instead of accepting expired ones, and pass max_age to every timestamped loads()/unsign():
email = serializer.loads(token, salt="password-reset", max_age=3600)
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-294
// nosec vnx-294
Or record an ignore rule: vulnetix ignore add --rule vnx-294 --reason "...". See Suppressing findings.