版本号不能用字符串比较
-- 错误:字符串排序,结果是 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 BETWEEN 或 LIKE 处理。
方案一:数据库存原始版本,程序用 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 库比较,兼容 beta、rc、snapshot 等预发布版本。
方案三:版本号数字化后 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;
优点:可建索引,百万级数据也快。
缺点:不支持 beta、rc 等预发布后缀。
方案对比
| 方案 | 速度 | 支持预发布 | 复杂度 |
|---|---|---|---|
| 程序 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 结构也是这样分字段的,迁入成本最低。
