CVE-2021-26921 (Go)

In util/session/sessionmanager.go in Argo CD before 1.8.4, tokens continue to work even when the user account is disabled.

Weakness:
CWE-613: name: Insufficient Session Expiration
description: 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/argoproj/argo-cd

Vulnerable code:
file_path: util/session/sessionmanager.go
snippet: func (mgr *SessionManager) Parse(tokenString string) (jwt.Claims, error) {
	// Parse takes the token string and a function for looking up the key. The latter is especially
	// useful if you use multiple keys for your application.  The standard is to use 'kid' in the
	// head of the token to identify which key to use, but the parsed token (head and claims) is provided
	// to the callback, providing flexibility.
	var claims jwt.MapClaims
	settings, err := mgr.settingsMgr.GetSettings()
	if err != nil {
		return nil, err
	}
	token, err := jwt.ParseWithClaims(tokenString, &claims, func(token *jwt.Token) (interface{}, error) {
		// Don't forget to validate the alg is what you expect:
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
		}
		return settings.ServerSignature, nil
	})
	if err != nil {
		return nil, err
	}

	issuedAt, err := jwtutil.IssuedAtTime(claims)
	if err != nil {
		return nil, err
	}

	subject := jwtutil.StringField(claims, "sub")
	id := jwtutil.StringField(claims, "jti")

	if projName, role, ok := rbacpolicy.GetProjectRoleFromSubject(subject); ok {
		proj, err := mgr.projectsLister.Get(projName)
		if err != nil {
			return nil, err
		}
		_, _, err = proj.GetJWTToken(role, issuedAt.Unix(), id)
		if err != nil {
			return nil, err
		}

		return token.Claims, nil
	}

	account, err := mgr.settingsMgr.GetAccount(subject)
	if err != nil {
		return nil, err
	}

	if id := jwtutil.StringField(claims, "jti"); id != "" && account.TokenIndex(id) == -1 {
		return nil, fmt.Errorf("account %s does not have token with id %s", subject, id)
	}

	if account.PasswordMtime != nil && issuedAt.Before(*account.PasswordMtime) {
		return nil, fmt.Errorf("Account password has changed since token issued")
	}
	return token.Claims, nil
}

Fixed code:
file_path: util/session/sessionmanager.go
snippet: func (mgr *SessionManager) Parse(tokenString string) (jwt.Claims, error) {
	// Parse takes the token string and a function for looking up the key. The latter is especially
	// useful if you use multiple keys for your application.  The standard is to use 'kid' in the
	// head of the token to identify which key to use, but the parsed token (head and claims) is provided
	// to the callback, providing flexibility.
	var claims jwt.MapClaims
	settings, err := mgr.settingsMgr.GetSettings()
	if err != nil {
		return nil, err
	}
	token, err := jwt.ParseWithClaims(tokenString, &claims, func(token *jwt.Token) (interface{}, error) {
		// Don't forget to validate the alg is what you expect:
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
		}
		return settings.ServerSignature, nil
	})
	if err != nil {
		return nil, err
	}

	issuedAt, err := jwtutil.IssuedAtTime(claims)
	if err != nil {
		return nil, err
	}

	subject := jwtutil.StringField(claims, "sub")
	id := jwtutil.StringField(claims, "jti")

	if projName, role, ok := rbacpolicy.GetProjectRoleFromSubject(subject); ok {
		proj, err := mgr.projectsLister.Get(projName)
		if err != nil {
			return nil, err
		}
		_, _, err = proj.GetJWTToken(role, issuedAt.Unix(), id)
		if err != nil {
			return nil, err
		}

		return token.Claims, nil
	}

	account, err := mgr.settingsMgr.GetAccount(subject)
	if err != nil {
		return nil, err
	}

	if !account.Enabled {
		return nil, fmt.Errorf("account %s is disabled", subject)
	}

	if id := jwtutil.StringField(claims, "jti"); id != "" && account.TokenIndex(id) == -1 {
		return nil, fmt.Errorf("account %s does not have token with id %s", subject, id)
	}

	if account.PasswordMtime != nil && issuedAt.Before(*account.PasswordMtime) {
		return nil, fmt.Errorf("Account password has changed since token issued")
	}
	return token.Claims, nil
}