JSON 支持
CVSS Skills 提供全面的 JSON 序列化和反序列化支持,便于数据存储、传输和与其他系统集成。
概述
CVSS 向量通过 Go 标准的 encoding/json 包支持 JSON。存在两种截然不同的 JSON 表示,由不同的 API 提供:
- 向量字符串形式 ——
json.Marshal(cvss)/json.Unmarshal产生一个 JSON 字符串,其内容为规范向量(例如"CVSS:3.1/AV:N/…")。紧凑、无损,是存储和传输的自然之选。 - 结构化形式 ——
cvss.ToJSON(calculator)返回一个JSONOutput对象,包含已分离的评分、严重性和各指标长值。当消费方需要评分明细而又不想重新解析或重新评分时使用。
两种形式都能干净地往返;向量字符串形式是规范的序列化方式。
JSON 结构
向量字符串形式(json.Marshal)
Cvss3x 实现了 MarshalJSON / UnmarshalJSON,使得 JSON 值就是向量字符串本身:
"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"对 *Cvss3x 调用 MarshalIndent 仍然只产生这个带引号的字符串(没有任何东西需要缩进)—— 若要格式化的结构化对象,请使用 ToJSON。
结构化形式(ToJSON)
ToJSON(calculator *Calculator) ([]byte, error) 返回带缩进的 JSON,评分和指标已填充完毕。它返回的字节已经过 marshal——直接使用即可,不要再通过 json.Marshal 传递(那样会把 byte slice 做 base64 编码)。
对于带时间指标的向量(CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H/E:F/RL:O/RC:C):
{
"version": "3.1",
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H/E:F/RL:O/RC:C",
"baseScore": 9.8,
"temporalScore": 9.1,
"baseSeverity": "Critical",
"temporalSeverity": "Critical",
"metrics": {
"base": {
"attackVector": "Network",
"attackComplexity": "Low",
"privilegesRequired": "None",
"userInteraction": "None",
"scope": "Unchanged",
"confidentiality": "High",
"integrity": "High",
"availability": "High",
"exploitabilityScore": 3.887,
"impactScore": 5.873
},
"temporal": {
"exploitCodeMaturity": "Functional",
"remediationLevel": "Official Fix",
"reportConfidence": "Confirmed"
}
}
}字段名遵循规范,而非结构体
指标字段使用 CVSS 长名称(attackVector、confidentiality、modifiedConfidentiality……)——没有 Impact 后缀。每个指标值是一个字符串长名称(例如 "Network"),而非嵌套对象。环境指标和 *Score 子评分仅在存在/已计算时出现。
最小的纯基础向量
纯基础向量(无时间/环境指标)会省略这些键:
{
"version": "3.1",
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"baseScore": 9.8,
"baseSeverity": "Critical",
"metrics": {
"base": {
"attackVector": "Network",
"attackComplexity": "Low",
"privilegesRequired": "None",
"userInteraction": "None",
"scope": "Unchanged",
"confidentiality": "High",
"integrity": "High",
"availability": "High",
"exploitabilityScore": 3.887,
"impactScore": 5.873
}
}
}序列化操作(Marshal)
向量字符串形式
对 *Cvss3x 调用 json.Marshal 产生一个 JSON 字符串,其内容为规范向量:
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/scagogogo/cvss-skills/pkg/parser"
)
func main() {
vector, err := parser.ParseString("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H")
if err != nil {
log.Fatal(err)
}
jsonData, err := json.Marshal(vector)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(jsonData))
// "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
}结构化形式(ToJSON)
若要带评分和各指标长值的 JSON 对象,请使用 ToJSON。它返回已经过 marshal 的字节——直接打印或写入即可:
func vectorToStructuredJSON(vector *cvss.Cvss3x) ([]byte, error) {
calc := cvss.NewCalculator(vector)
return vector.ToJSON(calc)
}
// 用法
out, err := vectorToStructuredJSON(vector)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(out))格式化的向量字符串
对 *Cvss3x 调用 MarshalIndent 仍然只产生带引号的向量字符串(无可缩进内容)。若需要格式化的对象,请使用上面的 ToJSON(它已经带缩进)。
func vectorToPrettyJSON(vector *cvss.Cvss3x) (string, error) {
jsonData, err := json.MarshalIndent(vector, "", " ")
if err != nil {
return "", err
}
return string(jsonData), nil
}自定义 JSON 标签
// 带特定 JSON 格式的自定义结构体
type CVSSExport struct {
Vector string `json:"cvss_vector"`
Version string `json:"version"`
BaseScore float64 `json:"base_score"`
Severity string `json:"severity"`
Timestamp string `json:"timestamp"`
}
func exportToCustomJSON(vector *cvss.Cvss3x) ([]byte, error) {
calculator := cvss.NewCalculator(vector)
score, err := calculator.Calculate()
if err != nil {
return nil, err
}
export := CVSSExport{
Vector: vector.String(),
Version: vector.Version(),
BaseScore: score,
Severity: calculator.GetSeverityRating(score).String(),
Timestamp: time.Now().Format(time.RFC3339),
}
return json.MarshalIndent(export, "", " ")
}反序列化操作(Unmarshal)
基本反序列化
json.Unmarshal 接受向量字符串 JSON 形式——JSON 值必须是一个带引号的向量字符串:
func vectorFromJSON(jsonData []byte) (*cvss.Cvss3x, error) {
var vector cvss.Cvss3x
err := json.Unmarshal(jsonData, &vector)
if err != nil {
return nil, fmt.Errorf("JSON 解组失败: %w", err)
}
return &vector, nil
}
// 用法
jsonData := []byte(`"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"`)
vector, err := vectorFromJSON(jsonData)
if err != nil {
log.Fatal(err)
}
fmt.Printf("已加载向量: %s\n", vector.String())结构化的 ToJSON 形式不能直接反序列化
ToJSON 的 JSONOutput 形状({"version":…,"metrics":…})是一个仅用于输出的表示。json.Unmarshal 到 *Cvss3x 期望的是向量字符串形式。若要从 JSONOutput 重构 Cvss3x,请提取其 vectorString 字段并对其进行反序列化(或用 parser.ParseString 解析)。
反序列化后验证
func loadAndValidateVector(jsonData []byte) (*cvss.Cvss3x, error) {
vector, err := vectorFromJSON(jsonData)
if err != nil {
return nil, err
}
// 验证加载的向量(Check 对第一个缺失/无效的指标返回普通 error;
// Validate 对所有指标返回结构化的 ValidationErrors)。
if err := vector.Check(); err != nil {
return nil, fmt.Errorf("加载的向量无效: %w", err)
}
// 额外的版本检查
if vector.MajorVersion != 3 {
return nil, fmt.Errorf("不支持的 CVSS 版本: %d.%d",
vector.MajorVersion, vector.MinorVersion)
}
return vector, nil
}处理缺失字段
func loadVectorWithDefaults(jsonData []byte) (*cvss.Cvss3x, error) {
var vector cvss.Cvss3x
// 解组前设置默认值
vector.MajorVersion = 3
vector.MinorVersion = 1
err := json.Unmarshal(jsonData, &vector)
if err != nil {
return nil, err
}
// 如有需要,用默认值填充缺失的基础指标
if vector.Cvss3xBase == nil {
return nil, fmt.Errorf("基础指标是必需的")
}
return &vector, nil
}文件操作
保存到文件
func saveVectorToFile(vector *cvss.Cvss3x, filename string) error {
// 使用 ToJSON 获得格式化的结构化对象,或用 json.Marshal
// 获得紧凑的向量字符串形式。
jsonData, err := vector.ToJSON(cvss.NewCalculator(vector))
if err != nil {
return fmt.Errorf("JSON 编组失败: %w", err)
}
err = os.WriteFile(filename, jsonData, 0644)
if err != nil {
return fmt.Errorf("文件写入失败: %w", err)
}
return nil
}
// 用法
err := saveVectorToFile(vector, "cvss_vector.json")
if err != nil {
log.Fatal(err)
}从文件加载
func loadVectorFromFile(filename string) (*cvss.Cvss3x, error) {
jsonData, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("文件读取失败: %w", err)
}
return loadAndValidateVector(jsonData)
}批量文件操作
func saveVectorBatch(vectors []*cvss.Cvss3x, directory string) error {
for i, vector := range vectors {
filename := filepath.Join(directory, fmt.Sprintf("vector_%d.json", i+1))
if err := saveVectorToFile(vector, filename); err != nil {
return fmt.Errorf("保存向量 %d 失败: %w", i+1, err)
}
}
return nil
}
func loadVectorBatch(directory string) ([]*cvss.Cvss3x, error) {
files, err := filepath.Glob(filepath.Join(directory, "*.json"))
if err != nil {
return nil, err
}
var vectors []*cvss.Cvss3x
for _, file := range files {
vector, err := loadVectorFromFile(file)
if err != nil {
log.Printf("警告: 加载 %s 失败: %v", file, err)
continue
}
vectors = append(vectors, vector)
}
return vectors, nil
}HTTP API 集成
REST API 处理器
func handleCVSSVector(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "POST":
// 从请求体解析向量
var request struct {
Vector string `json:"vector"`
}
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
http.Error(w, "无效的 JSON", http.StatusBadRequest)
return
}
// 解析 CVSS 向量
p := parser.NewCvss3xParser(request.Vector)
vector, err := p.Parse()
if err != nil {
http.Error(w, fmt.Sprintf("解析错误: %v", err), http.StatusBadRequest)
return
}
// 计算评分
calculator := cvss.NewCalculator(vector)
score, err := calculator.Calculate()
if err != nil {
http.Error(w, fmt.Sprintf("计算错误: %v", err), http.StatusInternalServerError)
return
}
// 返回响应
response := struct {
Vector *cvss.Cvss3x `json:"vector"`
Score float64 `json:"score"`
Severity string `json:"severity"`
}{
Vector: vector,
Score: score,
Severity: calculator.GetSeverityRating(score).String(),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
case "GET":
// 返回示例向量
example := getExampleVector()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(example)
}
}JSON Schema 验证
若必须针对 schema 验证结构化的 ToJSON 形式(例如在解析前的 API 边界处),它匹配如下形状:
import "github.com/xeipuuv/gojsonschema"
const cvssJSONSchema = `{
"type": "object",
"required": ["version", "vectorString", "baseScore", "baseSeverity", "metrics"],
"properties": {
"version": { "type": "string", "enum": ["3.0", "3.1"] },
"vectorString": { "type": "string" },
"baseScore": { "type": "number", "minimum": 0, "maximum": 10 },
"baseSeverity": { "type": "string", "enum": ["None", "Low", "Medium", "High", "Critical"] },
"metrics": {
"type": "object",
"required": ["base"],
"properties": {
"base": {
"type": "object",
"required": ["attackVector", "attackComplexity", "privilegesRequired",
"userInteraction", "scope", "confidentiality",
"integrity", "availability"]
}
}
}
}
}`
func validateCVSSJSON(jsonData []byte) error {
schemaLoader := gojsonschema.NewStringLoader(cvssJSONSchema)
documentLoader := gojsonschema.NewBytesLoader(jsonData)
result, err := gojsonschema.Validate(schemaLoader, documentLoader)
if err != nil {
return err
}
if !result.Valid() {
var errors []string
for _, desc := range result.Errors() {
errors = append(errors, desc.String())
}
return fmt.Errorf("验证错误: %s", strings.Join(errors, "; "))
}
return nil
}优先解析而非 schema 验证
最可靠的验证方式是 parser.ParseString(vectorString)(或对向量字符串形式 json.Unmarshal),然后对结果调用 Check() / Validate()。Schema 验证只检查形状;库会检查 CVSS 语义。
数据库集成
SQL 数据库存储
import "database/sql"
func saveVectorToDB(db *sql.DB, vector *cvss.Cvss3x) error {
jsonData, err := json.Marshal(vector)
if err != nil {
return err
}
query := `INSERT INTO cvss_vectors (vector_string, json_data, created_at) VALUES (?, ?, ?)`
_, err = db.Exec(query, vector.String(), string(jsonData), time.Now())
return err
}
func loadVectorFromDB(db *sql.DB, id int) (*cvss.Cvss3x, error) {
var jsonData string
query := `SELECT json_data FROM cvss_vectors WHERE id = ?`
err := db.QueryRow(query, id).Scan(&jsonData)
if err != nil {
return nil, err
}
return vectorFromJSON([]byte(jsonData))
}NoSQL 数据库存储
import "go.mongodb.org/mongo-driver/mongo"
func saveVectorToMongo(collection *mongo.Collection, vector *cvss.Cvss3x) error {
document := struct {
VectorString string `bson:"vector_string"`
Vector *cvss.Cvss3x `bson:"vector"`
CreatedAt time.Time `bson:"created_at"`
}{
VectorString: vector.String(),
Vector: vector,
CreatedAt: time.Now(),
}
_, err := collection.InsertOne(context.Background(), document)
return err
}性能优化
流式 JSON
func streamVectors(vectors []*cvss.Cvss3x, w io.Writer) error {
encoder := json.NewEncoder(w)
// 写入数组开始
w.Write([]byte("["))
for i, vector := range vectors {
if i > 0 {
w.Write([]byte(","))
}
if err := encoder.Encode(vector); err != nil {
return err
}
}
// 写入数组结束
w.Write([]byte("]"))
return nil
}内存高效加载
func processLargeJSONFile(filename string, processor func(*cvss.Cvss3x) error) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
decoder := json.NewDecoder(file)
// 读取开始括号
_, err = decoder.Token()
if err != nil {
return err
}
// 处理每个向量
for decoder.More() {
var vector cvss.Cvss3x
if err := decoder.Decode(&vector); err != nil {
return err
}
if err := processor(&vector); err != nil {
return err
}
}
// 读取结束括号
_, err = decoder.Token()
return err
}最佳实践
1. 错误处理
func safeJSONOperation(vector *cvss.Cvss3x) ([]byte, error) {
if vector == nil {
return nil, fmt.Errorf("向量不能为空")
}
if err := vector.Check(); err != nil {
return nil, fmt.Errorf("向量无效: %w", err)
}
jsonData, err := json.Marshal(vector)
if err != nil {
return nil, fmt.Errorf("JSON 编组失败: %w", err)
}
return jsonData, nil
}2. 版本兼容性
func ensureCompatibility(vector *cvss.Cvss3x) error {
if vector.MajorVersion != 3 {
return fmt.Errorf("不支持的主版本: %d", vector.MajorVersion)
}
if vector.MinorVersion < 0 || vector.MinorVersion > 1 {
return fmt.Errorf("不支持的次版本: %d", vector.MinorVersion)
}
return nil
}3. 数据完整性
func verifyJSONRoundTrip(original *cvss.Cvss3x) error {
// 序列化
jsonData, err := json.Marshal(original)
if err != nil {
return err
}
// 反序列化
var restored cvss.Cvss3x
if err := json.Unmarshal(jsonData, &restored); err != nil {
return err
}
// 比较
if original.String() != restored.String() {
return fmt.Errorf("往返验证失败")
}
return nil
}相关文档
- CVSS 数据结构 - 了解数据结构
- Calculator 计算器 - 评分计算
- Parser 解析器 - 向量解析
- 使用示例 - 详细使用示例