# Sub2API 安全问题分析:激活码与API Key跨实例使用风险 ## 问题描述 用户发现Sub2API系统生成的激活码(Redeem Code)和API Key在验证时未检查是否是本系统发放的,这意味着其他部署的Sub2API实例生成的激活码或API Key可能会在本系统中被接受和使用。 ## 问题分析 ### 1. 当前实现 #### 1.1 激活码(Redeem Code)生成 ```go // 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生成 ```go // 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 验证逻辑 ```go // 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、域名等)。 #### 激活码格式改进 ```go // 建议格式:{前缀}-{实例ID}-{随机} const CodePrefix = "S2P" // Sub2API Prefix func (s *RedeemService) GenerateCodeWithInstanceID(instanceID string) string { // 实例ID(8位) 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:]) } ``` #### 验证逻辑 ```go 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格式改进 ```go // 建议格式: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都关联到该标识。 #### 实现 ```go // 添加实例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. 配置建议 ```yaml # 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*