版本比较
本示例演示如何在 CPE 字符串中比较版本并执行基于版本的匹配操作。
概述
版本比较在处理 CPE 数据进行漏洞管理和软件清单时至关重要。CPE 库提供了多种方法来比较版本并确定兼容性。
下图展示了两个版本字符串如何逐段比较,以及比较结果如何用于范围判定:
完整示例
go
package main
import (
"fmt"
"log"
"sort"
"strings"
"github.com/scagogogo/cpe-skills"
)
// matchVersionPattern 检查具体版本是否匹配可能含 "*" 通配符的模式
// (如 "1.8.*" 匹配 "1.8.0_291")。按 "." 分段,模式段为 "*" 即匹配,
// 否则需与目标对应段相等;模式比版本短时,只要其所有段都匹配前缀即可。
func matchVersionPattern(version, pattern string) bool {
pSeg := strings.Split(pattern, ".")
vSeg := strings.Split(version, ".")
for i, p := range pSeg {
if p == "*" {
return true
}
if i >= len(vSeg) {
return false
}
if p != vSeg[i] {
return false
}
}
return len(pSeg) >= len(vSeg)
}
func main() {
fmt.Println("=== CPE版本比较示例 ===")
// 示例1:基本版本比较
fmt.Println("\n1. 基本版本比较:")
versions := []string{
"cpe:2.3:a:apache:tomcat:8.5.0:*:*:*:*:*:*:*",
"cpe:2.3:a:apache:tomcat:8.5.1:*:*:*:*:*:*:*",
"cpe:2.3:a:apache:tomcat:9.0.0:*:*:*:*:*:*:*",
"cpe:2.3:a:apache:tomcat:9.0.1:*:*:*:*:*:*:*",
}
for i, versionStr := range versions {
cpeObj, err := cpeskills.ParseCpe23(versionStr)
if err != nil {
log.Printf("解析失败 %s: %v", versionStr, err)
continue
}
fmt.Printf("版本 %d: %s (版本: %s)\n", i+1, cpeObj.ProductName, cpeObj.Version)
}
// 示例2:版本范围匹配
fmt.Println("\n2. 版本范围匹配:")
targetVersion, _ := cpeskills.ParseCpe23("cpe:2.3:a:apache:tomcat:8.5.5:*:*:*:*:*:*:*")
ranges := []struct {
min string
max string
description string
}{
{"8.5.0", "8.5.10", "Tomcat 8.5.x 系列 (0-10)"},
{"8.0.0", "9.0.0", "Tomcat 8.x 系列"},
{"9.0.0", "10.0.0", "Tomcat 9.x 系列"},
}
for _, r := range ranges {
// Version 是具名类型,传给 IsVersionInRange 前需转为 string。
inRange := cpeskills.IsVersionInRange(string(targetVersion.Version), r.min, r.max)
fmt.Printf("版本 %s 在范围 %s - %s (%s): %t\n",
targetVersion.Version, r.min, r.max, r.description, inRange)
}
// 示例3:语义版本比较
fmt.Println("\n3. 语义版本比较:")
baseVersion := "8.5.0"
compareVersions := []string{"8.4.9", "8.5.0", "8.5.1", "9.0.0"}
for _, compareVer := range compareVersions {
result := cpeskills.CompareVersions(baseVersion, compareVer)
var relationship string
switch result {
case -1:
relationship = "较旧"
case 0:
relationship = "相等"
case 1:
relationship = "较新"
}
fmt.Printf("%s 相对于 %s: %s\n", baseVersion, compareVer, relationship)
}
// 示例4:版本模式匹配
fmt.Println("\n4. 版本模式匹配:")
patterns := []string{
"cpe:2.3:a:microsoft:windows:10:*:*:*:*:*:*:*",
"cpe:2.3:a:microsoft:windows:11:*:*:*:*:*:*:*",
"cpe:2.3:a:oracle:java:1.8.*:*:*:*:*:*:*:*",
"cpe:2.3:a:oracle:java:11.*:*:*:*:*:*:*:*",
}
testCPEs := []string{
"cpe:2.3:a:microsoft:windows:10:*:*:*:*:*:*:*",
"cpe:2.3:a:oracle:java:1.8.0_291:*:*:*:*:*:*:*",
"cpe:2.3:a:oracle:java:11.0.12:*:*:*:*:*:*:*",
}
for _, testCPE := range testCPEs {
testObj, _ := cpeskills.ParseCpe23(testCPE)
fmt.Printf("\n测试: %s\n", testCPE)
for _, pattern := range patterns {
patternObj, _ := cpeskills.ParseCpe23(pattern)
// 仅比较 Version 字段,模式中的 "*" 通配符按段处理。
if matchVersionPattern(string(testObj.Version), string(patternObj.Version)) {
fmt.Printf(" ✓ 匹配模式: %s\n", pattern)
}
}
}
// 示例5:版本漏洞检查
fmt.Println("\n5. 版本漏洞检查:")
vulnerableRanges := []struct {
product string
minVersion string
maxVersion string
description string
}{
{"tomcat", "8.5.0", "8.5.4", "CVE-2021-25122"},
{"java", "1.8.0", "1.8.0_291", "CVE-2021-2163"},
{"windows", "10.0.0", "10.0.19041", "CVE-2021-1675"},
}
checkCPEs := []string{
"cpe:2.3:a:apache:tomcat:8.5.3:*:*:*:*:*:*:*",
"cpe:2.3:a:oracle:java:1.8.0_281:*:*:*:*:*:*:*",
"cpe:2.3:o:microsoft:windows:10.0.19042:*:*:*:*:*:*:*",
}
for _, checkCPE := range checkCPEs {
cpeObj, _ := cpeskills.ParseCpe23(checkCPE)
fmt.Printf("\n检查: %s\n", checkCPE)
for _, vuln := range vulnerableRanges {
// ProductName 是具名类型,与 string 比较前需转换。
if string(cpeObj.ProductName) == vuln.product {
isVulnerable := cpeskills.IsVersionInRange(string(cpeObj.Version), vuln.minVersion, vuln.maxVersion)
if isVulnerable {
fmt.Printf(" ⚠️ 存在漏洞: %s (版本 %s - %s)\n",
vuln.description, vuln.minVersion, vuln.maxVersion)
} else {
fmt.Printf(" ✅ 不受 %s 影响\n", vuln.description)
}
}
}
}
// 示例6:版本排序
fmt.Println("\n6. 版本排序:")
unsortedCPEs := []string{
"cpe:2.3:a:apache:tomcat:9.0.1:*:*:*:*:*:*:*",
"cpe:2.3:a:apache:tomcat:8.5.0:*:*:*:*:*:*:*",
"cpe:2.3:a:apache:tomcat:9.0.0:*:*:*:*:*:*:*",
"cpe:2.3:a:apache:tomcat:8.5.10:*:*:*:*:*:*:*",
"cpe:2.3:a:apache:tomcat:10.0.0:*:*:*:*:*:*:*",
}
fmt.Println("未排序的版本:")
for _, cpeStr := range unsortedCPEs {
cpeObj, _ := cpeskills.ParseCpe23(cpeStr)
fmt.Printf(" %s\n", cpeObj.Version)
}
// 用 sort.Slice + CompareVersions 升序排序。
sort.Slice(unsortedCPEs, func(i, j int) bool {
ci, _ := cpeskills.ParseCpe23(unsortedCPEs[i])
cj, _ := cpeskills.ParseCpe23(unsortedCPEs[j])
return cpeskills.CompareVersions(string(ci.Version), string(cj.Version)) < 0
})
fmt.Println("\n已排序的版本 (升序):")
for _, cpeStr := range unsortedCPEs {
cpeObj, _ := cpeskills.ParseCpe23(cpeStr)
fmt.Printf(" %s\n", cpeObj.Version)
}
}预期输出
text
=== CPE版本比较示例 ===
1. 基本版本比较:
版本 1: tomcat (版本: 8.5.0)
版本 2: tomcat (版本: 8.5.1)
版本 3: tomcat (版本: 9.0.0)
版本 4: tomcat (版本: 9.0.1)
2. 版本范围匹配:
版本 8.5.5 在范围 8.5.0 - 8.5.10 (Tomcat 8.5.x 系列 (0-10)): true
版本 8.5.5 在范围 8.0.0 - 9.0.0 (Tomcat 8.x 系列): true
版本 8.5.5 在范围 9.0.0 - 10.0.0 (Tomcat 9.x 系列): false
3. 语义版本比较:
8.5.0 相对于 8.4.9: 较新
8.5.0 相对于 8.5.0: 相等
8.5.0 相对于 8.5.1: 较旧
8.5.0 相对于 9.0.0: 较旧
...关键概念
1. 版本比较类型
- 精确匹配: 直接字符串比较
- 语义版本: 理解版本层次结构 (major.minor.patch)
- 范围匹配: 检查版本是否在范围内
- 模式匹配: 使用通配符和模式
2. 版本格式
库支持各种版本格式:
- 语义版本:
1.2.3 - 构建号:
1.8.0_291 - 基于日期:
2021.03.15 - 自定义格式:
10.0.19041.1234
3. 漏洞评估
版本比较对以下方面至关重要:
- 识别易受攻击的软件版本
- 检查补丁级别
- 合规性验证
- 安全扫描
最佳实践
- 规范化版本: 比较前始终规范化版本字符串
- 处理边界情况: 考虑预发布、测试版和 RC 版本
- 使用范围: 定义漏洞范围而不是精确版本
- 一致排序: 对版本列表使用语义排序
- 验证输入: 处理前始终验证版本字符串