Files
lijiaoqiao/docs/security_api_key_vulnerability_analysis_v1_2026-03-18.md
2026-03-26 16:04:46 +08:00

9.8 KiB
Raw Blame History

安全漏洞Subapi API Key 跨部署验证问题

发现时间2026-03-18 漏洞等级:严重P0 状态:历史漏洞分析文档(用于复盘),不作为当前实现基线。 实施基线:security_solution_v1_2026-03-18.mdHMAC-SHA256 方案)。


1. 漏洞描述

1.1 问题现象

Subapi 分发给用户的 API Key 和激活码只验证算法正确性,未验证 Key 是否由当前系统生成。

这意味着:

  • 部署在 A 服务器的 Subapi 生成的 API Key可以在部署在 B 服务器的 Subapi 中通过验证
  • 不同独立部署之间的 API Key 可以互相串用

1.2 漏洞原理

# Subapi 当前的验证逻辑(推测)
def verify_api_key(key):
    # 只验证格式和算法
    if validate_format(key) and validate_checksum(key):
        return True  # 通过验证
    return False

# 问题:没有验证 Key 的来源(哪个部署生成的)

1.3 影响范围

场景 影响
平台间串用 用户的 Key 可能在其他平台也能用
账号盗用 窃取的 Key 可以在任意部署使用
收益损失 供应方的配额可能被其他平台盗用
账务错误 调用记录和计费可能记到错误平台

2. 漏洞影响我们的规划

2.1 如果集成 Subapi

  • 我们的用户可能使用其他 Subapi 部署生成的 Key
  • 我们的计费可能被绕过
  • 供应方的收益可能被截取

2.2 解决方案

方案 A自建 API Key 体系(推荐)

# 我们的 API Key 设计
def generate_api_key(user_id, platform_id):
    # Key 结构:{platform_prefix}{version}{user_hash}{checksum}
    # platform_prefix: 我们的平台标识(如 "LGW"
    # user_hash: 用户ID的哈希
    # checksum: CRC32/MD5 校验

    key = f"lgw_{version}_{user_hash}_{checksum}"
    return key

def verify_api_key(key):
    # 1. 验证格式
    # 2. 验证平台标识(我们的平台)
    # 3. 验证校验和
    # 4. 验证是否在我们的数据库中

    if not key.startswith("lgw_"):
        return False  # 不是我们的 Key

    # 继续验证...
    return True

方案 B使用 Token 代替 API Key

  • 不直接传递 API Key
  • 使用 OAuth 2.0 风格的 Access Token
  • Token 绑定到具体部署,无法跨部署使用

3. 我们的 API Key 设计规范

3.1 Key 结构

{LGW}-{版本}-{用户哈希}-{时间戳}-{随机数}-{校验和}

示例:
lgw-v1-u7f3a2b1-t1700000000-r8f3a2-e9d4c1b2
字段 说明 长度
LGW 平台标识 3
v1 版本号 2
u7f3a2b1 用户哈希 8
t1700000000 时间戳 10
r8f3a2 随机数 6
e9d4c1b2 校验和 8

3.2 验证流程

收到 API Key 请求
    │
    ▼
┌─────────────────┐
│ 1. 格式验证    │ ──▶ 格式错误 → 400
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ 2. 平台标识    │ ──▶ 不是 "lgw-" → 401
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ 3. 校验和验证  │ ──▶ 校验失败 → 401
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ 4. 数据库验证   │ ──▶ Key 不存在/已禁用 → 401
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ 5. 权限验证    │ ──▶ 无权限 → 403
└────────┬────────┘
         │
         ▼
    验证通过

3.3 激活码设计

{LGW}-{类型}-{用户ID}-{过期时间}-{随机数}-{校验和}

示例:
lgw-act-1000-20260331-r8f3a2-e9d4c1b2
字段 说明
lgw 平台标识
act 激活码类型
1000 用户ID
20260331 过期日期
r8f3a2 随机数
e9d4c1b2 校验和

4. 技术实现

4.1 Key 生成服务

import hashlib
import secrets
import time

class APIKeyGenerator:
    PLATFORM_PREFIX = "lgw"
    VERSION = "v1"

    @classmethod
    def generate(cls, user_id: int) -> str:
        # 用户哈希8位
        user_hash = hashlib.md5(str(user_id).encode()).hexdigest()[:8]

        # 时间戳10位
        timestamp = str(int(time.time()))

        # 随机数6位
        random = secrets.token_hex(3)[:6]

        # 组合
        raw = f"{cls.PLATFORM_PREFIX}-{cls.VERSION}-{user_hash}-{timestamp}-{random}"

        # 校验和8位
        checksum = hashlib.md5(raw.encode()).hexdigest()[:8]

        return f"{raw}-{checksum}"

    @classmethod
    def verify(cls, key: str) -> bool:
        # 1. 格式验证
        parts = key.split("-")
        if len(parts) != 6:
            return False

        # 2. 平台标识验证
        if parts[0] != cls.PLATFORM_PREFIX:
            return False

        # 3. 校验和验证
        raw = "-".join(parts[:5])
        expected_checksum = hashlib.md5(raw.encode()).hexdigest()[:8]
        if parts[5] != expected_checksum:
            return False

        # 4. 数据库验证(在 Controller 中实现)
        return True

