数据库版本范围查询:SemVer 比较与漏洞影响版本存储设计

版本号不能用字符串比较

-- 错误:字符串排序,结果是 2.10.0 < 2.9.0
SELECT '2.10.0' < '2.9.0'  -- 返回 1(true)

-- 正确:语义化版本(SemVer)比较
-- 2.10.0 > 2.9.0(major.minor.patch 各位独立比较)

版本范围字符串如 >= 2.0-beta9 < 2.15.0 也无法直接用 SQL BETWEENLIKE 处理。

方案一:数据库存原始版本,程序用 SemVer 库比较

表结构简单:

-- 资产表
asset(id, name, product_version)

-- 漏洞规则表
vuln_rule(id, cve, affected_version_raw)

Java 使用 semver4j 比较:

<dependency>
    <groupId>org.semver4j</groupId>
    <artifactId>semver4j</artifactId>
    <version>5.3.0</version>
</dependency>
List<Asset> assets = assetMapper.selectAll();
List<VulnRule> rules = vulnMapper.selectAll();

for (Asset asset : assets) {
    for (VulnRule rule : rules) {
        if (VersionMatcher.match(asset.getVersion(), rule.getAffectedVersion())) {
            // 命中漏洞
        }
    }
}

优点:存储简单,逻辑清晰。 缺点:全表扫描,百万资产时性能差。

方案二:拆成上下界存数据库(企业推荐)

>= 2.0-beta9 < 2.15.0 拆成字段:

CREATE TABLE vulnerability_affected_version (
    id          BIGINT PRIMARY KEY,
    vuln_id     BIGINT,
    min_version VARCHAR(50),
    max_version VARCHAR(50),
    min_include TINYINT,   -- 1=>=, 0=>
    max_include TINYINT    -- 1=<=, 0=<
);

Spring4Shell 的两个受影响区间存成两条记录:

INSERT INTO vulnerability_affected_version VALUES
(1, 'CVE-2022-22965', '5.2.0', '5.2.20', 1, 0),  -- 5.2.0 <= ver < 5.2.20
(2, 'CVE-2022-22965', '5.3.0', '5.3.18', 1, 0);  -- 5.3.0 <= ver < 5.3.18

查询时取出所有规则,在程序里用 SemVer 库比较,兼容 betarcsnapshot 等预发布版本。

方案三:版本号数字化后 SQL 直接查

2.15.0 转成整数:

// 2 * 1_000_000 + 15 * 1_000 + 0 = 2_015_000
int versionNum = major * 1_000_000 + minor * 1_000 + patch;

数据库存两列:

ALTER TABLE asset ADD COLUMN version_num BIGINT;
ALTER TABLE vuln_rule ADD COLUMN min_num BIGINT, ADD COLUMN max_num BIGINT;

-- 建索引
CREATE INDEX idx_version_num ON asset(version_num);

直接 SQL 查询:

SELECT a.*, v.cve
FROM asset a
JOIN vuln_rule v
  ON a.version_num >= v.min_num
 AND a.version_num <  v.max_num;

优点:可建索引,百万级数据也快。 缺点:不支持 betarc 等预发布后缀。

方案对比

方案速度支持预发布复杂度
程序 SemVer 比较全表扫
拆字段 + SemVer 库取出再比
版本数字化 + SQL索引查询

推荐设计(漏洞管理平台)

参考 NVD、OSV、Snyk 等平台的数据结构:

-- 漏洞主表
vulnerability(cve, severity, description, published_date)

-- 受影响版本(一个漏洞可能有多个区间)
vulnerability_affected_version(
    vuln_id,
    product,        -- 组件名
    min_version,
    max_version,
    min_inclusive,
    max_inclusive
)

接入外部数据源(NVD API、OSV API)时,它们的 JSON 结构也是这样分字段的,迁入成本最低。