oauthlint-rules 0.1.1 → 0.2.0
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/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +10 -4
- package/dist/schema.js.map +1 -1
- package/package.json +9 -2
- package/rules/cors/null-origin.yml +53 -0
- package/rules/cors/reflect-origin.yml +68 -0
- package/rules/flow/credentials-in-url.yml +37 -0
- package/rules/flow/secret-in-log.yml +95 -0
- package/rules/flow/timing-unsafe-compare.yml +62 -16
- package/rules/flow/weak-bcrypt-rounds.yml +41 -0
- package/rules/flow/weak-password-hash.yml +74 -0
- package/rules/go/cookie/insecure.yml +35 -0
- package/rules/go/cors/allow-all.yml +44 -0
- package/rules/go/crypto/bcrypt-low-cost.yml +37 -0
- package/rules/go/crypto/weak-cipher.yml +38 -0
- package/rules/go/crypto/weak-password-hash.yml +47 -0
- package/rules/go/flow/weak-rand.yml +61 -0
- package/rules/go/jwt/hardcoded-secret.yml +37 -0
- package/rules/go/jwt/none-algorithm.yml +38 -0
- package/rules/go/jwt/parse-unverified.yml +33 -0
- package/rules/go/jwt/unchecked-method.yml +72 -0
- package/rules/go/tls/insecure-skip-verify.yml +32 -0
- package/rules/go/tls/min-version.yml +36 -0
- package/rules/java/cookie/insecure.yml +31 -0
- package/rules/java/cors/allow-all.yml +47 -0
- package/rules/java/crypto/ecb-mode.yml +42 -0
- package/rules/java/crypto/insecure-random.yml +62 -0
- package/rules/java/crypto/weak-password-hash.yml +47 -0
- package/rules/java/jwt/hardcoded-secret.yml +43 -0
- package/rules/java/jwt/unsigned-jwt.yml +41 -0
- package/rules/java/session/fixation-disabled.yml +34 -0
- package/rules/java/tls/trust-all-certs.yml +48 -0
- package/rules/java/web/csrf-disabled.yml +35 -0
- package/rules/java/web/frame-options-disabled.yml +37 -0
- package/rules/java/web/permit-all.yml +36 -0
- package/rules/jwt/decode-without-verify.yml +56 -0
- package/rules/jwt/no-algorithms-allowlist.yml +38 -0
- package/rules/oauth/no-nonce.yml +55 -0
- package/rules/oauth/pkce-plain.yml +40 -0
- package/rules/py/cookie/insecure-flags.yml +41 -0
- package/rules/py/flow/csrf-exempt.yml +39 -0
- package/rules/py/flow/debug-enabled.yml +37 -0
- package/rules/py/flow/insecure-random-token.yml +51 -0
- package/rules/py/flow/requests-verify-disabled.yml +50 -0
- package/rules/py/flow/weak-password-hash.yml +64 -0
- package/rules/py/jwt/alg-none.yml +41 -0
- package/rules/py/jwt/hardcoded-secret.yml +40 -0
- package/rules/py/jwt/no-algorithms.yml +45 -0
- package/rules/py/jwt/no-verify.yml +37 -0
- package/rules/py/secret/django-hardcoded-key.yml +32 -0
- package/rules/py/secret/flask-hardcoded-key.yml +33 -0
- package/rules/rust/cookie/insecure.yml +35 -0
- package/rules/rust/cors/permissive.yml +38 -0
- package/rules/rust/crypto/bcrypt-low-cost.yml +40 -0
- package/rules/rust/crypto/weak-cipher.yml +41 -0
- package/rules/rust/crypto/weak-password-hash.yml +46 -0
- package/rules/rust/flow/timing-unsafe-compare.yml +77 -0
- package/rules/rust/jwt/disable-signature-validation.yml +31 -0
- package/rules/rust/jwt/hardcoded-secret.yml +41 -0
- package/rules/rust/jwt/no-aud-validation.yml +34 -0
- package/rules/rust/jwt/no-expiration-validation.yml +35 -0
- package/rules/rust/tls/accept-invalid-certs.yml +30 -0
- package/rules/rust/tls/accept-invalid-hostnames.yml +32 -0
- package/rules/secret/public-env-secret.yml +58 -0
- package/rules/session/hardcoded-secret.yml +42 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: auth.go.crypto.bcrypt-low-cost
|
|
3
|
+
languages:
|
|
4
|
+
- go
|
|
5
|
+
severity: WARNING
|
|
6
|
+
message: |
|
|
7
|
+
`bcrypt.GenerateFromPassword` is called with a cost factor below 10. A
|
|
8
|
+
low work factor makes each hash cheap to compute, which lets an attacker
|
|
9
|
+
brute-force stolen password hashes far too quickly. OWASP recommends a
|
|
10
|
+
bcrypt cost of at least 10, and ≥ 12 for new applications, tuned so a
|
|
11
|
+
single hash takes roughly 250ms on your hardware.
|
|
12
|
+
|
|
13
|
+
Common LLM-generated mistake: `bcrypt.GenerateFromPassword(pw, 8)`
|
|
14
|
+
because the literal "looks fast enough". Use `bcrypt.DefaultCost` (10)
|
|
15
|
+
or raise the cost factor to 12 or higher.
|
|
16
|
+
# Matches only a numeric literal cost < 10 passed to
|
|
17
|
+
# `bcrypt.GenerateFromPassword`. `metavariable-comparison` constrains $N to
|
|
18
|
+
# numeric literals only, so `bcrypt.DefaultCost`, a cost ≥ 10, or a
|
|
19
|
+
# variable (`bcrypt.GenerateFromPassword(pw, cost)`) are NOT flagged.
|
|
20
|
+
patterns:
|
|
21
|
+
- pattern: bcrypt.GenerateFromPassword($PW, $N)
|
|
22
|
+
- metavariable-comparison:
|
|
23
|
+
metavariable: $N
|
|
24
|
+
comparison: $N < 10
|
|
25
|
+
metadata:
|
|
26
|
+
oauthlint-rule-id: AUTH-GO-CRYPTO-002
|
|
27
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/go-crypto-bcrypt-low-cost
|
|
28
|
+
category: security
|
|
29
|
+
cwe: CWE-916
|
|
30
|
+
owasp: A02:2021
|
|
31
|
+
llm-prevalence: MEDIUM
|
|
32
|
+
technology:
|
|
33
|
+
- bcrypt
|
|
34
|
+
references:
|
|
35
|
+
- https://pkg.go.dev/golang.org/x/crypto/bcrypt#GenerateFromPassword
|
|
36
|
+
- https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
|
|
37
|
+
- https://cwe.mitre.org/data/definitions/916.html
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: auth.go.crypto.weak-cipher
|
|
3
|
+
languages:
|
|
4
|
+
- go
|
|
5
|
+
severity: ERROR
|
|
6
|
+
message: |
|
|
7
|
+
A broken or deprecated block/stream cipher is used to protect data. DES
|
|
8
|
+
(`des.NewCipher`) and 3DES (`des.NewTripleDESCipher`) have a 64-bit block
|
|
9
|
+
and are considered insecure (Sweet32, brute-force), while RC4
|
|
10
|
+
(`rc4.NewCipher`) has well-known keystream biases and is forbidden by RFC
|
|
11
|
+
7465. For OAuth/OIDC this means tokens, client secrets, and other
|
|
12
|
+
sensitive material are not adequately protected and may be recovered by
|
|
13
|
+
an attacker.
|
|
14
|
+
|
|
15
|
+
Use authenticated AES instead: load the key with `crypto/aes`
|
|
16
|
+
(`aes.NewCipher(key)`) and wrap it in `cipher.NewGCM(block)` to get
|
|
17
|
+
AES-GCM, which provides both confidentiality and integrity.
|
|
18
|
+
# Flags only the broken ciphers DES, 3DES, and RC4. `aes.NewCipher(...)`
|
|
19
|
+
# is intentionally not matched.
|
|
20
|
+
patterns:
|
|
21
|
+
- pattern-either:
|
|
22
|
+
- pattern: des.NewCipher(...)
|
|
23
|
+
- pattern: des.NewTripleDESCipher(...)
|
|
24
|
+
- pattern: rc4.NewCipher(...)
|
|
25
|
+
metadata:
|
|
26
|
+
oauthlint-rule-id: AUTH-GO-CRYPTO-003
|
|
27
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/go-crypto-weak-cipher
|
|
28
|
+
category: security
|
|
29
|
+
cwe: CWE-327
|
|
30
|
+
owasp: A02:2021
|
|
31
|
+
llm-prevalence: MEDIUM
|
|
32
|
+
technology:
|
|
33
|
+
- crypto/des
|
|
34
|
+
- crypto/rc4
|
|
35
|
+
references:
|
|
36
|
+
- https://pkg.go.dev/crypto/aes#NewCipher
|
|
37
|
+
- https://pkg.go.dev/crypto/cipher#NewGCM
|
|
38
|
+
- https://cwe.mitre.org/data/definitions/327.html
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: auth.go.crypto.weak-password-hash
|
|
3
|
+
languages:
|
|
4
|
+
- go
|
|
5
|
+
severity: ERROR
|
|
6
|
+
message: |
|
|
7
|
+
A password is being hashed with a fast, general-purpose digest from the
|
|
8
|
+
Go standard library (MD5, SHA-1, SHA-256, SHA-512). These algorithms are
|
|
9
|
+
designed to be fast, which makes offline brute-force and rainbow-table
|
|
10
|
+
attacks cheap — they are NOT suitable for storing passwords (CWE-916).
|
|
11
|
+
|
|
12
|
+
Use a dedicated, slow password-hashing function with a per-password salt
|
|
13
|
+
and a tunable work factor: bcrypt
|
|
14
|
+
(`golang.org/x/crypto/bcrypt.GenerateFromPassword`), Argon2
|
|
15
|
+
(`golang.org/x/crypto/argon2.IDKey`), or scrypt
|
|
16
|
+
(`golang.org/x/crypto/scrypt.Key`). These resist brute-force by design.
|
|
17
|
+
# Anchored to the *password* character of the input: the digested/written
|
|
18
|
+
# argument must be named like a password (metavariable-regex on $PW). This
|
|
19
|
+
# avoids flagging `sha256.Sum256(fileBytes)` for file checksums or
|
|
20
|
+
# non-password fingerprints, and does not touch real password hashers
|
|
21
|
+
# (bcrypt/argon2/scrypt). Both the one-shot `Sum`/`Sum256`/`Sum512` form
|
|
22
|
+
# and the streaming `h := md5.New(); h.Write([]byte(password))` writer form
|
|
23
|
+
# are covered.
|
|
24
|
+
patterns:
|
|
25
|
+
- pattern-either:
|
|
26
|
+
- pattern: md5.Sum([]byte($PW))
|
|
27
|
+
- pattern: sha1.Sum([]byte($PW))
|
|
28
|
+
- pattern: sha256.Sum256([]byte($PW))
|
|
29
|
+
- pattern: sha512.Sum512([]byte($PW))
|
|
30
|
+
- pattern: $H.Write([]byte($PW))
|
|
31
|
+
- metavariable-regex:
|
|
32
|
+
metavariable: $PW
|
|
33
|
+
regex: (?i).*(password|passwd|pwd).*
|
|
34
|
+
metadata:
|
|
35
|
+
oauthlint-rule-id: AUTH-GO-CRYPTO-001
|
|
36
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/go-crypto-weak-password-hash
|
|
37
|
+
category: security
|
|
38
|
+
cwe: CWE-916
|
|
39
|
+
owasp: A02:2021
|
|
40
|
+
llm-prevalence: HIGH
|
|
41
|
+
technology:
|
|
42
|
+
- crypto/md5
|
|
43
|
+
- crypto/sha256
|
|
44
|
+
references:
|
|
45
|
+
- https://cwe.mitre.org/data/definitions/916.html
|
|
46
|
+
- https://pkg.go.dev/golang.org/x/crypto/bcrypt
|
|
47
|
+
- https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: auth.go.flow.weak-rand
|
|
3
|
+
languages:
|
|
4
|
+
- go
|
|
5
|
+
severity: ERROR
|
|
6
|
+
message: |
|
|
7
|
+
A security-sensitive value — its name indicates a token, secret, key,
|
|
8
|
+
password, nonce, OTP, or salt — is being generated with the `math/rand`
|
|
9
|
+
package. `math/rand` is a deterministic PRNG: its output is predictable
|
|
10
|
+
and an attacker who observes enough values can recover the seed and
|
|
11
|
+
forecast every future token. For OAuth/OIDC this means forgeable
|
|
12
|
+
`state` values, guessable authorization codes, and predictable refresh
|
|
13
|
+
tokens.
|
|
14
|
+
|
|
15
|
+
Use `crypto/rand` instead: allocate a byte slice and fill it with
|
|
16
|
+
`rand.Read(b)` (from `crypto/rand`), then hex- or base64url-encode it.
|
|
17
|
+
Never derive a credential from `math/rand`.
|
|
18
|
+
# We match `rand.$F(...)` where $F is one of the math/rand-EXCLUSIVE
|
|
19
|
+
# generators (Intn/Int/Int31/Int63/Float64/Perm). These do not exist in
|
|
20
|
+
# crypto/rand, so a match is unambiguous. We deliberately do NOT match
|
|
21
|
+
# `rand.Read(...)`, which exists in BOTH packages — the crypto/rand one is
|
|
22
|
+
# the safe path.
|
|
23
|
+
#
|
|
24
|
+
# The result must be assigned (`:=` or `=`) to a secret-shaped identifier;
|
|
25
|
+
# `i := rand.Intn(10)` for a loop or `delay := rand.Intn(500)` for jitter
|
|
26
|
+
# are not flagged. The name constraint uses `metavariable-pattern` +
|
|
27
|
+
# `pattern-regex` (rather than `metavariable-regex`) so that $VAR is
|
|
28
|
+
# recorded as a distinguishing binding — otherwise Semgrep's Go frontend
|
|
29
|
+
# leaves the short-var-decl LHS unbound and collapses two genuinely
|
|
30
|
+
# distinct secrets into a single finding.
|
|
31
|
+
pattern-either:
|
|
32
|
+
- patterns:
|
|
33
|
+
- pattern: '$VAR := rand.$F(...)'
|
|
34
|
+
- metavariable-regex:
|
|
35
|
+
metavariable: $F
|
|
36
|
+
regex: ^(Intn|Int|Int31|Int63|Float64|Perm)$
|
|
37
|
+
- metavariable-pattern:
|
|
38
|
+
metavariable: $VAR
|
|
39
|
+
patterns:
|
|
40
|
+
- pattern-regex: (?i)(token|secret|key|password|nonce|otp|salt)
|
|
41
|
+
- patterns:
|
|
42
|
+
- pattern: '$VAR = rand.$F(...)'
|
|
43
|
+
- metavariable-regex:
|
|
44
|
+
metavariable: $F
|
|
45
|
+
regex: ^(Intn|Int|Int31|Int63|Float64|Perm)$
|
|
46
|
+
- metavariable-pattern:
|
|
47
|
+
metavariable: $VAR
|
|
48
|
+
patterns:
|
|
49
|
+
- pattern-regex: (?i)(token|secret|key|password|nonce|otp|salt)
|
|
50
|
+
metadata:
|
|
51
|
+
oauthlint-rule-id: AUTH-GO-FLOW-001
|
|
52
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/go-flow-weak-rand
|
|
53
|
+
category: security
|
|
54
|
+
cwe: CWE-330
|
|
55
|
+
owasp: A02:2021
|
|
56
|
+
llm-prevalence: HIGH
|
|
57
|
+
technology:
|
|
58
|
+
- math/rand
|
|
59
|
+
references:
|
|
60
|
+
- https://pkg.go.dev/crypto/rand#Read
|
|
61
|
+
- https://cwe.mitre.org/data/definitions/330.html
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: auth.go.jwt.hardcoded-secret
|
|
3
|
+
languages:
|
|
4
|
+
- go
|
|
5
|
+
severity: ERROR
|
|
6
|
+
message: |
|
|
7
|
+
A JWT HMAC signing/verification key is hardcoded as a string literal in a
|
|
8
|
+
call to golang-jwt. Anyone who can read the source or git history can
|
|
9
|
+
forge or tamper with tokens, which is a complete authentication bypass.
|
|
10
|
+
|
|
11
|
+
Load the secret from the environment or a secret manager instead, e.g.
|
|
12
|
+
`key := []byte(os.Getenv("JWT_SECRET"))` and `token.SignedString(key)`.
|
|
13
|
+
Never commit signing keys to source control.
|
|
14
|
+
# Two shapes are flagged, both requiring the key to be a `[]byte("...")`
|
|
15
|
+
# built directly from a string literal (the `"..."` metavariadic only
|
|
16
|
+
# matches a string-literal AST node): the signing side
|
|
17
|
+
# `tok.SignedString([]byte("x"))`, and a golang-jwt Keyfunc that returns
|
|
18
|
+
# `[]byte("x")` as the verification key. Because the literal must appear
|
|
19
|
+
# in key position, `[]byte(os.Getenv("JWT_SECRET"))` and `[]byte(secret)`
|
|
20
|
+
# (a variable) are NOT matched — only string literals.
|
|
21
|
+
pattern-either:
|
|
22
|
+
- pattern: $TOKEN.SignedString([]byte("..."))
|
|
23
|
+
- patterns:
|
|
24
|
+
- pattern: return []byte("..."), nil
|
|
25
|
+
- pattern-inside: 'func(...) (interface{}, error) { ... }'
|
|
26
|
+
metadata:
|
|
27
|
+
oauthlint-rule-id: AUTH-GO-JWT-003
|
|
28
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/go-jwt-hardcoded-secret
|
|
29
|
+
category: security
|
|
30
|
+
cwe: CWE-798
|
|
31
|
+
owasp: API2:2023
|
|
32
|
+
llm-prevalence: HIGH
|
|
33
|
+
technology:
|
|
34
|
+
- golang-jwt
|
|
35
|
+
references:
|
|
36
|
+
- https://pkg.go.dev/github.com/golang-jwt/jwt/v5#Token.SignedString
|
|
37
|
+
- https://cwe.mitre.org/data/definitions/798.html
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: auth.go.jwt.none-algorithm
|
|
3
|
+
languages:
|
|
4
|
+
- go
|
|
5
|
+
severity: ERROR
|
|
6
|
+
message: |
|
|
7
|
+
A JWT is created or accepted with the `none` algorithm, which produces an
|
|
8
|
+
unsigned token. Anyone can forge such a token by setting `alg: none` and
|
|
9
|
+
omitting the signature, so the server has no way to verify who issued it —
|
|
10
|
+
a complete authentication bypass (CWE-347). In golang-jwt this happens
|
|
11
|
+
when signing with `jwt.SigningMethodNone` or passing the
|
|
12
|
+
`jwt.UnsafeAllowNoneSignatureType` sentinel to `SignedString`.
|
|
13
|
+
|
|
14
|
+
Never use the `none` algorithm. Sign tokens with a real algorithm such as
|
|
15
|
+
HS256 (`jwt.SigningMethodHS256`), RS256 (`jwt.SigningMethodRS256`), or
|
|
16
|
+
ES256 (`jwt.SigningMethodES256`) and verify them with the matching key.
|
|
17
|
+
# Targets code that ENABLES `none`: building a token with
|
|
18
|
+
# `SigningMethodNone`, or the `UnsafeAllowNoneSignatureType` sentinel (which
|
|
19
|
+
# is only ever used to allow none). A defensive comparison such as
|
|
20
|
+
# `token.Method == jwt.SigningMethodNone` to REJECT none is deliberately not
|
|
21
|
+
# flagged. HS256/RS256/ES256 are never matched.
|
|
22
|
+
pattern-either:
|
|
23
|
+
- pattern: jwt.NewWithClaims(jwt.SigningMethodNone, ...)
|
|
24
|
+
- pattern: jwt.New(jwt.SigningMethodNone, ...)
|
|
25
|
+
- pattern: jwt.New(jwt.SigningMethodNone)
|
|
26
|
+
- pattern: jwt.UnsafeAllowNoneSignatureType
|
|
27
|
+
metadata:
|
|
28
|
+
oauthlint-rule-id: AUTH-GO-JWT-001
|
|
29
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/go-jwt-none-algorithm
|
|
30
|
+
category: security
|
|
31
|
+
cwe: CWE-347
|
|
32
|
+
owasp: API2:2023
|
|
33
|
+
llm-prevalence: HIGH
|
|
34
|
+
technology:
|
|
35
|
+
- golang-jwt
|
|
36
|
+
references:
|
|
37
|
+
- https://pkg.go.dev/github.com/golang-jwt/jwt/v5#pkg-variables
|
|
38
|
+
- https://cwe.mitre.org/data/definitions/347.html
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: auth.go.jwt.parse-unverified
|
|
3
|
+
languages:
|
|
4
|
+
- go
|
|
5
|
+
severity: ERROR
|
|
6
|
+
message: |
|
|
7
|
+
A JWT is decoded with `ParseUnverified`, which parses the token WITHOUT
|
|
8
|
+
checking its signature. Any claims read from the result are fully
|
|
9
|
+
attacker-controlled — an attacker can forge arbitrary subjects, scopes,
|
|
10
|
+
or expiry and the token will still parse. Trusting these claims for
|
|
11
|
+
authentication or authorization is a complete auth bypass (CWE-347).
|
|
12
|
+
|
|
13
|
+
Verify the signature instead: use `jwt.Parse(tok, keyfunc)` or
|
|
14
|
+
`jwt.ParseWithClaims(tok, claims, keyfunc)` with a `Keyfunc` that returns
|
|
15
|
+
the expected signing key, so a token with a bad or missing signature is
|
|
16
|
+
rejected.
|
|
17
|
+
# Matches the `ParseUnverified` method on any *jwt.Parser receiver:
|
|
18
|
+
# `parser.ParseUnverified(...)`, `jwt.NewParser().ParseUnverified(...)`,
|
|
19
|
+
# `new(jwt.Parser).ParseUnverified(...)`. `jwt.Parse(...)` and
|
|
20
|
+
# `jwt.ParseWithClaims(...)` verify the signature and are NOT matched.
|
|
21
|
+
pattern: $P.ParseUnverified($TOK, $CLAIMS)
|
|
22
|
+
metadata:
|
|
23
|
+
oauthlint-rule-id: AUTH-GO-JWT-002
|
|
24
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/go-jwt-parse-unverified
|
|
25
|
+
category: security
|
|
26
|
+
cwe: CWE-347
|
|
27
|
+
owasp: API2:2023
|
|
28
|
+
llm-prevalence: HIGH
|
|
29
|
+
technology:
|
|
30
|
+
- golang-jwt
|
|
31
|
+
references:
|
|
32
|
+
- https://pkg.go.dev/github.com/golang-jwt/jwt/v5#Parser.ParseUnverified
|
|
33
|
+
- https://cwe.mitre.org/data/definitions/347.html
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: auth.go.jwt.unchecked-method
|
|
3
|
+
languages:
|
|
4
|
+
- go
|
|
5
|
+
severity: ERROR
|
|
6
|
+
message: |
|
|
7
|
+
A `Keyfunc` passed to `jwt.Parse`/`jwt.ParseWithClaims` returns the
|
|
8
|
+
verification key WITHOUT first checking `token.Method` (the signing
|
|
9
|
+
algorithm). This enables an algorithm-confusion attack: if the server
|
|
10
|
+
verifies RS256 tokens with an RSA public key, an attacker can forge a
|
|
11
|
+
token signed with HS256 using that public key as the HMAC secret, and the
|
|
12
|
+
library will accept it — a complete authentication bypass (CWE-347).
|
|
13
|
+
|
|
14
|
+
Always assert the signing method inside the keyfunc before returning the
|
|
15
|
+
key, e.g.:
|
|
16
|
+
`if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { return nil, err }`
|
|
17
|
+
(or `*jwt.SigningMethodRSA` / `*jwt.SigningMethodECDSA` as appropriate),
|
|
18
|
+
so a token signed with an unexpected algorithm is rejected.
|
|
19
|
+
# Matches a keyfunc literal that returns the key directly (`return $KEY, nil`)
|
|
20
|
+
# and whose body does NOT reference `.Method` (neither a type assertion
|
|
21
|
+
# `t.Method.(*jwt.SigningMethod...)` nor a comparison `t.Method != ...`).
|
|
22
|
+
# The `pattern-not` with the `<... $T.Method ...>` deep-expression operator
|
|
23
|
+
# excludes any keyfunc that inspects the signing method, keeping correct
|
|
24
|
+
# method-checking keyfuncs from being flagged.
|
|
25
|
+
patterns:
|
|
26
|
+
- pattern-either:
|
|
27
|
+
- pattern: |
|
|
28
|
+
jwt.Parse($TOK, func($T *jwt.Token) (interface{}, error) {
|
|
29
|
+
...
|
|
30
|
+
return $KEY, nil
|
|
31
|
+
})
|
|
32
|
+
- pattern: |
|
|
33
|
+
jwt.ParseWithClaims($TOK, $CLAIMS, func($T *jwt.Token) (interface{}, error) {
|
|
34
|
+
...
|
|
35
|
+
return $KEY, nil
|
|
36
|
+
})
|
|
37
|
+
- pattern-not: |
|
|
38
|
+
jwt.Parse($TOK, func($T *jwt.Token) (interface{}, error) {
|
|
39
|
+
...
|
|
40
|
+
$T.Method
|
|
41
|
+
...
|
|
42
|
+
})
|
|
43
|
+
- pattern-not: |
|
|
44
|
+
jwt.ParseWithClaims($TOK, $CLAIMS, func($T *jwt.Token) (interface{}, error) {
|
|
45
|
+
...
|
|
46
|
+
$T.Method
|
|
47
|
+
...
|
|
48
|
+
})
|
|
49
|
+
- pattern-not: |
|
|
50
|
+
jwt.Parse($TOK, func($T *jwt.Token) (interface{}, error) {
|
|
51
|
+
...
|
|
52
|
+
if <... $T.Method ...> { ... }
|
|
53
|
+
...
|
|
54
|
+
})
|
|
55
|
+
- pattern-not: |
|
|
56
|
+
jwt.ParseWithClaims($TOK, $CLAIMS, func($T *jwt.Token) (interface{}, error) {
|
|
57
|
+
...
|
|
58
|
+
if <... $T.Method ...> { ... }
|
|
59
|
+
...
|
|
60
|
+
})
|
|
61
|
+
metadata:
|
|
62
|
+
oauthlint-rule-id: AUTH-GO-JWT-004
|
|
63
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/go-jwt-unchecked-method
|
|
64
|
+
category: security
|
|
65
|
+
cwe: CWE-347
|
|
66
|
+
owasp: API2:2023
|
|
67
|
+
llm-prevalence: HIGH
|
|
68
|
+
technology:
|
|
69
|
+
- golang-jwt
|
|
70
|
+
references:
|
|
71
|
+
- https://pkg.go.dev/github.com/golang-jwt/jwt/v5#Keyfunc
|
|
72
|
+
- https://cwe.mitre.org/data/definitions/347.html
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: auth.go.tls.insecure-skip-verify
|
|
3
|
+
languages:
|
|
4
|
+
- go
|
|
5
|
+
severity: ERROR
|
|
6
|
+
message: |
|
|
7
|
+
A `tls.Config` is created with `InsecureSkipVerify: true`, which disables
|
|
8
|
+
verification of the server's certificate chain and host name. Any
|
|
9
|
+
attacker who can intercept the connection can present any certificate
|
|
10
|
+
and read or tamper with the traffic — a classic man-in-the-middle hole.
|
|
11
|
+
For OAuth/OIDC this leaks authorization codes, access tokens, and client
|
|
12
|
+
secrets in transit.
|
|
13
|
+
|
|
14
|
+
Never set `InsecureSkipVerify: true`. Leave verification on (the default).
|
|
15
|
+
To trust a private CA in development, set `RootCAs` to a `*x509.CertPool`
|
|
16
|
+
loaded with that CA instead.
|
|
17
|
+
# Matches only the literal `true`. `InsecureSkipVerify: false` and the
|
|
18
|
+
# field's absence are not flagged. Works for `tls.Config{...}` and
|
|
19
|
+
# `&tls.Config{...}` (the composite literal matches inside the address-of).
|
|
20
|
+
pattern: 'tls.Config{..., InsecureSkipVerify: true, ...}'
|
|
21
|
+
metadata:
|
|
22
|
+
oauthlint-rule-id: AUTH-GO-TLS-001
|
|
23
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/go-tls-insecure-skip-verify
|
|
24
|
+
category: security
|
|
25
|
+
cwe: CWE-295
|
|
26
|
+
owasp: A02:2021
|
|
27
|
+
llm-prevalence: HIGH
|
|
28
|
+
technology:
|
|
29
|
+
- crypto/tls
|
|
30
|
+
references:
|
|
31
|
+
- https://pkg.go.dev/crypto/tls#Config
|
|
32
|
+
- https://cwe.mitre.org/data/definitions/295.html
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: auth.go.tls.min-version
|
|
3
|
+
languages:
|
|
4
|
+
- go
|
|
5
|
+
severity: ERROR
|
|
6
|
+
message: |
|
|
7
|
+
A `tls.Config` is created with `MinVersion` pinned to an obsolete
|
|
8
|
+
protocol — SSL 3.0, TLS 1.0, or TLS 1.1. These versions have known
|
|
9
|
+
cryptographic weaknesses (POODLE, BEAST, downgrade attacks) and are
|
|
10
|
+
deprecated by RFC 8996. Allowing them lets an attacker negotiate a
|
|
11
|
+
broken cipher and intercept or tamper with OAuth/OIDC traffic,
|
|
12
|
+
leaking authorization codes, access tokens, and client secrets.
|
|
13
|
+
|
|
14
|
+
Set `MinVersion` to at least `tls.VersionTLS12`, and ideally
|
|
15
|
+
`tls.VersionTLS13`, so the handshake refuses obsolete protocols.
|
|
16
|
+
# Matches the obsolete constants only. `tls.VersionTLS12` and
|
|
17
|
+
# `tls.VersionTLS13` are not flagged. Works for both `tls.Config{...}`
|
|
18
|
+
# and `&tls.Config{...}` (the composite literal matches inside `&`).
|
|
19
|
+
patterns:
|
|
20
|
+
- pattern: 'tls.Config{..., MinVersion: $V, ...}'
|
|
21
|
+
- metavariable-regex:
|
|
22
|
+
metavariable: $V
|
|
23
|
+
regex: ^tls\.(VersionSSL30|VersionTLS10|VersionTLS11)$
|
|
24
|
+
metadata:
|
|
25
|
+
oauthlint-rule-id: AUTH-GO-TLS-002
|
|
26
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/go-tls-min-version
|
|
27
|
+
category: security
|
|
28
|
+
cwe: CWE-326
|
|
29
|
+
owasp: A02:2021
|
|
30
|
+
llm-prevalence: MEDIUM
|
|
31
|
+
technology:
|
|
32
|
+
- crypto/tls
|
|
33
|
+
references:
|
|
34
|
+
- https://pkg.go.dev/crypto/tls#Config
|
|
35
|
+
- https://datatracker.ietf.org/doc/html/rfc8996
|
|
36
|
+
- https://cwe.mitre.org/data/definitions/326.html
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: auth.java.cookie.insecure
|
|
3
|
+
languages:
|
|
4
|
+
- java
|
|
5
|
+
severity: ERROR
|
|
6
|
+
message: |
|
|
7
|
+
A servlet Cookie is created with a security attribute explicitly
|
|
8
|
+
disabled. `setSecure(false)` lets the cookie travel over plain HTTP,
|
|
9
|
+
and `setHttpOnly(false)` makes it readable from JavaScript — either way
|
|
10
|
+
a session or auth cookie can be intercepted or stolen (CWE-614). This is
|
|
11
|
+
a common AI-generated mistake where the flag is set to `false` to "make
|
|
12
|
+
it work" over localhost and never switched back.
|
|
13
|
+
|
|
14
|
+
Set `cookie.setSecure(true)` and `cookie.setHttpOnly(true)` on every
|
|
15
|
+
session or authentication cookie, and add `SameSite` (e.g. `Strict` or
|
|
16
|
+
`Lax`) to further limit cross-site exposure.
|
|
17
|
+
pattern-either:
|
|
18
|
+
- pattern: $C.setSecure(false)
|
|
19
|
+
- pattern: $C.setHttpOnly(false)
|
|
20
|
+
metadata:
|
|
21
|
+
oauthlint-rule-id: AUTH-JAVA-COOKIE-001
|
|
22
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/java-cookie-insecure
|
|
23
|
+
category: security
|
|
24
|
+
cwe: CWE-614
|
|
25
|
+
owasp: A05:2021
|
|
26
|
+
llm-prevalence: HIGH
|
|
27
|
+
technology:
|
|
28
|
+
- servlet
|
|
29
|
+
references:
|
|
30
|
+
- https://owasp.org/www-community/controls/SecureCookieAttribute
|
|
31
|
+
- https://cwe.mitre.org/data/definitions/614.html
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: auth.java.cors.allow-all
|
|
3
|
+
languages:
|
|
4
|
+
- java
|
|
5
|
+
severity: ERROR
|
|
6
|
+
message: |
|
|
7
|
+
CORS is configured to allow every origin with the wildcard `*`. Any
|
|
8
|
+
website can then make cross-origin requests to this endpoint, defeating
|
|
9
|
+
the same-origin policy (CWE-942). This is a common AI-generated Spring
|
|
10
|
+
mistake — `@CrossOrigin(origins = "*")` or `addAllowedOrigin("*")` is
|
|
11
|
+
pasted in to "make the browser call work" and the intended scope is
|
|
12
|
+
never added. A bare `@CrossOrigin` (no arguments) also defaults to all
|
|
13
|
+
origins.
|
|
14
|
+
|
|
15
|
+
Restrict CORS to an explicit allowlist of trusted origins instead, e.g.
|
|
16
|
+
`@CrossOrigin(origins = "https://app.example.com")` or
|
|
17
|
+
`config.setAllowedOrigins(List.of("https://app.example.com"))`. Note that
|
|
18
|
+
`addAllowedOriginPattern("*")` combined with `setAllowCredentials(true)`
|
|
19
|
+
is just as dangerous, because it sends the victim's cookies cross-origin.
|
|
20
|
+
# Covers the Spring MVC annotation (with an explicit `origins = "*"`, with
|
|
21
|
+
# the value alias `@CrossOrigin("*")`, and the bare `@CrossOrigin` which
|
|
22
|
+
# defaults to all origins), plus the programmatic CorsConfiguration API:
|
|
23
|
+
# `addAllowedOrigin("*")` and `setAllowedOrigins(...)` whose argument is a
|
|
24
|
+
# list literal containing `"*"` (`List.of("*")`, `Arrays.asList("*")`,
|
|
25
|
+
# `Collections.singletonList("*")`, ...). Only the literal `"*"` is
|
|
26
|
+
# flagged — explicit origins like `"https://app.example.com"` are not.
|
|
27
|
+
pattern-either:
|
|
28
|
+
- pattern: '@CrossOrigin(origins = "*")'
|
|
29
|
+
- pattern: '@CrossOrigin(origins = {..., "*", ...})'
|
|
30
|
+
- pattern: '@CrossOrigin("*")'
|
|
31
|
+
- pattern: |
|
|
32
|
+
@CrossOrigin
|
|
33
|
+
$RET $M(...) { ... }
|
|
34
|
+
- pattern: $C.addAllowedOrigin("*")
|
|
35
|
+
- pattern: $C.setAllowedOrigins($L(..., "*", ...))
|
|
36
|
+
metadata:
|
|
37
|
+
oauthlint-rule-id: AUTH-JAVA-CORS-001
|
|
38
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/java-cors-allow-all
|
|
39
|
+
category: security
|
|
40
|
+
cwe: CWE-942
|
|
41
|
+
owasp: A05:2021
|
|
42
|
+
llm-prevalence: HIGH
|
|
43
|
+
technology:
|
|
44
|
+
- spring-web
|
|
45
|
+
references:
|
|
46
|
+
- https://docs.spring.io/spring-framework/reference/web/webmvc-cors.html
|
|
47
|
+
- https://cwe.mitre.org/data/definitions/942.html
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: auth.java.crypto.ecb-mode
|
|
3
|
+
languages:
|
|
4
|
+
- java
|
|
5
|
+
severity: ERROR
|
|
6
|
+
message: |
|
|
7
|
+
A JCA `Cipher` is being created in ECB mode (or with a bare algorithm
|
|
8
|
+
alias that defaults to ECB). ECB encrypts each block independently, so
|
|
9
|
+
identical plaintext blocks produce identical ciphertext blocks — it is
|
|
10
|
+
deterministic and leaks structure/patterns of the plaintext (CWE-327).
|
|
11
|
+
`Cipher.getInstance("AES")`, `"DES"`, `"DESede"`, or `"Blowfish"` with no
|
|
12
|
+
mode specified silently falls back to ECB as well.
|
|
13
|
+
|
|
14
|
+
Use an authenticated mode: `Cipher.getInstance("AES/GCM/NoPadding")` with
|
|
15
|
+
a unique 12-byte IV per message. At minimum use CBC with a random IV plus
|
|
16
|
+
a separate HMAC (encrypt-then-MAC). Never use ECB or a bare cipher alias.
|
|
17
|
+
# Match only the transformation string passed to Cipher.getInstance(...).
|
|
18
|
+
# The metavariable-regex flags either a bare symmetric algorithm alias
|
|
19
|
+
# (AES / DES / DESede / Blowfish with no mode — these default to ECB) or an
|
|
20
|
+
# explicit ECB mode on one of those block ciphers (any padding). It is
|
|
21
|
+
# scoped to symmetric block ciphers on purpose: "AES/GCM/NoPadding" and
|
|
22
|
+
# "AES/CBC/PKCS5Padding" have a safe mode, and "RSA/ECB/OAEPWith..." is
|
|
23
|
+
# asymmetric (the "/ECB/" token there is a JCA quirk, not block-ECB), so
|
|
24
|
+
# none of them match.
|
|
25
|
+
patterns:
|
|
26
|
+
- pattern: javax.crypto.Cipher.getInstance($TRANSFORM, ...)
|
|
27
|
+
- metavariable-regex:
|
|
28
|
+
metavariable: $TRANSFORM
|
|
29
|
+
regex: (?i)^"(aes|des|desede|blowfish)(/ecb/.*)?"$
|
|
30
|
+
metadata:
|
|
31
|
+
oauthlint-rule-id: AUTH-JAVA-CRYPTO-003
|
|
32
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/java-crypto-ecb-mode
|
|
33
|
+
category: security
|
|
34
|
+
cwe: CWE-327
|
|
35
|
+
owasp: A02:2021
|
|
36
|
+
llm-prevalence: MEDIUM
|
|
37
|
+
technology:
|
|
38
|
+
- javax.crypto
|
|
39
|
+
references:
|
|
40
|
+
- https://cwe.mitre.org/data/definitions/327.html
|
|
41
|
+
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures/
|
|
42
|
+
- https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: auth.java.crypto.insecure-random
|
|
3
|
+
languages:
|
|
4
|
+
- java
|
|
5
|
+
severity: ERROR
|
|
6
|
+
message: |
|
|
7
|
+
A security-sensitive value (token, secret, key, password, nonce, OTP, or
|
|
8
|
+
salt) is generated with a non-cryptographic PRNG. `java.util.Random` and
|
|
9
|
+
`Math.random()` are predictable: their output is seeded from system time
|
|
10
|
+
and the internal state can be recovered from a few observed values, so an
|
|
11
|
+
attacker can reconstruct the "random" secret (CWE-330). This is a common
|
|
12
|
+
AI-generated mistake — `new Random().nextInt(...)` gets pasted in to
|
|
13
|
+
produce a token because it looks random enough.
|
|
14
|
+
|
|
15
|
+
Use `java.security.SecureRandom` instead. For example,
|
|
16
|
+
`new SecureRandom().nextBytes(buf)` to fill a byte buffer, then encode it
|
|
17
|
+
(Base64/hex) to build the token or secret.
|
|
18
|
+
pattern-either:
|
|
19
|
+
# $VAR = new Random().nextInt(...) / nextLong() / nextDouble() / nextFloat()
|
|
20
|
+
- patterns:
|
|
21
|
+
- pattern-either:
|
|
22
|
+
- pattern: $VAR = new java.util.Random(...).$M(...)
|
|
23
|
+
- pattern: $VAR = new Random(...).$M(...)
|
|
24
|
+
- metavariable-regex:
|
|
25
|
+
metavariable: $VAR
|
|
26
|
+
regex: (?i).*(token|secret|key|password|passwd|nonce|otp|salt).*
|
|
27
|
+
- metavariable-regex:
|
|
28
|
+
metavariable: $M
|
|
29
|
+
regex: ^(nextInt|nextLong|nextDouble|nextFloat|nextBoolean)$
|
|
30
|
+
# $VAR = Math.random()
|
|
31
|
+
- patterns:
|
|
32
|
+
- pattern: $VAR = Math.random()
|
|
33
|
+
- metavariable-regex:
|
|
34
|
+
metavariable: $VAR
|
|
35
|
+
regex: (?i).*(token|secret|key|password|passwd|nonce|otp|salt).*
|
|
36
|
+
# $RND.nextBytes($BUF) where $RND is a java.util.Random (not SecureRandom)
|
|
37
|
+
# and the buffer is a secret.
|
|
38
|
+
- patterns:
|
|
39
|
+
- pattern-either:
|
|
40
|
+
- pattern: |
|
|
41
|
+
java.util.Random $RND = new java.util.Random(...);
|
|
42
|
+
...
|
|
43
|
+
$RND.nextBytes($BUF);
|
|
44
|
+
- pattern: |
|
|
45
|
+
Random $RND = new Random(...);
|
|
46
|
+
...
|
|
47
|
+
$RND.nextBytes($BUF);
|
|
48
|
+
- metavariable-regex:
|
|
49
|
+
metavariable: $BUF
|
|
50
|
+
regex: (?i).*(token|secret|key|password|passwd|nonce|otp|salt).*
|
|
51
|
+
metadata:
|
|
52
|
+
oauthlint-rule-id: AUTH-JAVA-CRYPTO-002
|
|
53
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/java-crypto-insecure-random
|
|
54
|
+
category: security
|
|
55
|
+
cwe: CWE-330
|
|
56
|
+
owasp: A02:2021
|
|
57
|
+
llm-prevalence: HIGH
|
|
58
|
+
technology:
|
|
59
|
+
- java.util
|
|
60
|
+
references:
|
|
61
|
+
- https://docs.oracle.com/javase/8/docs/api/java/security/SecureRandom.html
|
|
62
|
+
- https://cwe.mitre.org/data/definitions/330.html
|