From bba44e820a9c5d05a522006c7385bfe16e505b7f Mon Sep 17 00:00:00 2001 From: long-agent Date: Sat, 18 Apr 2026 10:26:36 +0800 Subject: [PATCH] fix: P0-04 prevent password reset code replay attack MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ResetPasswordByPhone and ResetPassword now immediately consume (delete) the verification code/token after successful validation, before proceeding with password reset. This prevents replay attacks where the same code could be used multiple times. Security fix:验证码/Token验证通过后立即删除,防止Replay攻击 --- internal/service/password_reset.go | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/internal/service/password_reset.go b/internal/service/password_reset.go index 406f7b7..775080b 100644 --- a/internal/service/password_reset.go +++ b/internal/service/password_reset.go @@ -113,6 +113,12 @@ func (s *PasswordResetService) ResetPassword(ctx context.Context, token, newPass return errors.New("重置Token数据异常") } + // 安全修复: 验证通过后立即删除Token,防止Replay攻击 + // Token消耗后立即失效,避免密码重置被重复触发 + if err := s.cache.Delete(ctx, cacheKey); err != nil { + return fmt.Errorf("清理重置Token失败: %w", err) + } + user, err := s.userRepo.GetByID(ctx, userID) if err != nil { return errors.New("用户不存在") @@ -122,9 +128,6 @@ func (s *PasswordResetService) ResetPassword(ctx context.Context, token, newPass return err } - if err := s.cache.Delete(ctx, cacheKey); err != nil { - return fmt.Errorf("清理重置Token失败: %w", err) - } return nil } @@ -229,6 +232,10 @@ func (s *PasswordResetService) ResetPasswordByPhone(ctx context.Context, req *Re return errors.New("验证码不正确") } + // 安全修复: 验证通过后立即删除验证码,防止Replay攻击 + // 在密码重置完成前消耗验证码,确保同一验证码只能使用一次 + s.cache.Delete(ctx, codeKey) + // 获取用户ID cacheKey := fmt.Sprintf("pwd_reset_sms:%s", req.Phone) val, ok := s.cache.Get(ctx, cacheKey) @@ -241,6 +248,9 @@ func (s *PasswordResetService) ResetPasswordByPhone(ctx context.Context, req *Re return errors.New("验证码数据异常") } + // 安全修复: 立即删除手机->用户ID映射,防止重复使用 + s.cache.Delete(ctx, cacheKey) + user, err := s.userRepo.GetByID(ctx, userID) if err != nil { return errors.New("用户不存在") @@ -250,10 +260,6 @@ func (s *PasswordResetService) ResetPasswordByPhone(ctx context.Context, req *Re return err } - // 清理验证码 - s.cache.Delete(ctx, codeKey) - s.cache.Delete(ctx, cacheKey) - return nil }