CVE-2023-4126 (Go, published 2023-08-03T04:15Z)
Description: value: Insufficient Session Expiration in GitHub repository answerdev/answer prior to v1.1.0.
Severity: nan (CVSS v3 base score: 8.8)
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/answerdev/answer
Files changed:
internal/controller/user_controller.go
internal/repo/auth/auth.go
internal/schema/user_schema.go
internal/service/auth/auth.go
internal/service/user_admin/user_backyard.go
internal/service/user_service.go
Commit message: feat(password): logout other user when update password


Vulnerable code:
	resp, err := uc.userService.UseRePassword(ctx, req)
	handler.HandleResponse(ctx, err, resp)
// @Param data body schema.UserModifyPassWordRequest  true "UserModifyPassWordRequest"
	req := &schema.UserModifyPassWordRequest{}
func (ar *authRepo) RemoveUserTokens(ctx context.Context, userID string) {
	Avatar *AvatarInfo `json:"avatar"`
// UserModifyPassWordRequest
type UserModifyPassWordRequest struct {
	UserID  string `json:"-" `        // user_id
	OldPass string `json:"old_pass" ` // old password
	Pass    string `json:"pass" `     // password
func (u *UserModifyPassWordRequest) Check() (errFields []*validator.FormErrorField, err error) {
	RemoveUserTokens(ctx context.Context, userID string)
// RemoveUserTokens Log out all users under this user id
func (as *AuthService) RemoveUserTokens(ctx context.Context, userID string) {
	as.authRepo.RemoveUserTokens(ctx, userID)
	us.authService.RemoveUserTokens(ctx, req.UserID)
	us.authService.RemoveUserTokens(ctx, req.UserID)
// UseRePassword
func (us *UserService) UseRePassword(ctx context.Context, req *schema.UserRePassWordRequest) (resp *schema.GetUserResp, err error) {
		return nil, errors.BadRequest(reason.EmailVerifyURLExpired)
		return nil, err
		return nil, errors.BadRequest(reason.UserNotFound)
		return nil, err
		return nil, err
	resp = &schema.GetUserResp{}
	return resp, nil
func (us *UserService) UserModifyPassWordVerification(ctx context.Context, request *schema.UserModifyPassWordRequest) (bool, error) {
	userInfo, has, err := us.userRepo.GetByUserID(ctx, request.UserID)
		return false, fmt.Errorf("user does not exist")
	isPass := us.verifyPassword(ctx, request.OldPass, userInfo.Pass)
func (us *UserService) UserModifyPassword(ctx context.Context, request *schema.UserModifyPassWordRequest) error {
	enpass, err := us.encryptPassword(ctx, request.Pass)
	userInfo, has, err := us.userRepo.GetByUserID(ctx, request.UserID)
	if !has {
		return fmt.Errorf("user does not exist")
	isPass := us.verifyPassword(ctx, request.OldPass, userInfo.Pass)
		return fmt.Errorf("the old password verification failed")
func (us *UserService) verifyPassword(ctx context.Context, LoginPass, UserPass string) bool {
	err := bcrypt.CompareHashAndPassword([]byte(UserPass), []byte(LoginPass))

Fixed code:
	err := uc.userService.UpdatePasswordWhenForgot(ctx, req)
	handler.HandleResponse(ctx, err, nil)
// @Param data body schema.UserModifyPasswordReq  true "UserModifyPasswordReq"
	req := &schema.UserModifyPasswordReq{}
	req.AccessToken = middleware.ExtractToken(ctx)
func (ar *authRepo) RemoveUserTokens(ctx context.Context, userID string, remainToken string) {
		if token == remainToken {
			continue
		}
	// user have password
	HavePassword bool `json:"have_password"`
	r.HavePassword = len(userInfo.Pass) > 0
	Avatar       *AvatarInfo `json:"avatar"`
	HavePassword bool        `json:"have_password"`
type UserModifyPasswordReq struct {
	OldPass     string `json:"old_pass"`
	Pass        string `json:"pass"`
	UserID      string `json:"-"`
	AccessToken string `json:"-"`
func (u *UserModifyPasswordReq) Check() (errFields []*validator.FormErrorField, err error) {
	RemoveUserTokens(ctx context.Context, userID string, remainToken string)
// RemoveUserAllTokens Log out all users under this user id
func (as *AuthService) RemoveUserAllTokens(ctx context.Context, userID string) {
	as.authRepo.RemoveUserTokens(ctx, userID, "")
}

// RemoveTokensExceptCurrentUser remove all tokens except the current user
func (as *AuthService) RemoveTokensExceptCurrentUser(ctx context.Context, userID string, accessToken string) {
	as.authRepo.RemoveUserTokens(ctx, userID, accessToken)
	us.authService.RemoveUserAllTokens(ctx, req.UserID)
	us.authService.RemoveUserAllTokens(ctx, req.UserID)
	resp.HavePassword = len(userInfo.Pass) > 0
// UpdatePasswordWhenForgot update user password when user forgot password
func (us *UserService) UpdatePasswordWhenForgot(ctx context.Context, req *schema.UserRePassWordRequest) (err error) {
		return errors.BadRequest(reason.EmailVerifyURLExpired)
		return err
		return errors.BadRequest(reason.UserNotFound)
		return err
		return err
	// When the user changes the password, all the current user's tokens are invalid.
	us.authService.RemoveUserAllTokens(ctx, userInfo.ID)
	return nil
func (us *UserService) UserModifyPassWordVerification(ctx context.Context, req *schema.UserModifyPasswordReq) (bool, error) {
	userInfo, has, err := us.userRepo.GetByUserID(ctx, req.UserID)
		return false, errors.BadRequest(reason.UserNotFound)
	isPass := us.verifyPassword(ctx, req.OldPass, userInfo.Pass)
func (us *UserService) UserModifyPassword(ctx context.Context, req *schema.UserModifyPasswordReq) error {
	enpass, err := us.encryptPassword(ctx, req.Pass)
	userInfo, exist, err := us.userRepo.GetByUserID(ctx, req.UserID)
	if !exist {
		return errors.BadRequest(reason.UserNotFound)

	isPass := us.verifyPassword(ctx, req.OldPass, userInfo.Pass)
		return errors.BadRequest(reason.OldPasswordVerificationFailed)

	us.authService.RemoveTokensExceptCurrentUser(ctx, userInfo.ID, req.AccessToken)
func (us *UserService) verifyPassword(ctx context.Context, loginPass, userPass string) bool {
	if len(loginPass) == 0 && len(userPass) == 0 {
		return true
	}
	err := bcrypt.CompareHashAndPassword([]byte(userPass), []byte(loginPass))