Files
tokens-reef/deploy/docs-backup/SECURITY_ISSUE_CROSS_INSTANCE.md
Developer 349d783fd1 refactor: clean up project structure
- Remove old review reports (keep latest only)
- Move docs/ to deploy/docs-backup/
- Move performance-testing/ to deploy/
- Clean up test output files
- Organize root directory
2026-04-06 23:36:03 +08:00

6.4 KiB
Raw Permalink Blame History

Sub2API 安全问题分析激活码与API Key跨实例使用风险

问题描述

用户发现Sub2API系统生成的激活码Redeem Code和API Key在验证时未检查是否是本系统发放的这意味着其他部署的Sub2API实例生成的激活码或API Key可能会在本系统中被接受和使用。

问题分析

1. 当前实现

1.1 激活码Redeem Code生成

// service/redeem_service.go - GenerateRandomCode
func (s *RedeemService) GenerateRandomCode() (string, error) {
    // 纯随机生成,无系统标识
    bytes := make([]byte, 16)
    if _, err := rand.Read(bytes); err != nil {
        return "", fmt.Errorf("generate random bytes: %w", err)
    }
    
    code := hex.EncodeToString(bytes)
    // 格式XXXX-XXXX-XXXX-XXXX
    return formatCode(code), nil
}

问题激活码只是32位十六进制随机字符串没有包含任何系统标识信息。任何Sub2API实例都能生成相同格式的激活码。

1.2 API Key生成

// service/api_key_service.go - GenerateKey
func (s *APIKeyService) GenerateKey() (string, error) {
    // 格式sk-{32位随机字符}
    bytes := make([]byte, 16)
    if _, err := rand.Read(bytes); err != nil {
        return "", err
    }
    return "sk-" + hex.EncodeToString(bytes), nil
}

问题API Key也只是随机字符串没有包含实例标识。

1.3 验证逻辑

// service/redeem_service.go - Redeem
func (s *RedeemService) Redeem(ctx context.Context, userID int64, code string) (*RedeemCode, error) {
    // 只检查码是否存在,不检查来源
    redeemCode, err := s.redeemRepo.GetByCode(ctx, code)
    if err != nil {
        if errors.Is(err, ErrRedeemCodeNotFound) {
            return nil, ErrRedeemCodeNotFound
        }
        return nil, fmt.Errorf("get redeem code: %w", err)
    }
    
    // 检查状态和有效期
    if !redeemCode.CanUse() {
        return nil, ErrRedeemCodeUsed
    }
    // ... 兑换逻辑
}

验证流程

  1. 数据库中查找激活码
  2. 检查状态是否为"unused"
  3. 检查是否过期

缺陷:没有验证激活码是否"由本系统生成"。

2. 影响范围

资源类型 受影响场景 风险等级
激活码 兑换余额/并发/订阅
API Key API访问
管理员API Key 管理操作 极高

3. 攻击场景

场景1跨实例激活码兑换

攻击者:
1. 在自己的Sub2API实例A生成激活码
2. 将激活码在受害者实例B兑换
3. 如果激活码未被使用,可能成功兑换

场景2窃取API Key

攻击者:
1. 通过某种方式获取他人的API Key
2. 在自己的Sub2API实例使用该Key
3. 如果Key有效可以访问受害者账户

解决方案

方案一在Key中嵌入实例标识推荐

实现思路

在生成激活码和API Key时嵌入系统实例的唯一标识如实例ID、域名等

激活码格式改进

// 建议格式:{前缀}-{实例ID}-{随机}
const CodePrefix = "S2P"  // Sub2API Prefix

func (s *RedeemService) GenerateCodeWithInstanceID(instanceID string) string {
    // 实例ID8位
    instancePart := fmt.Sprintf("%-8s", instanceID[:8])
    
    // 随机部分24位
    randomBytes := make([]byte, 12)
    rand.Read(randomBytes)
    randomPart := hex.EncodeToString(randomBytes)
    
    return fmt.Sprintf("%s-%s-%s-%s", 
        CodePrefix,
        instancePart,
        randomPart[:8],
        randomPart[8:])
}

验证逻辑

func (s *RedeemService) ValidateCode(code string) error {
    parts := strings.Split(code, "-")
    if len(parts) != 4 {
        return ErrInvalidCodeFormat
    }
    
    // 验证前缀
    if parts[0] != CodePrefix {
        return ErrCodePrefixMismatch
    }
    
    // 验证实例ID
    instanceID := parts[1]
    if instanceID != s.instanceID {
        return ErrCodeFromOtherInstance
    }
    
    // 继续验证其他逻辑...
}

API Key格式改进

// 建议格式sk-{实例ID简称}-{随机}
const (
    KeyPrefix = "sk"
    InstanceShort = "s2p"  // 简写
)

func (s *APIKeyService) GenerateKey() string {
    randomBytes := make([]byte, 16)
    rand.Read(randomBytes)
    
    return fmt.Sprintf("%s-%s%s", 
        KeyPrefix, 
        InstanceShort, 
        hex.EncodeToString(randomBytes))
}

方案二:数据库隔离(替代方案)

实现思路

在数据库中为每个实例分配唯一标识所有生成的激活码和API Key都关联到该标识。

实现

// 添加实例ID字段
type RedeemCode struct {
    InstanceID string  // 实例标识
    Code       string
    // ...
}

// 生成时设置实例ID
func (s *RedeemService) GenerateCode() *RedeemCode {
    return &RedeemCode{
        InstanceID: s.config.InstanceID,
        Code:       generateCode(),
    }
}

// 验证时检查实例ID
func (s *RedeemService) Redeem(ctx context.Context, code string) error {
    dbCode, _ := s.redeemRepo.GetByCode(ctx, code)
    if dbCode.InstanceID != s.config.InstanceID {
        return ErrCodeFromOtherInstance
    }
    // ...
}

实施建议

1. 短期措施

  1. 记录实例标识:记录每个激活码/Key的生成实例在日志中
  2. 增强监控:监控来自异常位置的兑换请求
  3. 用户教育提醒用户不要泄露自己的Key

2. 长期措施

  1. 方案一实施在Key格式中嵌入实例标识
  2. 数据库迁移:为现有激活码/Key添加实例ID字段
  3. 过渡期:支持新旧格式并行,逐步迁移

3. 配置建议

# config.yaml
security:
  # 实例标识用于Key生成
  instance_id: "your-unique-instance-id"
  
  # 是否启用实例验证(升级后启用)
  validate_instance: true

风险评估

风险项 当前状态 影响程度 建议优先级
激活码跨实例使用 存在
API Key跨实例使用 存在
管理员Key跨实例 存在 极高 紧急

总结

当前Sub2API系统在激活码和API Key生成时未包含系统标识存在跨实例使用风险。建议在后续版本中

  1. 短期:添加日志记录,便于追踪
  2. 长期修改Key格式嵌入实例标识

此问题需要开发团队评估影响范围后实施修复。


文档版本1.0 最后更新2025-01 分析基于Sub2API v0.1.104