资讯动态

CNNVD漏洞数据离线库:字段解析、入库与实战查询指南

发布时间:2026/10/19 12:17:46 来源:尧图企业网站定制
简介CNNVD漏洞资源文件是一份面向安全研究人员、渗透测试学习者及企业运维人员的数据资源聚焦中国国家信息安全漏洞库的公开漏洞条目可用于漏洞情报检索、风险分析与防护研究。包内共25个文件含23个gzip压缩包、1个XML文件及1个Markdown说明文档。gzip包按年份归档1999至2019年的CNNVD漏洞XML数据XML文件对应最新漏洞信息MD文件则介绍目录结构与用法整体约68.85MB适合本地化存储、批量解析和离线研究也便于导入自有漏洞管理平台进行二次加工。已有1058人学习浏览。借助这批数据可直观统计漏洞类型分布、影响版本与危害等级通过脚本批量解析XML字段可整理出专属漏洞知识库为系统补丁优先级排序、安全基线检查、应急响应演练和日常安全运营提供重要参考依据。1. CNNVD漏洞资源文件安全从业者该有的离线情报底库做安全的人都知道等漏洞情报真出问题时线上漏洞库往往是最先卡壳的接口限流、字段说法不统一、想按产品线过滤却导不出来。CNNVD漏洞资源文件解决的正是这个问题——它是一份可离线保存、结构化组织的漏洞情报底库涵盖漏洞编号、危害等级、影响产品、修复方案等关键信息。应急响应时不用去各家平台现扒本地一把梭。它适合做漏洞运营、等保测评、甲方安全自查的从业者也适合刚入门想弄清漏洞数据长什么样的新人。2. 先认清这张表的骨架字段语义与检索路径2.1 核心字段漏洞编号、危害等级和影响面才是关键我拿到任何漏洞库数据时第一件事永远是看字段清单不是看有多少条数据。CNNVD这份资源的记录粒度通常到单个漏洞每条记录里真正能直接干活的字段大概是下面这几个字段作用使用场景漏洞编号CNNVD-YYYYMM-NNNN唯一标识和外部情报源做关联危害等级危急/高危/中危/低危排优先级漏洞类型CWE分类描述判断攻击面影响产品厂商-产品-版本资产匹配修复方案补丁、升级版本生成处置单发布时间漏洞公开时间时效性判断关于编号格式我常看到有人把CNNVD编号和CVE编号搞混二者位数和命名规则并不一致。做关联分析时最省事的做法是把两个编号都单独存一列而不是拼在一个字符串里后面做JOIN能省掉大量清洗工作。2.2 数据格式差异XML、JSON、CSV选哪个看场景这份资源在市面上流通的常见格式有XML、JSON和CSV三种。我可以直接说结论如果做历史数据批量分析和入库选CSV如果做增量同步和跨系统交换选JSON如果只是存档不做二次开发XML也够用。常见做法是优先拿JSON做增量拿CSV做全量快照。原因在于JSON保留的层级关系更适合描述“一个漏洞影响多个版本”而CSV遇到这种一对多关系时会变成一行记录里用分隔符拼多个版本号解析时要额外处理。我一般会收两套格式一套入库一套留着排查用。2.3 判断资源新旧文件命名规则看一眼就懂拿到资源压缩包先别急着导数据。先看命名里有没有年份和月份区间。如果一个包按年份拆分那文件名里的年份就是数据的时间跨度而不是发布日期——这两个概念经常被搞反。另一个判断点是增量包和全量包的区分。一般打包方会同时给一个全量文件和一个自某个时间点以来的增量文件。识别的方法不复杂解压后看里面有没有类似update_time或者last_modified的字段或者看文件里最新一条记录的发布时间是否接近当前日期。如果一份资源里最晚的漏洞记录距今超过一个月那这份资源做实时响应就不够格只能当历史数据参考。3. 把资源变成资产解析入库与增量更新的完整流程3.1 解析脚本从XML到SQLite的三步拆解拿到XML格式的漏洞库最常见的处理方式是落到SQLite里查询。很多人在这一步翻车原因是直接用正则去抠字段——XML结构一旦有属性顺序变化就废了。我一般用Python的xml.etree.ElementTree解析逻辑清楚出错也容易定位。import xml.etree.ElementTree as ET import sqlite3 # 解析XML文件提取漏洞记录并写入SQLite tree ET.parse(cnnvd_data.xml) root tree.getroot() conn sqlite3.connect(cnnvd.db) cursor conn.cursor() cursor.execute( CREATE TABLE IF NOT EXISTS vulns ( cnnvd_id TEXT PRIMARY KEY, name TEXT, severity TEXT, cve_id TEXT, affected_product TEXT, fix_action TEXT, publish_time TEXT ) ) # 注意不同来源的XML字段名可能不同上线前先打印一条记录确认 count 0 for vuln in root.findall(vulnerability): item { cnnvd_id: vuln.findtext(cnnvd_id, ), name: vuln.findtext(name, ), severity: vuln.findtext(severity, ), cve_id: vuln.findtext(cve_id, ), affected_product: vuln.findtext(affected_product, ), fix_action: vuln.findtext(fix_action, ), publish_time: vuln.findtext(publish_time, ), } cursor.execute( INSERT OR REPLACE INTO vulns VALUES (:cnnvd_id, :name, :severity, :cve_id, :affected_product, :fix_action, :publish_time) , item) count 1 conn.commit() conn.close() print(f写入记录数: {count})代码里有两个细节值得注意。findtext对不存在的字段会返回None我统一补了空字符串避免后面拼接字符串时报错。INSERT OR REPLACE的策略是直接以cnnvd_id为主键后续增量更新时相同编号的记录会被覆盖不会产生重复冗余。影响产品字段在这个解析逻辑里是一整块字符串。实际分析时我会再拆一层按“厂商|产品|版本”的分隔规则单独建表这样才能和内部资产清单做匹配。3.2 增量更新策略按日期去重还是按编号去重增量更新这件事不同来源的CNNVD资源逻辑不太一样。有的按发布时间给增量包有的按收录时间给增量包两种逻辑得出的“新数据”范围完全不同。我踩过一次坑某次更新后库里出现了同一漏洞编号但两条不同修复方案的记录。排查后发现增量包是按收录时间筛选的同一条漏洞在初次收录和补录修复信息时被当成两条记录打包了。解决方式是先按编号做去重再比较时间去重。顺序不能反。按编号去重用一条简单的SQL就能完成-- 按编号去重保留修复信息最完整的一条 SELECT cnnvd_id, name, severity, cve_id, affected_product, fix_action, publish_time FROM ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY cnnvd_id ORDER BY (fix_action IS NOT NULL) DESC, publish_time DESC ) AS rn FROM vulns_staging ) t WHERE rn 1;这里ROW_NUMBER按cnnvd_id分区排序时优先保留修复方案非空的记录其次保留发布时间最新的记录。窗口函数看起来高级其实不复杂但有前提数据要先落到临时表里不要在原始表上直接操作给自己留条后路。3.3 多源交叉验证CVE编号和CNNVD编号怎么对齐同一个漏洞CVE编号和CNNVD编号都有这是常态。但两份数据的发布时间、危害等级可能不一致——因为两个库的评级体系不同CNNVD更侧重国内受影响范围CVE更侧重通用严重性。对齐逻辑不复杂难在编号格式。CVE编号是CVE-年份-序号CNNVD编号是CNNVD-年份月份-序号。做关联时最稳的匹配键是CVE编号字段因为CNNVD记录的cve_id一般直接引用CVE编号。匹配率不需要追求100%。比如某些漏洞只有CNNVD收录没有CVE编号这是正常现象不要当成数据质量问题。对齐的核心目的是交叉验证不是强行合并——两边都有记录时取等级更高的一方做保守处置只有一方记录时标注来源。4. 实战排查漏洞数据怎么落到业务资产清单4.1 按影响产品字段定位内部资产而不是搜漏洞名称做漏洞排查时最常见的低效操作是拿漏洞名称去搜资产。漏洞名称是攻击者视角的描述资产清单是运维视角的命名双方经常对不上。正确路径是反过来从影响产品字段倒推资产。例如影响产品里写着某CMS的内容管理系统存在代码执行漏洞那就应该在资产清单里搜“CMS”相关的产品名确认版本是否落在受影响区间。-- 按影响产品定位资产场景查某内容管理系统相关漏洞 SELECT cnnvd_id, name, severity, affected_product, fix_action FROM vulns WHERE affected_product LIKE %内容管理系统% OR affected_product LIKE %CMS% ORDER BY severity DESC, publish_time DESC;这里两个LIKE条件用OR连接是因为不同记录里同一产品的写法可能不一致——这是多源数据合并后的常态。做完这一步输出结果基本就是一个待处置清单哪些系统受影响、对应修复版本是什么。4.2 补丁闭环修复方案字段才是处置单的核心查询结果里的fix_action字段很多时候包含的不只是补丁下载地址还有升级版本号、临时缓解措施。这些信息适合先整理成处置建议再发给运维执行而不是直接把漏洞编号扔过去。我一般会把查询结果导成CSV格式再手工加一列负责人。这里有个效率技巧不要导出全字段只导编号、产品、等级、修复建议责任人才看得下去。字段太多反而会被漏掉关键信息。# 导出处置清单用制表符分隔方便直接贴进表格工具 sqlite3 -header -tsv cnnvd.db \ SELECT cnnvd_id, severity, affected_product, fix_action FROM vulns WHERE severity危急 AND affected_product LIKE %某系统%; \ urgent_fix.tsv这条命令的作用是把SQLite里的查询结果导出为Tab分隔的文本文件--header参数带上列名。用Tab而不是逗号是因为修复方案字段里经常有逗号直接导CSV容易把列拆坏。4.3 常见误用三个容易翻车的查询场景先说第一个误用拿CNNVD的等级字段直接当CVE等级用。两者评级标准不一样对外报告时职责不同混用会被审计挑刺。第二筛选“只影响某版本”的漏洞时很多人用精确匹配版本号但漏洞描述里常写的是“低于某版本”应该用范围筛选而不是等值匹配。第三历史数据里的产品名称和现在的资产命名不完全一致比如厂商改名、产品线整合这时候靠分词匹配比全名匹配命中率高。5. 避坑指南CNNVD数据里五个容易踩的坑5.1 解析后记录数比源文件少排查半天发现是编码问题现象用Python脚本解析XML后打印的记录数只有源文件的一半也没有报错。原因XML文件头没声明编码或声明的编码与实际不符解析时含特殊字符的记录被静默跳过。解决解析前先用文本编辑器打开XML确认?xml version1.0 encodingUTF-8?这一行存在并把encoding改成实际编码。解析时显式指定编码tree ET.parse(cnnvd_data.xml, parserET.XMLParser(encodingutf-8))5.2 增量包导入后数据不减反增编号换行符在捣乱现象按编号去重后仍然出现重复肉眼对比两条记录内容一模一样。原因某条记录的编号字段末尾带了\r或不可见字符导致两条相同记录被当成不同编号。解决入库前统一清洗编号字段去掉首尾空白和控制字符item[cnnvd_id] vuln.findtext(cnnvd_id, ).strip()5.3 危害等级统计和官方口径不一致原因是大小写不统一现象统计各等级漏洞数量时出现“高危”“高危 ”“High”三种写法数量汇总对不上。原因多份数据源合并时等级字段的枚举值没有做归一化。解决入库时把等级字段统一映射为中文枚举值severity_map {高: 高危, High: 高危, 中: 中危, 低: 低危}5.4 漏洞时间筛选不准发布时间和收录时间被混用现象按月份筛选漏洞发现某个月的记录特别少下个月又异常多。原因有的记录写的是漏洞公开时间有的写的是收录时间时间字段没有统一口径。解决筛选前先确认字段定义。该资源里如果同时有两个时间字段以发布时间为准做统计收录时间只用于增量更新判断。5.5 查询明显存在的漏洞却返回空是影响产品字段做了转义现象用某个产品名做精确查询返回零结果但手工翻阅数据时明明能看到。原因影响产品字段里包含了HTML转义字符如amp;代表查询条件没做对应转义。解决查询时把替换成amp;再匹配或者入库时直接统一反转义。6. 进阶技巧把这份漏洞库做成一个可交互的离线查询服务到这里数据已经入库了但SQLite文件直接丢给别人用并不友好——不是人人都会写SQL。我一般在数据落库后加一步用Python标准库起一个轻量HTTP服务把查询接口暴露出来团队里任何人通过浏览器就能按等级、产品名、时间范围筛选漏洞不需要碰命令行。from http.server import BaseHTTPRequestHandler, HTTPServer import sqlite3 import urllib.parse class VulnHandler(BaseHTTPRequestHandler): def do_GET(self): params urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query) severity params.get(severity, [])[0] product params.get(product, [])[0] conn sqlite3.connect(cnnvd.db) sql SELECT cnnvd_id, name, severity, affected_product, fix_action FROM vulns WHERE 11 args [] if severity: sql AND severity ? args.append(severity) if product: sql AND affected_product LIKE ? args.append(f%{product}%) rows conn.execute(sql, args).fetchall() conn.close() output for row in rows: output ftrtd{row[0]}/tdtd{row[1]}/tdtd{row[2]}/tdtd{row[3]}/td/tr html ftable border1trth编号/thth名称/thth等级/thth影响产品/th/tr{output}/table self.send_response(200) self.send_header(Content-Type, text/html; charsetutf-8) self.end_headers() self.wfile.write(html.encode(utf-8)) HTTPServer((127.0.0.1, 8080), VulnHandler).serve_forever()这段代码的核心思路是把查询参数通过URL传递服务端把参数拼进SQL返回一个简单的HTML表格。11这个恒真条件是为了后面拼接多个筛选条件时不破坏SQL语法这是动态查询的常见写法。这个服务有一个明显边界——它只适合内网小范围使用没有做鉴权千万不能暴露到公网。如果团队有统一的安全平台更合适的做法是导出成JSON格式通过平台上送告警数据和漏洞扫描结果做关联分析。从那以后我每次做漏洞数据整理都强制走一遍“先确认字段→清洗入库→验证去重→提供查询入口”的流程省下的都是应急时的保命时间。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