CVE-2022-31145 (Go, published 2022-07-13T21:15Z)
Description: value: FlyteAdmin is the control plane for Flyte responsible for managing entities and administering workflow executions. In versions 1.1.30 and prior, authenticated users using an external identity provider can continue to use Access Tokens and ID Tokens even after they expire. Users who use FlyteAdmin as the OAuth2 Authorization Server are unaffected by this issue. A patch is available on the `master` branch of the repository. As a workaround, rotating signing keys immediately will invalidate all open sessions and force all users to attempt to obtain new tokens. Those who use this workaround should continue to rotate keys until FlyteAdmin has been upgraded and hide FlyteAdmin deployment ingress URL from the internet.
Severity: nan (CVSS v3 base score: 6.5)
Weakness: CWE-613 \u2014 Insufficient Session Expiration
According to WASC, "Insufficient Session Expiration is when a web site permits an attacker to reuse old session credentials or session IDs for authorization."
Repository: https://github.com/flyteorg/flyteadmin
Files changed:
auth/authzserver/authorize_test.go
auth/authzserver/resource_server.go
auth/authzserver/resource_server_test.go
Commit message: Merge pull request from GHSA-qwrj-9hmp-gpxh

* Fix claims verification for access tokens in external IdP setup

Signed-off-by: Haytham Abuelfutuh <haytham@afutuh.com>

* Add another test case for no signature

Signed-off-by: Haytham Abuelfutuh <haytham@afutuh.com>

