Ver Fonte

security: GHSA-g962-2j28-3cg9 (HIGH) JWT Audience Validation Bypass in Local Key and HMAC Modes

jamesread há 4 meses atrás
pai
commit
e97d8ecbd8

+ 19 - 5
service/internal/auth/otjwt/jwt.go

@@ -33,6 +33,13 @@ func parseJwtToken(cfg *config.Config, jwtString string) (*jwt.Token, error) {
 	return parseJwtTokenWithHMAC(cfg, jwtString)
 }
 
+func parserOptionsWithAudience(cfg *config.Config) []jwt.ParserOption {
+	if cfg.AuthJwtAud == "" {
+		return nil
+	}
+	return []jwt.ParserOption{jwt.WithAudience(cfg.AuthJwtAud)}
+}
+
 func getClaimsFromJwtToken(cfg *config.Config, jwtString string) (jwt.MapClaims, error) {
 	token, err := parseJwtToken(cfg, jwtString)
 
@@ -56,7 +63,8 @@ func parseJwtTokenWithRemoteKey(cfg *config.Config, jwtToken string) (*jwt.Token
 		return nil, err
 	}
 
-	return jwt.Parse(jwtToken, jwksVerifier.Keyfunc, jwt.WithAudience(cfg.AuthJwtAud))
+	opts := parserOptionsWithAudience(cfg)
+	return jwt.Parse(jwtToken, jwksVerifier.Keyfunc, opts...)
 }
 
 var (
@@ -148,24 +156,30 @@ func parseJwtTokenWithLocalKey(cfg *config.Config, jwtString string) (*jwt.Token
 		return nil, err
 	}
 
-	return jwt.Parse(jwtString, func(token *jwt.Token) (interface{}, error) {
+	keyFunc := func(token *jwt.Token) (interface{}, error) {
 		if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
 			return nil, fmt.Errorf("parseJwt expected token algorithm RSA but got: %v", token.Header["alg"])
 		}
 
 		return pubKey, nil
-	})
+	}
+
+	opts := parserOptionsWithAudience(cfg)
+	return jwt.Parse(jwtString, keyFunc, opts...)
 }
 
 // Hash-based Message Authentication Code
 func parseJwtTokenWithHMAC(cfg *config.Config, jwtString string) (*jwt.Token, error) {
-	return jwt.Parse(jwtString, func(token *jwt.Token) (interface{}, error) {
+	keyFunc := func(token *jwt.Token) (interface{}, error) {
 		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
 			return nil, fmt.Errorf("parseJwt expected token algorithm HMAC but got: %v", token.Header["alg"])
 		}
 
 		return []byte(cfg.AuthJwtHmacSecret), nil
-	})
+	}
+
+	opts := parserOptionsWithAudience(cfg)
+	return jwt.Parse(jwtString, keyFunc, opts...)
 }
 
 func lookupClaimValueOrDefault(claims jwt.MapClaims, key string, def string) string {

+ 21 - 1
service/internal/auth/otjwt/jwt_test.go

@@ -66,12 +66,19 @@ func newMux() *http.ServeMux {
 }
 
 func createJWTTokenWithExpiration(t *testing.T, privateKey *rsa.PrivateKey, expire int64) string {
+	return createJWTTokenWithExpirationAndAudience(t, privateKey, expire, "")
+}
+
+func createJWTTokenWithExpirationAndAudience(t *testing.T, privateKey *rsa.PrivateKey, expire int64, audience string) string {
 	token := jwt.New(jwt.SigningMethodRS256)
 	claims := token.Claims.(jwt.MapClaims)
 	claims["nbf"] = time.Now().Unix() - 1000
 	claims["exp"] = time.Now().Unix() + expire
 	claims["sub"] = "test"
 	claims["olivetinGroup"] = "test"
+	if audience != "" {
+		claims["aud"] = audience
+	}
 
 	tokenStr, err := token.SignedString(privateKey)
 	if err != nil {
@@ -108,6 +115,10 @@ func verifyJWTResponse(t *testing.T, res *http.Response, expectCode int) {
 }
 
 func testJwkValidation(t *testing.T, expire int64, expectCode int) {
+	testJwkValidationWithAudience(t, expire, expectCode, "", "")
+}
+
+func testJwkValidationWithAudience(t *testing.T, expire int64, expectCode int, configAudience, tokenAudience string) {
 	privateKey, publicKeyPath := createKeys(t)
 	defer os.Remove(publicKeyPath)
 
@@ -116,8 +127,9 @@ func testJwkValidation(t *testing.T, expire int64, expectCode int) {
 	cfg.AuthJwtClaimUsername = "sub"
 	cfg.AuthJwtClaimUserGroup = "olivetinGroup"
 	cfg.AuthJwtHeader = "Authorization"
+	cfg.AuthJwtAud = configAudience
 
-	tokenStr := createJWTTokenWithExpiration(t, privateKey, expire)
+	tokenStr := createJWTTokenWithExpirationAndAudience(t, privateKey, expire, tokenAudience)
 	handler := setupJWTTestHandler(t, cfg)
 
 	srv := httptest.NewServer(handler)
@@ -135,6 +147,14 @@ func TestJWTSignatureVerificationFails(t *testing.T) {
 	testJwkValidation(t, -500, 403)
 }
 
+func TestJWTAudienceValidationRejectsWrongAudience(t *testing.T) {
+	testJwkValidationWithAudience(t, 1000, 403, "expected-audience", "wrong-audience")
+}
+
+func TestJWTAudienceValidationAcceptsCorrectAudience(t *testing.T) {
+	testJwkValidationWithAudience(t, 1000, 200, "expected-audience", "expected-audience")
+}
+
 func createJWTTokenWithGroups(t *testing.T, privateKey *rsa.PrivateKey, groups interface{}) string {
 	token := jwt.New(jwt.SigningMethodRS256)
 	claims := token.Claims.(jwt.MapClaims)