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

258 lines
6.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 {
// 实例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:])
}
```
#### 验证逻辑
```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*