VYPR
High severity7.5NVD Advisory· Published Mar 21, 2025· Updated Apr 15, 2026

CVE-2025-30204

CVE-2025-30204

Description

golang-jwt is a Go implementation of JSON Web Tokens. Starting in version 3.2.0 and prior to versions 5.2.2 and 4.5.2, the function parse.ParseUnverified splits (via a call to strings.Split) its argument (which is untrusted data) on periods. As a result, in the face of a malicious request whose Authorization header consists of Bearer followed by many period characters, a call to that function incurs allocations to the tune of O(n) bytes (where n stands for the length of the function's argument), with a constant factor of about 16. This issue is fixed in 5.2.2 and 4.5.2.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/golang-jwt/jwt/v5Go
>= 5.0.0-rc.1, < 5.2.25.2.2
github.com/golang-jwt/jwt/v4Go
< 4.5.24.5.2
github.com/golang-jwt/jwtGo
>= 3.2.0, <= 3.2.2

Patches

3
0951d184286d

Merge commit from fork

https://github.com/golang-jwt/jwtMichael FridmanMar 21, 2025via ghsa
2 files changed · +122 3
  • jwt_test.go+89 0 added
    @@ -0,0 +1,89 @@
    +package jwt
    +
    +import (
    +	"testing"
    +)
    +
    +func TestSplitToken(t *testing.T) {
    +	t.Parallel()
    +
    +	tests := []struct {
    +		name     string
    +		input    string
    +		expected []string
    +		isValid  bool
    +	}{
    +		{
    +			name:     "valid token with three parts",
    +			input:    "header.claims.signature",
    +			expected: []string{"header", "claims", "signature"},
    +			isValid:  true,
    +		},
    +		{
    +			name:     "invalid token with two parts only",
    +			input:    "header.claims",
    +			expected: nil,
    +			isValid:  false,
    +		},
    +		{
    +			name:     "invalid token with one part only",
    +			input:    "header",
    +			expected: nil,
    +			isValid:  false,
    +		},
    +		{
    +			name:     "invalid token with extra delimiter",
    +			input:    "header.claims.signature.extra",
    +			expected: nil,
    +			isValid:  false,
    +		},
    +		{
    +			name:     "invalid empty token",
    +			input:    "",
    +			expected: nil,
    +			isValid:  false,
    +		},
    +		{
    +			name:     "valid token with empty parts",
    +			input:    "..signature",
    +			expected: []string{"", "", "signature"},
    +			isValid:  true,
    +		},
    +		{
    +			// We are just splitting the token into parts, so we don't care about the actual values.
    +			// It is up to the caller to validate the parts.
    +			name:     "valid token with all parts empty",
    +			input:    "..",
    +			expected: []string{"", "", ""},
    +			isValid:  true,
    +		},
    +		{
    +			name:     "invalid token with just delimiters and extra part",
    +			input:    "...",
    +			expected: nil,
    +			isValid:  false,
    +		},
    +		{
    +			name:     "invalid token with many delimiters",
    +			input:    "header.claims.signature..................",
    +			expected: nil,
    +			isValid:  false,
    +		},
    +	}
    +
    +	for _, tt := range tests {
    +		t.Run(tt.name, func(t *testing.T) {
    +			parts, ok := splitToken(tt.input)
    +			if ok != tt.isValid {
    +				t.Errorf("expected %t, got %t", tt.isValid, ok)
    +			}
    +			if ok {
    +				for i, part := range tt.expected {
    +					if parts[i] != part {
    +						t.Errorf("expected %s, got %s", part, parts[i])
    +					}
    +				}
    +			}
    +		})
    +	}
    +}
    
  • parser.go+33 3 modified
    @@ -8,6 +8,8 @@ import (
     	"strings"
     )
     
    +const tokenDelimiter = "."
    +
     type Parser struct {
     	// If populated, only these methods will be considered valid.
     	validMethods []string
    @@ -136,9 +138,10 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
     // It's only ever useful in cases where you know the signature is valid (since it has already
     // been or will be checked elsewhere in the stack) and you want to extract values from it.
     func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Token, parts []string, err error) {
    -	parts = strings.Split(tokenString, ".")
    -	if len(parts) != 3 {
    -		return nil, parts, newError("token contains an invalid number of segments", ErrTokenMalformed)
    +	var ok bool
    +	parts, ok = splitToken(tokenString)
    +	if !ok {
    +		return nil, nil, newError("token contains an invalid number of segments", ErrTokenMalformed)
     	}
     
     	token = &Token{Raw: tokenString}
    @@ -196,6 +199,33 @@ func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Toke
     	return token, parts, nil
     }
     
    +// splitToken splits a token string into three parts: header, claims, and signature. It will only
    +// return true if the token contains exactly two delimiters and three parts. In all other cases, it
    +// will return nil parts and false.
    +func splitToken(token string) ([]string, bool) {
    +	parts := make([]string, 3)
    +	header, remain, ok := strings.Cut(token, tokenDelimiter)
    +	if !ok {
    +		return nil, false
    +	}
    +	parts[0] = header
    +	claims, remain, ok := strings.Cut(remain, tokenDelimiter)
    +	if !ok {
    +		return nil, false
    +	}
    +	parts[1] = claims
    +	// One more cut to ensure the signature is the last part of the token and there are no more
    +	// delimiters. This avoids an issue where malicious input could contain additional delimiters
    +	// causing unecessary overhead parsing tokens.
    +	signature, _, unexpected := strings.Cut(remain, tokenDelimiter)
    +	if unexpected {
    +		return nil, false
    +	}
    +	parts[2] = signature
    +
    +	return parts, true
    +}
    +
     // DecodeSegment decodes a JWT specific base64url encoding. This function will
     // take into account whether the [Parser] is configured with additional options,
     // such as [WithStrictDecoding] or [WithPaddingAllowed].
    
bf316c48137a

Introduce (*Parser).ParseUnverified

https://github.com/golang-jwt/jwtzimbatmJul 21, 2016via ghsa
1 file changed · +62 48
  • parser.go+62 48 modified
    @@ -21,55 +21,9 @@ func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
     }
     
     func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
    -	parts := strings.Split(tokenString, ".")
    -	if len(parts) != 3 {
    -		return nil, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed)
    -	}
    -
    -	var err error
    -	token := &Token{Raw: tokenString}
    -
    -	// parse Header
    -	var headerBytes []byte
    -	if headerBytes, err = DecodeSegment(parts[0]); err != nil {
    -		if strings.HasPrefix(strings.ToLower(tokenString), "bearer ") {
    -			return token, NewValidationError("tokenstring should not contain 'bearer '", ValidationErrorMalformed)
    -		}
    -		return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
    -	}
    -	if err = json.Unmarshal(headerBytes, &token.Header); err != nil {
    -		return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
    -	}
    -
    -	// parse Claims
    -	var claimBytes []byte
    -	token.Claims = claims
    -
    -	if claimBytes, err = DecodeSegment(parts[1]); err != nil {
    -		return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
    -	}
    -	dec := json.NewDecoder(bytes.NewBuffer(claimBytes))
    -	if p.UseJSONNumber {
    -		dec.UseNumber()
    -	}
    -	// JSON Decode.  Special case for map type to avoid weird pointer behavior
    -	if c, ok := token.Claims.(MapClaims); ok {
    -		err = dec.Decode(&c)
    -	} else {
    -		err = dec.Decode(&claims)
    -	}
    -	// Handle decode error
    +	token, parts, err := p.ParseUnverified(tokenString, claims)
     	if err != nil {
    -		return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
    -	}
    -
    -	// Lookup signature method
    -	if method, ok := token.Header["alg"].(string); ok {
    -		if token.Method = GetSigningMethod(method); token.Method == nil {
    -			return token, NewValidationError("signing method (alg) is unavailable.", ValidationErrorUnverifiable)
    -		}
    -	} else {
    -		return token, NewValidationError("signing method (alg) is unspecified.", ValidationErrorUnverifiable)
    +		return token, err
     	}
     
     	// Verify signing method is in the required set
    @@ -129,3 +83,63 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
     
     	return token, vErr
     }
    +
    +// WARNING: Don't use this method unless you know what you're doing
    +//
    +// This method parses the token but doesn't validate the signature. It's only
    +// ever useful in cases where you know the signature is valid (because it has
    +// been checked previously in the stack) and you want to extract values from
    +// it.
    +func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Token, parts []string, err error) {
    +	parts = strings.Split(tokenString, ".")
    +	if len(parts) != 3 {
    +		return nil, parts, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed)
    +	}
    +
    +	token = &Token{Raw: tokenString}
    +
    +	// parse Header
    +	var headerBytes []byte
    +	if headerBytes, err = DecodeSegment(parts[0]); err != nil {
    +		if strings.HasPrefix(strings.ToLower(tokenString), "bearer ") {
    +			return token, parts, NewValidationError("tokenstring should not contain 'bearer '", ValidationErrorMalformed)
    +		}
    +		return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
    +	}
    +	if err = json.Unmarshal(headerBytes, &token.Header); err != nil {
    +		return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
    +	}
    +
    +	// parse Claims
    +	var claimBytes []byte
    +	token.Claims = claims
    +
    +	if claimBytes, err = DecodeSegment(parts[1]); err != nil {
    +		return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
    +	}
    +	dec := json.NewDecoder(bytes.NewBuffer(claimBytes))
    +	if p.UseJSONNumber {
    +		dec.UseNumber()
    +	}
    +	// JSON Decode.  Special case for map type to avoid weird pointer behavior
    +	if c, ok := token.Claims.(MapClaims); ok {
    +		err = dec.Decode(&c)
    +	} else {
    +		err = dec.Decode(&claims)
    +	}
    +	// Handle decode error
    +	if err != nil {
    +		return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
    +	}
    +
    +	// Lookup signature method
    +	if method, ok := token.Header["alg"].(string); ok {
    +		if token.Method = GetSigningMethod(method); token.Method == nil {
    +			return token, parts, NewValidationError("signing method (alg) is unavailable.", ValidationErrorUnverifiable)
    +		}
    +	} else {
    +		return token, parts, NewValidationError("signing method (alg) is unspecified.", ValidationErrorUnverifiable)
    +	}
    +
    +	return token, parts, nil
    +}
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.