4.2 激活码生成服务

class ActivationCodeGenerator:
    PLATFORM_PREFIX = "lgw"
    CODE_TYPE = "act"

    @classmethod
    def generate(cls, user_id: int, expiry_days: int) -> str:
        # 计算过期日期
        expiry = datetime.now() + timedelta(days=expiry_days)
        expiry_str = expiry.strftime("%Y%m%d")

        # 随机数
        random = secrets.token_hex(3)[:6]

        # 组合
        raw = f"{cls.PLATFORM_PREFIX}-{cls.CODE_TYPE}-{user_id}-{expiry_str}-{random}"

        # 校验和
        checksum = hashlib.md5(raw.encode()).hexdigest()[:8]

        return f"{raw}-{checksum}"

5. 数据库设计

-- API Keys 表
CREATE TABLE api_keys (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    key_hash VARCHAR(64) NOT NULL UNIQUE COMMENT 'Key 的哈希(用于查询)',
    key_prefix VARCHAR(20) NOT NULL COMMENT 'Key 前缀(用于展示)',

    -- 绑定信息
    team_id BIGINT,
    organization_id BIGINT,

    -- 权限
    permissions JSON COMMENT '权限列表',
    allowed_models JSON COMMENT '允许的模型列表',
    allowed_ips JSON COMMENT 'IP 白名单',

    -- 限制
    rate_limit_rpm INT DEFAULT 60,
    rate_limit_tpm INT DEFAULT 100000,
    max_concurrent INT DEFAULT 10,

    -- 状态
    status VARCHAR(20) DEFAULT 'active' COMMENT 'active/disabled/expired',

    -- 时间
    expires_at TIMESTAMP,
    last_used_at TIMESTAMP,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    -- 审计
    created_by BIGINT,
    ip_address VARCHAR(45),
    description VARCHAR(200),

    INDEX idx_user_id (user_id),
    INDEX idx_key_hash (key_hash),
    INDEX idx_status (status),
    INDEX idx_expires_at (expires_at)
) COMMENT 'API Keys 表';

-- 激活码表
CREATE TABLE activation_codes (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    code_hash VARCHAR(64) NOT NULL UNIQUE COMMENT '激活码哈希',
    code_prefix VARCHAR(20) NOT NULL COMMENT '激活码前缀',

    -- 绑定信息
    user_id BIGINT NOT NULL,
    target_type VARCHAR(20) COMMENT '激活目标类型: subscription/package',
    target_id BIGINT COMMENT '激活目标ID',

    -- 状态
    status VARCHAR(20) DEFAULT 'unused' COMMENT 'unused/used/expired',
    used_at TIMESTAMP,
    used_by BIGINT COMMENT '使用者ID',

    -- 时间
    expires_at TIMESTAMP NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    INDEX idx_code_hash (code_hash),
    INDEX idx_user_id (user_id),
    INDEX idx_status (status),
    INDEX idx_expires_at (expires_at)
) COMMENT '激活码表';

6. 与 Subapi 集成时的处理

6.1 方案:我们的 Gateway 作为唯一入口

用户请求
    │
    ▼
我们的 Gateway验证 Key 来源)
    │
    ├── 我们的 Key → 处理
    │
    └── Subapi 格式的 Key → 拒绝或转发到 Subapi

6.2 API Key 识别逻辑

def identify_key_type(key: str) -> str:
    if key.startswith("lgw-"):
        return "own"  # 我们的 Key
    elif key.startswith("sk-"):
        return "openai"  # OpenAI 原始 Key
    else:
        return "unknown"  # 未知类型

6.3 流量分离

Key 类型 处理方式
lgw- 开头 我们的 Gateway 处理
sk- 开头 直接转发到对应供应商
其他 Subapi 格式 转发到 Subapi如果有集成

7. 风险评估与缓解

7.1 风险评估

风险 影响 可能性 严重性
Subapi Key 串用 计费损失/账号盗用 严重
激活码伪造 权益被盗用
Key 泄露 未授权使用

7.2 缓解措施

  1. 强制 Key 来源验证

    • 所有 Key 必须包含平台标识
    • 验证时必须查询数据库
  2. Key 轮换

    • 定期轮换 Key
    • 用户可手动轮换
  3. 使用监控

    • 记录 Key 使用情况
    • 异常使用告警
  4. IP 限制

    • 支持 IP 白名单
    • 异常 IP 告警

8. 结论

  1. Subapi 存在严重安全漏洞API Key 不验证来源,可在任意部署使用

  2. 我们的系统必须自建 Key 体系

    • Key 必须包含平台标识
    • 必须数据库验证
    • 必须防伪造
  3. 集成时流量分离

    • 我们的 Key 由我们处理
    • Subapi Key 转发到 Subapi

文档状态:安全漏洞分析 关联文档

  • supply_detailed_design_v1_2026-03-18.md