oauthlint-rules 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/rules/cookie/long-lived.yml +2 -2
- package/rules/flow/insecure-random.yml +5 -5
- package/rules/flow/oauth-credential-in-log.yml +9 -8
- package/rules/flow/secret-in-log.yml +7 -6
- package/rules/go/flow/oauth-credential-in-log.yml +9 -8
- package/rules/go/flow/weak-rand.yml +3 -3
- package/rules/go/jwt/skip-claims-validation.yml +6 -6
- package/rules/go/jwt/unchecked-method.yml +6 -6
- package/rules/go/tls/insecure-skip-verify.yml +6 -6
- package/rules/jwt/ignore-expiration.yml +4 -4
- package/rules/oauth/implicit-flow.yml +3 -3
- package/rules/oauth/long-token-lifetime.yml +3 -3
- package/rules/oauth/no-pkce.yml +4 -4
- package/rules/oauth/token-in-localstorage.yml +6 -7
- package/rules/py/cors/allow-all.yml +8 -7
- package/rules/py/flow/insecure-random-token.yml +6 -5
- package/rules/py/flow/oauth-credential-in-log.yml +9 -8
- package/rules/py/jwt/algorithm-confusion.yml +9 -8
- package/rules/py/oauth/insecure-transport-env.yml +3 -3
- package/rules/rust/crypto/weak-password-hash.yml +6 -5
- package/rules/rust/jwt/algorithm-confusion.yml +8 -7
- package/rules/rust/jwt/hardcoded-secret.yml +5 -4
- package/rules/rust/jwt/no-issuer-validation.yml +7 -7
- package/rules/session/hardcoded-secret.yml +3 -3
- package/rules/session/no-regeneration.yml +6 -5
- package/rules/tls/reject-unauthorized.yml +8 -7
package/package.json
CHANGED
|
@@ -6,8 +6,8 @@ rules:
|
|
|
6
6
|
severity: INFO
|
|
7
7
|
message: |
|
|
8
8
|
An auth-looking cookie is being set with a `maxAge` greater than
|
|
9
|
-
30 days
|
|
10
|
-
milliseconds
|
|
9
|
+
30 days. The threshold is 30 × 24 × 60 × 60 × 1000 = 2_592_000_000
|
|
10
|
+
milliseconds. Long-lived session cookies expand the blast radius
|
|
11
11
|
of any single token theft and bypass server-side revocation if
|
|
12
12
|
the application doesn't validate freshness on every request.
|
|
13
13
|
|
|
@@ -5,11 +5,11 @@ rules:
|
|
|
5
5
|
- typescript
|
|
6
6
|
severity: ERROR
|
|
7
7
|
message: |
|
|
8
|
-
`Math.random()`
|
|
9
|
-
|
|
10
|
-
value, an OAuth `state`, a session id, a nonce, or a
|
|
11
|
-
code. `Math.random()` is NOT cryptographically secure and
|
|
12
|
-
predictable enough for an attacker with enough samples to recover
|
|
8
|
+
`Math.random()` produces a value whose name marks it as
|
|
9
|
+
security-sensitive. The value is assigned to (or computed for) a token,
|
|
10
|
+
a CSRF value, an OAuth `state`, a session id, a nonce, or a
|
|
11
|
+
verification code. `Math.random()` is NOT cryptographically secure and
|
|
12
|
+
is predictable enough for an attacker with enough samples to recover
|
|
13
13
|
the seed.
|
|
14
14
|
|
|
15
15
|
Use `crypto.randomBytes(N)` (Node) or `crypto.getRandomValues()`
|
|
@@ -5,14 +5,15 @@ rules:
|
|
|
5
5
|
- typescript
|
|
6
6
|
severity: ERROR
|
|
7
7
|
message: |
|
|
8
|
-
An OAuth/OIDC credential
|
|
9
|
-
`code`, an `access_token` /
|
|
10
|
-
`
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
authorization code or token can be
|
|
15
|
-
complete the OAuth exchange
|
|
8
|
+
An OAuth/OIDC credential from the request flows into a logging call.
|
|
9
|
+
The tainted value is an authorization `code`, an `access_token` /
|
|
10
|
+
`refresh_token` / `id_token`, a bearer `token`, a `client_secret`, or
|
|
11
|
+
the raw `Authorization` header, and the sink is a `console.*` or
|
|
12
|
+
`logger.*` call. Logs are written to files, shipped to aggregators
|
|
13
|
+
(Datadog, Splunk, CloudWatch) and read by people and systems that should
|
|
14
|
+
never see live credentials. A leaked authorization code or token can be
|
|
15
|
+
replayed to impersonate the user or complete the OAuth exchange
|
|
16
|
+
(CWE-532).
|
|
16
17
|
|
|
17
18
|
Never log the raw credential. Redact or mask it before logging
|
|
18
19
|
(`token.slice(0, 4) + '…'`), log a non-sensitive identifier instead
|
|
@@ -5,12 +5,13 @@ rules:
|
|
|
5
5
|
- typescript
|
|
6
6
|
severity: WARNING
|
|
7
7
|
message: |
|
|
8
|
-
A secret-shaped value
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
A secret-shaped value is passed to a logging call. The logged
|
|
9
|
+
identifier is named like a credential (`password`, `token`, `secret`,
|
|
10
|
+
`apiKey`, `accessToken`, `refreshToken`, `privateKey`, `clientSecret`,
|
|
11
|
+
…) and the sink is a `console.*` or `logger.*` call. Logs are routinely
|
|
12
|
+
written to files, shipped to aggregators (Datadog, Splunk, CloudWatch)
|
|
13
|
+
and read by people who should never see the raw secret. This is a
|
|
14
|
+
textbook credential leak.
|
|
14
15
|
|
|
15
16
|
Never log secrets. Redact or mask them before logging
|
|
16
17
|
(`token.slice(0, 4) + '…'`), log a non-sensitive identifier instead
|
|
@@ -4,14 +4,15 @@ rules:
|
|
|
4
4
|
- go
|
|
5
5
|
severity: ERROR
|
|
6
6
|
message: |
|
|
7
|
-
An OAuth/OIDC credential
|
|
8
|
-
`code`, an `access_token` /
|
|
9
|
-
`
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
Splunk, CloudWatch) and read by people and systems
|
|
13
|
-
live credentials. A leaked authorization code or
|
|
14
|
-
to impersonate the user or complete the OAuth
|
|
7
|
+
An OAuth/OIDC credential from the HTTP request flows into a logging call.
|
|
8
|
+
The tainted value is an authorization `code`, an `access_token` /
|
|
9
|
+
`refresh_token` / `id_token`, a bearer `token`, a `client_secret`, or
|
|
10
|
+
the raw `Authorization` header, and the sink is a `log.*`, `slog.*`,
|
|
11
|
+
`fmt.Print*`, or `logger.*` call. Logs are written to files, shipped to
|
|
12
|
+
aggregators (Datadog, Splunk, CloudWatch) and read by people and systems
|
|
13
|
+
that should never see live credentials. A leaked authorization code or
|
|
14
|
+
token can be replayed to impersonate the user or complete the OAuth
|
|
15
|
+
exchange (CWE-532).
|
|
15
16
|
|
|
16
17
|
Never log the raw credential. Redact or mask it before logging, log a
|
|
17
18
|
non-sensitive identifier instead (a user id, a key id), or drop the
|
|
@@ -4,9 +4,9 @@ rules:
|
|
|
4
4
|
- go
|
|
5
5
|
severity: ERROR
|
|
6
6
|
message: |
|
|
7
|
-
A security-sensitive value
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
A security-sensitive value is being generated with the `math/rand`
|
|
8
|
+
package. Its name indicates a token, secret, key, password, nonce, OTP,
|
|
9
|
+
or salt. `math/rand` is a deterministic PRNG: its output is predictable
|
|
10
10
|
and an attacker who observes enough values can recover the seed and
|
|
11
11
|
forecast every future token. For OAuth/OIDC this means forgeable
|
|
12
12
|
`state` values, guessable authorization codes, and predictable refresh
|
|
@@ -4,12 +4,12 @@ rules:
|
|
|
4
4
|
- go
|
|
5
5
|
severity: WARNING
|
|
6
6
|
message: |
|
|
7
|
-
A JWT parser
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
parses successfully, so a stolen or
|
|
12
|
-
it were current (CWE-613).
|
|
7
|
+
A JWT parser turns off registered-claims validation with
|
|
8
|
+
`jwt.WithoutClaimsValidation()`. That option disables the `exp`
|
|
9
|
+
(expiry), `nbf` (not-before) and `iat` (issued-at) checks golang-jwt
|
|
10
|
+
performs by default. With validation disabled an expired or
|
|
11
|
+
not-yet-valid token still parses successfully, so a stolen or
|
|
12
|
+
long-expired token is accepted as if it were current (CWE-613).
|
|
13
13
|
|
|
14
14
|
Remove `jwt.WithoutClaimsValidation()` and let golang-jwt validate the
|
|
15
15
|
time-based claims. If a specific claim must be relaxed, scope it narrowly
|
|
@@ -4,12 +4,12 @@ rules:
|
|
|
4
4
|
- go
|
|
5
5
|
severity: ERROR
|
|
6
6
|
message: |
|
|
7
|
-
A `Keyfunc`
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
A JWT `Keyfunc` returns the verification key without checking `token.Method`, enabling algorithm confusion.
|
|
8
|
+
It is passed to `jwt.Parse`/`jwt.ParseWithClaims` and hands back the key
|
|
9
|
+
without first asserting the signing algorithm. If the server verifies
|
|
10
|
+
RS256 tokens with an RSA public key, an attacker can forge an HS256 token
|
|
11
|
+
using that public key as the HMAC secret, and the library will accept it:
|
|
12
|
+
a complete authentication bypass (CWE-347).
|
|
13
13
|
|
|
14
14
|
Always assert the signing method inside the keyfunc before returning the
|
|
15
15
|
key, e.g.:
|
|
@@ -4,12 +4,12 @@ rules:
|
|
|
4
4
|
- go
|
|
5
5
|
severity: ERROR
|
|
6
6
|
message: |
|
|
7
|
-
A `tls.Config`
|
|
8
|
-
verification of the server's certificate chain and host
|
|
9
|
-
attacker who can intercept the connection can present any
|
|
10
|
-
and read or tamper with the traffic
|
|
11
|
-
For OAuth/OIDC this leaks authorization codes,
|
|
12
|
-
secrets in transit.
|
|
7
|
+
A `tls.Config` sets `InsecureSkipVerify: true`, disabling TLS certificate verification.
|
|
8
|
+
This turns off verification of the server's certificate chain and host
|
|
9
|
+
name, so any attacker who can intercept the connection can present any
|
|
10
|
+
certificate and read or tamper with the traffic: a classic
|
|
11
|
+
man-in-the-middle hole. For OAuth/OIDC this leaks authorization codes,
|
|
12
|
+
access tokens, and client secrets in transit.
|
|
13
13
|
|
|
14
14
|
Never set `InsecureSkipVerify: true`. Leave verification on (the default).
|
|
15
15
|
To trust a private CA in development, set `RootCAs` to a `*x509.CertPool`
|
|
@@ -5,10 +5,10 @@ rules:
|
|
|
5
5
|
- typescript
|
|
6
6
|
severity: WARNING
|
|
7
7
|
message: |
|
|
8
|
-
`
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
`ignoreExpiration: true` in a `jsonwebtoken` `verify()` call disables the
|
|
9
|
+
`exp` claim check. An expired token is then accepted as valid forever, so
|
|
10
|
+
a stolen or long-old token never stops working, defeating the whole point
|
|
11
|
+
of short-lived access tokens.
|
|
12
12
|
|
|
13
13
|
Remove `ignoreExpiration: true` so the `exp` claim is enforced, and set a
|
|
14
14
|
sane `expiresIn` when signing (`jwt.sign(payload, key, { expiresIn: '15m'
|
|
@@ -5,9 +5,9 @@ rules:
|
|
|
5
5
|
- typescript
|
|
6
6
|
severity: ERROR
|
|
7
7
|
message: |
|
|
8
|
-
OAuth implicit flow (
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
OAuth implicit flow is deprecated by the OAuth 2.0 Security BCP (RFC 9700)
|
|
9
|
+
and the OAuth 2.1 draft. It is triggered by `response_type=token` or
|
|
10
|
+
`response_type=id_token token`. The access token leaks into the URL
|
|
11
11
|
fragment, browser history, and referrer headers, and there is no
|
|
12
12
|
refresh-token mechanism.
|
|
13
13
|
|
|
@@ -5,9 +5,9 @@ rules:
|
|
|
5
5
|
- typescript
|
|
6
6
|
severity: WARNING
|
|
7
7
|
message: |
|
|
8
|
-
An OAuth
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
An OAuth token-lifetime field is set to a literal value longer than
|
|
9
|
+
24 hours. The value of `expires_in` (or a comparable field) exceeds
|
|
10
|
+
86_400 seconds. Long-lived access tokens make every token theft
|
|
11
11
|
catastrophic because they remain valid for days or weeks; the
|
|
12
12
|
industry standard is 15-60 minutes for access tokens, paired with
|
|
13
13
|
a refresh-token rotation flow for longer sessions.
|
package/rules/oauth/no-pkce.yml
CHANGED
|
@@ -5,10 +5,10 @@ rules:
|
|
|
5
5
|
- typescript
|
|
6
6
|
severity: WARNING
|
|
7
7
|
message: |
|
|
8
|
-
OAuth
|
|
9
|
-
mobile, or native app)
|
|
10
|
-
|
|
11
|
-
and exchanged by an attacker.
|
|
8
|
+
OAuth authorization request from a public client omits the PKCE `code_challenge` parameter.
|
|
9
|
+
The request looks like a public client (SPA, mobile, or native app) yet
|
|
10
|
+
carries no `code_challenge`. Without PKCE, the authorization code can be
|
|
11
|
+
intercepted and exchanged by an attacker.
|
|
12
12
|
|
|
13
13
|
RFC 8252 §6 mandates PKCE for native/SPA clients. RFC 9700 (OAuth 2.0
|
|
14
14
|
Security BCP) recommends PKCE for ALL clients, including confidential
|
|
@@ -5,17 +5,16 @@ rules:
|
|
|
5
5
|
- typescript
|
|
6
6
|
severity: WARNING
|
|
7
7
|
message: |
|
|
8
|
-
An OAuth/OIDC token is
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
`getItem(...)`. There is no browser-side mitigation, unlike
|
|
8
|
+
An OAuth/OIDC token is written to `localStorage` / `sessionStorage`, readable by any script on the origin.
|
|
9
|
+
The key is token-named (`access_token`, `refresh_token`, `id_token`, …),
|
|
10
|
+
and web storage is exposed to every script on the page, so any XSS,
|
|
11
|
+
including a compromised third-party dependency, can exfiltrate the token
|
|
12
|
+
via `getItem(...)`. There is no browser-side mitigation, unlike
|
|
14
13
|
`HttpOnly` cookies.
|
|
15
14
|
|
|
16
15
|
Keep access/refresh/id tokens in memory only, or have the server
|
|
17
16
|
issue them in a `Secure; HttpOnly; SameSite=Strict` cookie.
|
|
18
|
-
`sessionStorage` is no safer than `localStorage` against XSS
|
|
17
|
+
`sessionStorage` is no safer than `localStorage` against XSS: it
|
|
19
18
|
grants the same attacker capability.
|
|
20
19
|
|
|
21
20
|
See CWE-922: Insecure Storage of Sensitive Information.
|
|
@@ -4,14 +4,15 @@ rules:
|
|
|
4
4
|
- python
|
|
5
5
|
severity: ERROR
|
|
6
6
|
message: |
|
|
7
|
-
Flask-CORS
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
Flask-CORS allows any origin while credentials are enabled.
|
|
8
|
+
This configuration pairs `supports_credentials=True` with a wildcard
|
|
9
|
+
origin (`origins="*"`, `origins=["*"]`, or no `origins` argument at all,
|
|
10
|
+
since Flask-CORS defaults to `*`). The CORS spec forbids the
|
|
10
11
|
`Access-Control-Allow-Origin: *` + `Access-Control-Allow-Credentials: true`
|
|
11
|
-
combination, so browsers will block it; the dangerous "fix" is to leave
|
|
12
|
-
wildcard in place while keeping credentials on, which exposes
|
|
13
|
-
cross-origin access to ANY website (CWE-942). For OAuth/OIDC
|
|
14
|
-
cookies, session tokens and CSRF protections cross-origin.
|
|
12
|
+
combination, so browsers will block it; the dangerous "fix" is to leave
|
|
13
|
+
the wildcard in place while keeping credentials on, which exposes
|
|
14
|
+
credentialed cross-origin access to ANY website (CWE-942). For OAuth/OIDC
|
|
15
|
+
this leaks cookies, session tokens and CSRF protections cross-origin.
|
|
15
16
|
|
|
16
17
|
Credentialed requests must use an explicit allow-list of trusted origins,
|
|
17
18
|
e.g. `CORS(app, origins=["https://app.example.com"], supports_credentials=True)`.
|
|
@@ -4,11 +4,12 @@ rules:
|
|
|
4
4
|
- python
|
|
5
5
|
severity: ERROR
|
|
6
6
|
message: |
|
|
7
|
-
A security-sensitive value
|
|
8
|
-
|
|
9
|
-
`random` is a pseudo-random number generator
|
|
10
|
-
and is NOT cryptographically secure
|
|
11
|
-
reproduced by an attacker, defeating the
|
|
7
|
+
A security-sensitive value is being generated with the `random` module.
|
|
8
|
+
The value is a token, secret, password, OTP, nonce, API key, or
|
|
9
|
+
reset/verification code. `random` is a pseudo-random number generator
|
|
10
|
+
seeded from predictable state and is NOT cryptographically secure: its
|
|
11
|
+
output can be predicted or reproduced by an attacker, defeating the
|
|
12
|
+
secret entirely.
|
|
12
13
|
|
|
13
14
|
Use the `secrets` module or `os.urandom` instead:
|
|
14
15
|
`secrets.token_urlsafe(32)`, `secrets.token_hex(16)`,
|
|
@@ -4,14 +4,15 @@ rules:
|
|
|
4
4
|
- python
|
|
5
5
|
severity: ERROR
|
|
6
6
|
message: |
|
|
7
|
-
An OAuth/OIDC credential
|
|
8
|
-
`code`, an `access_token` /
|
|
9
|
-
`
|
|
10
|
-
|
|
11
|
-
written to files, shipped to aggregators
|
|
12
|
-
and read by people and systems that should
|
|
13
|
-
leaked authorization code or token can be
|
|
14
|
-
user or complete the OAuth exchange
|
|
7
|
+
An OAuth/OIDC credential from the request flows into a logging call.
|
|
8
|
+
The tainted value is an authorization `code`, an `access_token` /
|
|
9
|
+
`refresh_token` / `id_token`, a bearer `token`, a `client_secret`, or
|
|
10
|
+
the raw `Authorization` header, and the sink is `print`, `logging.*`,
|
|
11
|
+
or a `logger.*` call. Logs are written to files, shipped to aggregators
|
|
12
|
+
(Datadog, Splunk, CloudWatch) and read by people and systems that should
|
|
13
|
+
never see live credentials. A leaked authorization code or token can be
|
|
14
|
+
replayed to impersonate the user or complete the OAuth exchange
|
|
15
|
+
(CWE-532).
|
|
15
16
|
|
|
16
17
|
Never log the raw credential. Redact or mask it before logging, log a
|
|
17
18
|
non-sensitive identifier instead (a user id, a key id), or drop the field
|
|
@@ -4,15 +4,16 @@ rules:
|
|
|
4
4
|
- python
|
|
5
5
|
severity: ERROR
|
|
6
6
|
message: |
|
|
7
|
-
A JWT is decoded with an `algorithms` allowlist that mixes
|
|
8
|
-
HMAC algorithm (HS256/HS384/HS512) with an
|
|
9
|
-
This enables the "algorithm confusion"
|
|
10
|
-
RSA/EC PUBLIC key can sign a forged
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
A JWT is decoded with an `algorithms` allowlist that mixes an HMAC algorithm with an asymmetric one.
|
|
8
|
+
The list combines a symmetric HMAC algorithm (HS256/HS384/HS512) with an
|
|
9
|
+
asymmetric one (RS*/ES*/PS*). This enables the "algorithm confusion"
|
|
10
|
+
attack: an attacker who knows your RSA/EC PUBLIC key can sign a forged
|
|
11
|
+
token with HMAC, using that public key string as the shared secret.
|
|
12
|
+
Because HS* is also accepted, PyJWT verifies the forgery with the public
|
|
13
|
+
key as the HMAC secret and treats it as valid: a complete authentication
|
|
14
|
+
bypass.
|
|
14
15
|
|
|
15
|
-
Allow only ONE algorithm family
|
|
16
|
+
Allow only ONE algorithm family, the one you actually use. If you issue
|
|
16
17
|
RS256 tokens, pin `jwt.decode(token, public_key, algorithms=["RS256"])`
|
|
17
18
|
and never also accept an HS* algorithm with the same verification key.
|
|
18
19
|
|
|
@@ -4,9 +4,9 @@ rules:
|
|
|
4
4
|
- python
|
|
5
5
|
severity: ERROR
|
|
6
6
|
message: |
|
|
7
|
-
`OAUTHLIB_INSECURE_TRANSPORT` is
|
|
8
|
-
|
|
9
|
-
requests integration, Django OAuth Toolkit
|
|
7
|
+
`OAUTHLIB_INSECURE_TRANSPORT` is set, disabling oauthlib's HTTPS
|
|
8
|
+
requirement for OAuth flows. This affects `requests-oauthlib`, Authlib's
|
|
9
|
+
requests integration, and Django OAuth Toolkit. oauthlib raises
|
|
10
10
|
`InsecureTransportError` to stop you exchanging codes and tokens over
|
|
11
11
|
cleartext; setting this variable silences that guard, so authorization
|
|
12
12
|
codes, `client_secret`, and access/refresh tokens travel over plain
|
|
@@ -4,11 +4,12 @@ rules:
|
|
|
4
4
|
- rust
|
|
5
5
|
severity: ERROR
|
|
6
6
|
message: |
|
|
7
|
-
A password is
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
storing passwords
|
|
7
|
+
A password is hashed with a fast, general-purpose digest unsuitable for
|
|
8
|
+
password storage. The digest is MD5 (via the `md5` crate) or
|
|
9
|
+
SHA-1/SHA-256/SHA-512 (via the RustCrypto `sha1`/`sha2` crates). These
|
|
10
|
+
algorithms are designed to be fast, which makes offline brute-force and
|
|
11
|
+
rainbow-table attacks cheap; they are NOT suitable for storing passwords
|
|
12
|
+
(CWE-916).
|
|
12
13
|
|
|
13
14
|
Use a dedicated, slow password-hashing function with a per-password salt
|
|
14
15
|
and a tunable work factor: Argon2 (`argon2` crate,
|
|
@@ -4,13 +4,14 @@ rules:
|
|
|
4
4
|
- rust
|
|
5
5
|
severity: ERROR
|
|
6
6
|
message: |
|
|
7
|
-
A `jsonwebtoken` `Validation` accepts
|
|
8
|
-
|
|
9
|
-
(`Algorithm::
|
|
10
|
-
|
|
11
|
-
(which is not secret) and signs a
|
|
12
|
-
key bytes as the HMAC shared
|
|
13
|
-
token as valid, letting the
|
|
7
|
+
A `jsonwebtoken` `Validation` accepts both HMAC and asymmetric algorithms,
|
|
8
|
+
enabling algorithm confusion. The accepted-algorithm list MIXES an HMAC
|
|
9
|
+
family (`Algorithm::HS256`/`HS384`/`HS512`) with an asymmetric family
|
|
10
|
+
(`Algorithm::RS*`/`ES*`/`PS*`). When both families are accepted, an
|
|
11
|
+
attacker takes your RSA/EC PUBLIC key (which is not secret) and signs a
|
|
12
|
+
forged token with HS*, using the public key bytes as the HMAC shared
|
|
13
|
+
secret. `decode` then verifies that forged token as valid, letting the
|
|
14
|
+
attacker mint arbitrary identities and claims.
|
|
14
15
|
|
|
15
16
|
Pin `validation.algorithms` to a SINGLE family you actually use, e.g.
|
|
16
17
|
`validation.algorithms = vec![Algorithm::RS256];` when your issuer signs
|
|
@@ -4,10 +4,11 @@ rules:
|
|
|
4
4
|
- rust
|
|
5
5
|
severity: ERROR
|
|
6
6
|
message: |
|
|
7
|
-
A JWT HMAC signing/verification key is hardcoded as a literal
|
|
8
|
-
to jsonwebtoken's `EncodingKey::from_secret` /
|
|
9
|
-
Anyone who can read the source or git history
|
|
10
|
-
tokens, which is a complete authentication
|
|
7
|
+
A JWT HMAC signing/verification key is hardcoded as a literal. It is
|
|
8
|
+
passed directly to jsonwebtoken's `EncodingKey::from_secret` /
|
|
9
|
+
`DecodingKey::from_secret`. Anyone who can read the source or git history
|
|
10
|
+
can forge or tamper with tokens, which is a complete authentication
|
|
11
|
+
bypass.
|
|
11
12
|
|
|
12
13
|
Load the secret at runtime from the environment or a secret manager
|
|
13
14
|
instead, e.g. `let key = std::env::var("JWT_SECRET")?;` followed by
|
|
@@ -4,13 +4,13 @@ rules:
|
|
|
4
4
|
- rust
|
|
5
5
|
severity: WARNING
|
|
6
6
|
message: |
|
|
7
|
-
A JWT is decoded with a `jsonwebtoken` `Validation` that never sets the
|
|
8
|
-
|
|
9
|
-
`jsonwebtoken` crate does not validate the `iss` claim unless
|
|
10
|
-
so a token signed by an attacker-controlled or otherwise
|
|
11
|
-
passes validation as long as the signature checks out.
|
|
12
|
-
lets a token from the wrong authorization server be
|
|
13
|
-
API.
|
|
7
|
+
A JWT is decoded with a `jsonwebtoken` `Validation` that never sets the expected issuer.
|
|
8
|
+
Because the issuer is not pinned, `decode` accepts a token minted by ANY
|
|
9
|
+
issuer: the `jsonwebtoken` crate does not validate the `iss` claim unless
|
|
10
|
+
you opt in, so a token signed by an attacker-controlled or otherwise
|
|
11
|
+
untrusted issuer passes validation as long as the signature checks out.
|
|
12
|
+
For OAuth/OIDC this lets a token from the wrong authorization server be
|
|
13
|
+
replayed against this API.
|
|
14
14
|
|
|
15
15
|
Pin the issuer before decoding, e.g.
|
|
16
16
|
`validation.set_issuer(&["https://issuer.example.com"])` (or set
|
|
@@ -5,9 +5,9 @@ rules:
|
|
|
5
5
|
- typescript
|
|
6
6
|
severity: ERROR
|
|
7
7
|
message: |
|
|
8
|
-
An `express-session` / `cookie-session` `secret` is
|
|
9
|
-
|
|
10
|
-
AI-generated example
|
|
8
|
+
An `express-session` / `cookie-session` `secret` is a hard-coded string
|
|
9
|
+
literal. The infamous `secret: 'keyboard cat'` is the canonical
|
|
10
|
+
AI-generated example. This key signs the session cookie: anyone who
|
|
11
11
|
reads it from your source or git history can forge arbitrary session
|
|
12
12
|
cookies and impersonate any user.
|
|
13
13
|
|
|
@@ -5,11 +5,12 @@ rules:
|
|
|
5
5
|
- typescript
|
|
6
6
|
severity: WARNING
|
|
7
7
|
message: |
|
|
8
|
-
The user is
|
|
9
|
-
`req.session.
|
|
10
|
-
session
|
|
11
|
-
|
|
12
|
-
before login retains access
|
|
8
|
+
The user is marked as logged in without first regenerating the session id.
|
|
9
|
+
The identity is written onto the session (`req.session.user = ...`,
|
|
10
|
+
`req.session.userId = ...`, etc.) before the session id is rotated. This
|
|
11
|
+
opens the door to session-fixation attacks: an attacker who plants a
|
|
12
|
+
known session id in the victim's browser before login retains access
|
|
13
|
+
after authentication succeeds.
|
|
13
14
|
|
|
14
15
|
Call `req.session.regenerate(cb)` (or your framework's equivalent)
|
|
15
16
|
between authenticating the credentials and writing the user
|
|
@@ -5,16 +5,17 @@ rules:
|
|
|
5
5
|
- typescript
|
|
6
6
|
severity: ERROR
|
|
7
7
|
message: |
|
|
8
|
-
|
|
9
|
-
`NODE_TLS_REJECT_UNAUTHORIZED=0`
|
|
10
|
-
certificate, including self-signed or
|
|
11
|
-
removes the protection TLS provides against
|
|
12
|
-
anyone able to intercept the network path can
|
|
13
|
-
certificate, then read and modify the traffic
|
|
8
|
+
TLS certificate validation is disabled for this connection.
|
|
9
|
+
Setting `rejectUnauthorized: false` or `NODE_TLS_REJECT_UNAUTHORIZED=0`
|
|
10
|
+
makes the connection accept ANY certificate, including self-signed or
|
|
11
|
+
attacker-supplied ones. This removes the protection TLS provides against
|
|
12
|
+
man-in-the-middle attacks: anyone able to intercept the network path can
|
|
13
|
+
present a forged certificate, then read and modify the traffic
|
|
14
|
+
(credentials, tokens, data).
|
|
14
15
|
|
|
15
16
|
Keep certificate validation enabled. If the server uses a private or
|
|
16
17
|
self-signed CA, supply that CA explicitly instead of turning validation
|
|
17
|
-
off
|
|
18
|
+
off, e.g. `new https.Agent({ ca: fs.readFileSync('ca.pem') })`. See
|
|
18
19
|
CWE-295 and the Node.js TLS docs.
|
|
19
20
|
# Matches an options object literal that contains `rejectUnauthorized: false`,
|
|
20
21
|
# wherever it appears (https.request, new https.Agent, tls.connect, axios
|