RSTR-JWT-005 — Go jwt.Parse keyfunc without method validation

Summary

github.com/golang-jwt/jwt's Parse / ParseWithClaims accepts any signing algorithm the token header claims, including none and HS256-vs-RS256 confusion attacks. The keyfunc you pass it must explicitly check token.Method and reject anything except the algorithm your application actually issues.

If the keyfunc returns the public key without checking — the standard copy-paste pattern from many tutorials — an attacker can flip the algorithm to HS256 and forge a token signed with the public key as the HMAC secret.

Severity

High.

Languages

Go.

What rastray flags

jwt.Parse / jwt.ParseWithClaims calls whose keyfunc returns a key without first checking token.Method:

token, err := jwt.Parse(raw, func(t *jwt.Token) (interface{}, error) {
    return publicKey, nil           // ← flagged: no t.Method check
})

What rastray deliberately does not flag

Keyfuncs that validate the signing method first:

token, err := jwt.Parse(raw, func(t *jwt.Token) (interface{}, error) {
    if _, ok := t.Method.(*jwt.SigningMethodRSA); !ok {
        return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
    }
    return publicKey, nil
})

How to fix it

Always assert the concrete SigningMethod type. For RS256:

import "github.com/golang-jwt/jwt/v5"

func verify(raw string) (*jwt.Token, error) {
    return jwt.Parse(raw, func(t *jwt.Token) (interface{}, error) {
        if _, ok := t.Method.(*jwt.SigningMethodRSA); !ok {
            return nil, fmt.Errorf("unexpected alg: %v", t.Header["alg"])
        }
        return publicKey, nil
    }, jwt.WithValidMethods([]string{"RS256"}))
}

jwt.WithValidMethods(...) (v5+) is a second belt-and-suspenders check that fails earlier than the keyfunc.

References