Vulnerable code:
// #nosec
const sampleIDToken = `eyJraWQiOiJaNmRtWl9UWGhkdXctalVCWjZ1RUV6dm5oLWpoTk8wWWhlbUI3cWFfTE9jIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiIwMHVra2k0OHBzSDhMaWtZVjVkNiIsIm5hbWUiOiJIYXl0aGFtIEFidWVsZnV0dWgiLCJ2ZXIiOjEsImlzcyI6Imh0dHBzOi8vZGV2LTE0MTg2NDIyLm9rdGEuY29tL29hdXRoMi9hdXNrbmdubjd1QlZpUXE2YjVkNiIsImF1ZCI6IjBvYWtraGV0ZU5qQ01FUnN0NWQ2IiwiaWF0IjoxNjE4NDUzNjc5LCJleHAiOjE2MTg0NTcyNzksImp0aSI6IklELmE0YXpLdUphVFM2YzNTeHdpWWdTMHhPbTM2bVFnVlVVN0I4V2dEdk80dFkiLCJhbXIiOlsicHdkIl0sImlkcCI6IjBvYWtrbTFjaTFVZVBwTlUwNWQ2IiwicHJlZmVycmVkX3VzZXJuYW1lIjoiaGF5dGhhbUB1bmlvbi5haSIsImF1dGhfdGltZSI6MTYxODQ0NjI0NywiYXRfaGFzaCI6Ikg5Q0FweWlrQkpGYXJ4d1FUbnB6ZFEifQ.SJ3BTD_MFcrYvTnql181Ddeb_mOm81z_S7ZKQ6P8mMgWqn94LZ2nG8k8-_odaaNAAT-M1nAFKWqZAQGvliwS1_TsD8_j0cen5zYnGcz2Uu5fFlvoHwuPgy5JYYNOXkXYgPnIb3kNkgXKbkdjS9hdbMfvnPd9rr8v0yzqf0AQBnUe-cPrzY-ZJjvh80IWDZgSjoP244tTYppPkx8UtedJLJZ4tzB7aXlEyoRV-DpmOLfJkAmblRm4OsO1qjwmx3HSIy_T-0PANn-g4AS07rpoMYHRcqncdgcAsVfGxjyWiOg3kbymLqpGlkIZgzmev-TmpoDp0QkUVPOntuiB57GZ6g`

	raw, err := r.signatureVerifier.VerifySignature(ctx, tokenStr)
	claimsRaw := map[string]interface{}{}
	if err = json.Unmarshal(raw, &claimsRaw); err != nil {
		return nil, fmt.Errorf("failed to unmarshal user info claim into UserInfo type. Error: %w", err)
	return verifyClaims(sets.NewString(append(r.allowedAudience, expectedAudience)...), claimsRaw)
func newMockResourceServer(t testing.TB) ResourceServer {
				"issuer": "https://dev-14186422.okta.com",
				"jwks_uri": "URL/keys",
			}`, "URL", *serverURL))
	defer s.Close()

		BaseURL: stdlibConfig.URL{URL: *config.MustParseURL(s.URL)},
	return r
}

func TestNewOAuth2ResourceServer(t *testing.T) {
	newMockResourceServer(t)
	r := newMockResourceServer(t)
	_, err := r.ValidateAccessToken(context.Background(), "myserver", sampleIDToken)
	assert.Error(t, err)

Fixed code:
	jwtgo "github.com/golang-jwt/jwt/v4"
	_, err := r.signatureVerifier.VerifySignature(ctx, tokenStr)
	t, _, err := jwtgo.NewParser().ParseUnverified(tokenStr, jwtgo.MapClaims{})
	if err != nil {
		return nil, fmt.Errorf("failed to parse token: %v", err)
	}

	if err = t.Claims.Valid(); err != nil {
		return nil, fmt.Errorf("failed to validate token: %v", err)
	return verifyClaims(sets.NewString(append(r.allowedAudience, expectedAudience)...), t.Claims.(jwtgo.MapClaims))
	"crypto/rand"
	"crypto/rsa"
	"time"

	"github.com/golang-jwt/jwt/v4"
func newMockResourceServer(t testing.TB, publicKey rsa.PublicKey) (resourceServer ResourceServer, closer func()) {
				"issuer": "https://whatever.okta.com",
				"jwks_uri": "{URL}/keys",
			}`, "{URL}", *serverURL))
			key := jwk.NewRSAPublicKey()
			err := key.FromRaw(&publicKey)
			if err != nil {
				http.Error(w, err.Error(), 400)
				return
			}

			keys.Add(key)

			return
		BaseURL:         stdlibConfig.URL{URL: *config.MustParseURL(s.URL)},
		AllowedAudience: []string{"https://localhost"},
	return r, func() {
		s.Close()
	}
	sampleRSAKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if !assert.NoError(t, err) {
		t.FailNow()
	}

	r, closer := newMockResourceServer(t, sampleRSAKey.PublicKey)
	defer closer()

	t.Run("No signature", func(t *testing.T) {
		sampleIDToken, err := jwt.NewWithClaims(jwt.SigningMethodRS512, jwt.RegisteredClaims{
			Audience:  r.allowedAudience,
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			Issuer:    "localhost",
			Subject:   "someone",
		}).SignedString(sampleRSAKey)
		if !assert.NoError(t, err) {
			t.FailNow()
		}

		parts := strings.Split(sampleIDToken, ".")
		sampleIDToken = strings.Join(parts[:len(parts)-1], ".") + "."

		_, err = r.ValidateAccessToken(context.Background(), "myserver", sampleIDToken)
		if !assert.Error(t, err) {
			t.FailNow()
		}

		assert.Contains(t, err.Error(), "failed to verify id token signature")
	})

	t.Run("Invalid signature", func(t *testing.T) {
		sampleRSAKey, err := rsa.GenerateKey(rand.Reader, 2048)
		if !assert.NoError(t, err) {
			t.FailNow()
		}

		sampleIDToken, err := jwt.NewWithClaims(jwt.SigningMethodRS512, jwt.RegisteredClaims{
			Audience:  r.allowedAudience,
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			Issuer:    "localhost",
			Subject:   "someone",
		}).SignedString(sampleRSAKey)
		if !assert.NoError(t, err) {
			t.FailNow()
		}

		_, err = r.ValidateAccessToken(context.Background(), "myserver", sampleIDToken)
		if !assert.Error(t, err) {
			t.FailNow()
		}

		assert.Contains(t, err.Error(), "failed to verify id token signature")
	})

	t.Run("Invalid audience", func(t *testing.T) {
		sampleIDToken, err := jwt.NewWithClaims(jwt.SigningMethodRS512, jwt.RegisteredClaims{
			Audience:  []string{"https://hello world"},
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			Issuer:    "localhost",
			Subject:   "someone",
		}).SignedString(sampleRSAKey)
		if !assert.NoError(t, err) {
			t.FailNow()
		}

		_, err = r.ValidateAccessToken(context.Background(), "myserver", sampleIDToken)
		if !assert.Error(t, err) {
			t.FailNow()
		}

		assert.Contains(t, err.Error(), "invalid audience")
	})

	t.Run("Expired token", func(t *testing.T) {
		sampleIDToken, err := jwt.NewWithClaims(jwt.SigningMethodRS512, jwt.StandardClaims{
			Audience:  r.allowedAudience[0],
			ExpiresAt: time.Now().Add(-time.Hour).Unix(),
			IssuedAt:  time.Now().Add(-2 * time.Hour).Unix(),
			Issuer:    "localhost",
			Subject:   "someone",
		}).SignedString(sampleRSAKey)
		if !assert.NoError(t, err) {
			t.FailNow()
		}

		_, err = r.ValidateAccessToken(context.Background(), "myserver", sampleIDToken)
		if !assert.Error(t, err) {
			t.FailNow()
		}

		assert.Contains(t, err.Error(), "failed to validate token: Token is expired")
	})