ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

安全测试可视化看板落地实践:Grafana集成漏洞管理与告警配置

安全测试可视化看板落地实践:Grafana集成漏洞管理与告警配置 安全测试结果的可视化过去一直是被低估的环节。扫描器出了报告、渗透测试写了文档、修复完再登记个Excel等月底复盘时才拉出来看一眼中间出了什么风险、修复进展怎么样、还有多少高危漏洞在裸奔全靠人工脑补。我也是被这个问题折磨了大半年才下决心把Grafana接入安全测试流程把漏洞数据、资产信息、修复进度全部集中到一个看板上让团队成员打开浏览器就能看到当前的安全态势而不是等每周的例会材料。这篇博文就围绕这套可视化看板的集成实践展开从方案选型、数据源接入、看板搭建到告警配置完整记录我落地这套系统时踩过的坑和沉淀下来的做法。适合安全工程师、DevOps运维人员和测试开发同学参考尤其是正在做安全测试结果聚合但不知道从哪下手的人。1. 项目背景与方案选型为什么安全测试结果需要可视化看板1.1 安全测试数据的现状痛点先说一个很现实的问题安全测试的结果数据天生就是多源异构的。漏洞扫描器比如Nessus、AWVS、Xray输出自己的报告格式SAST工具报告一堆代码缺陷DAST工具跑出一堆URL问题渗透测试的人工发现还散落在Word和Excel里中间件漏洞、供应链组件风险又来自另一个平台。想把这些数据汇总起来回答几个最基础的问题——当前有多少高危漏洞、哪些资产受影响、修复率是多少——都能让人焦头烂额。我见过不少团队的做法是每周让安全工程师手动导出几份报告拼到PPT里。这种做法有两个致命问题。一是损耗实时性漏洞从发现到被人看见中间隔了好几天攻击者比你还早知道二是损耗细节汇总报告通常只会保留高危有几个、中危有几个这类汇总数字真正排查需要知道的具体漏洞、受影响资产、时间分布这些信息全部被打平了。可视化看板要解决的就是让这些数据从报告里的静态文字变成屏幕上的实时态势。不是把报告变得更漂亮而是把分散的工具结果统一起来基于同一个时间轴、同一套资产维度让趋势、变化、分布变得可见且可追踪。1.2 技术方案选型的心路历程选型的核心问题只有一个数据往哪儿放。当时我对比了三类方案。第一类是直接购买商业的安全运营平台功能全、开箱即用但价格高而且通常要绑定自家扫描器我们已有的工具链不好接入数据模型也被锁死。第二类是自研一个小型管理系统后端API加上前端列表页。这个方案灵活但开发成本不低而且做好了也就是个漏洞管理列表图表只能自己画趋势分析、报表导出这些功能全要重复造轮子迭代周期太长。第三类就是Grafana加Prometheus/Loki/MySQL这条路。Grafana本身是可视化平台不需要做报表模块它的数据源插件体系相当成熟Prometheus、MySQL、Elasticsearch、Loki都原生支持意味着数据只要有地方存就能快速出图告警功能内置配置好通知渠道就能联动钉钉、邮件和企业微信。而且社区插件多后续想扩展也方便。最终我选择Grafana作为展示层底数数据的存储根据不同来源分了三条链路实时监控指标走Prometheus日志和扫描结果详情走Loki结构化漏洞库走MySQL。看板连接这三个数据源各取所长。1.3 Grafana在安全测试场景中的定位很多人一提Grafana就想到机器监控CPU、内存、网络流量觉得这是运维的专属工具。实际上Grafana的处理对象是时序指标和结构化数据安全测试结果天然符合这个模型——漏洞发现时间是时间轴风险等级是维度资产IP、团队归属这些是标签。所以Grafana完全可以承载安全测试看板且不需要引入一套全新的安全产品。我推荐的定位方式Grafana只做呈现和告警不承担数据采集和业务逻辑。安全测试的数据采集仍然由各扫描器、爬虫和CI/CD流水线完成通过写脚本或者接口上报到存储层Grafana负责把存储层的数据组织成有价值的视图。这个分工让系统边界清晰扫描器要换、数据格式要改都动不到看板本身。2. 数据管道与Grafana数据源对接让安全测试数据流进来2.1 三种数据接入路径的取舍先明确一个概念Grafana本身不存数据它只是问数据源要数据。所以接入的第一步是想清楚安全测试结果以什么形式、存在哪里。我实际落地时用了三条路径适配不同类型的数据。路径A结构化漏洞库入MySQL。这类数据格式最规整——漏洞编号、标题、风险等级、受影响资产、发现时间、修复状态。适合放在关系型数据库里Grafana通过MySQL数据源查询做分组统计和多维筛选。适用于漏洞管理、修复进度追踪。路径B采集器指标入Prometheus。扫描任务的执行状态、扫描耗时、扫描器自身健康度这类运行时指标以指标的形式暴露。Prometheus按固定间隔抓取Grafana展示成时序图。适用于流水线监控、扫描任务趋势。路径C半结构化报告入Loki。扫描器导出的JSON报告、系统日志、CI阶段输出不适合拆成字段存数据库直接以日志形式推给Loki。Grafana的LogQL可以解析JSON字段提取漏洞等级、扫描目标等信息。适用于渗透测试过程日志、扫描器原始输出检索。这三条路径可以并行。数据源头不一样进存储的通道不同但最终都汇入同一个Grafana组织可以在同一块看板上关联展示。2.2 结构化数据表设计与入库脚本我的实践是建两张核心表漏洞主表和资产表。字段设计上有几个容易被忽视的点单独说说。漏洞主表的字段字段名类型说明idbigint自增主键vuln_idvarchar(64)漏洞唯一编号通常是扫描器自带IDtitlevarchar(255)漏洞标题severityvarchar(16)严重级别critical/high/medium/lowasset_ipvarchar(64)受影响资产IPasset_namevarchar(128)资产名称teamvarchar(128)负责团队statusvarchar(16)状态open/fixing/fixed/ignoreddiscovered_atdatetime发现时间fixed_atdatetime修复时间可为空sourcevarchar(64)来源nessus/xray/sast/dast/manualraw_datajson扫描器原始字段留着备用几个容易踩坑的设计点severity不要用中文用枚举字符串。中文在分组统计和过滤时容易出编码问题而且告警规则判断时大小写不敏感容易误判。asset_ip和asset_name分开存别看它们经常是一对一但资产改名或IP复用很常见拆开才能关联资产历史。raw_data字段很重要。扫描器报告经常带一些额外信息CVE编号、CVSS分值、漏洞描述当前用不到但后续做关联分析时就是宝不需要回头改表结构。入库脚本我用Python写了个定时任务每小时从扫描平台拉一次新增漏洞做增量写入。关键点是要维护一个游标位置记录上次拉取的偏移量避免重复入库。首次全量导入没关系但增量阶段如果每轮都全量拉数据量上来后压力会很大。import mysql.connector import requests import json def fetch_vulns(last_id): payload {since_id: last_id, limit: 100} resp requests.post(https://scanner.internal/api/v1/vulns, jsonpayload, timeout30) resp.raise_for_status() return resp.json()[data] def sync_vulns(): last_id load_cursor() cnx mysql.connector.connect(usergrafana_ro, password..., host10.0.1.10, databasesecurity_center) cursor cnx.cursor() for vuln in fetch_vulns(last_id): sql INSERT INTO vulns (vuln_id, title, severity, asset_ip, asset_name, team, status, discovered_at, source, raw_data) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE titleVALUES(title), severityVALUES(severity) cursor.execute(sql, (vuln[vuln_id], vuln[title], vuln[severity], vuln[asset_ip], vuln[asset_name], vuln[team], vuln[status], vuln[discovered_at], vuln[source], json.dumps(vuln, ensure_asciiFalse))) last_id max(last_id, vuln[id]) cnx.commit() cursor.close() cnx.close() save_cursor(last_id)增量同步的游标一定要持久化。我就吃过亏脚本重启后游标丢失结果从0开始全量同步把数据库连接拖崩了。现在游标存在本地文件里脚本每次启动先读文件再决定从哪里开始拉。2.3 Loki接入扫描报告日志Loki和Prometheus是同一家公司的产品接入方式很像是老朋友了。在Grafana中添加Loki数据源只需要填一个URL。扫描器报告的推送我用的Promtail文件监听模式把扫描结果JSON文件的目录挂给Promtail新文件出现时自动推送。看板中查询Loki数据长度这样写{appsecurity-scanner} | json | severityhigh | unwrap duration这条查询的意思选择app等于security-scanner的日志流解析JSON字段筛出高危漏洞然后把duration字段作为数值用于图表绘制。需要注意LogQL的json解析要求日志内容是标准JSON单行格式如果是pretty打印的多行JSONPromtail推过来会解析失败。这个坑我踩了不止一次现在扫描器输出日志前统一先做一次单行化处理。2.4 数据源配置的坑与注意事项Grafana数据源配置本身不难但有几个细节值得记住。MySQL数据源建议使用只读账号。看板只需要查询不需要写入。用读写账号万一某条查询误触发了更新操作后果是很严重的。另外Grafana对MySQL查询有个限制默认不允许SELECT以外的语句这个可以保持开启。连接超时参数要设置。数据量大的情况下一条慢查询可能拖到30秒以上看板加载时会转圈很久。我在MySQL数据源配置中将timeout设为10秒查询超过10秒就直接失败宁可看板局部加载失败也不能卡死整个页面。Loki查询如果涉及高频解析建议开启log volume的缓存。这个在Loki配置里可以调减少重复查询的负载。3. 看板设计与可视化落地从一堆数字到一屏了然3.1 看板的分层设计思路看板不是把图表堆在一起就完事了布局和层级直接决定了使用效率。我设计的看板分了四个区域按照总览→趋势→明细→追踪的逻辑排布。第一行是核心指标卡当前未修复漏洞总数、高危及以上数量、本周新增漏洞数、本周修复数、平均修复时长。这五个数字回答的是当前状态怎么样。第二行是趋势图漏洞发现趋势按天堆叠柱状图和修复趋势按天折线图。回答的是最近是变好还是变坏。第三行是分布图按资产维度的漏洞分布按团队维度的修复负载按来源工具的漏洞占比。回答的是问题集中在哪。第四行是明细列表未修复漏洞的表格支持按严重级别、资产、团队过滤点击能跳到关联的资产详情。回答的是具体是哪些漏洞在拖后腿。这个分层逻辑不是拍脑袋定的。我见过很多看板把几十个图表不分主次全堆在上面结果打开后大脑一片空白根本不知道先看哪里。核心指标卡给结论趋势图给方向分布图给定位明细表给抓手一层往下钻一层。3.2 核心指标卡SQL与告警阈值设定指标卡的SQL相对简单但要写出正确结果有几个细节。以未修复高危漏洞数为例这个查询在MySQL中的写法是SELECT COUNT(*) FROM vulns WHERE status IN (open, fixing) AND severity high这里要注意status的取值。漏洞状态流转一般是open→fixing→fixed→ignored未修复应该包含open和fixing两个状态。很多看板只统计statusopen会导致同一漏洞进入修复流程后从看板上消失数字看起来很好看但实际风险还在那里。本周新增漏洞数的SQL要小心时间跨度的边界SELECT COUNT(*) FROM vulns WHERE discovered_at DATE_SUB(NOW(), INTERVAL 7 DAY)这个写法是可以的但如果你希望本周是从周一开始算而不是过去7天就要用YEARWEEK函数。我在踩了一次周一例会看板数字和上周五对不上的坑之后写了个统一的时间定义在变量里加了一个date_range下拉框默认本周可切到近7天、近30天、本季度所有图表共用同一个变量口径才不会乱。告警阈值方面我给高危未修复数设了三级阈值5个以下绿色5到10个黄色超过10个红色。这个阈值不是随便定的是通过回看历史三个月的数据算出来的——正常情况下高危未修复数在3到8之间波动5作为黄色预警线能捕捉到上升趋势10作为红色告警线则意味着某个环节大概率出故障了。3.3 趋势图与分布图的构建方法趋势图使用Grafana的Time series面板。MySQL数据源的表查询要按时间维度做分组聚合SELECT DATE_FORMAT(discovered_at, %Y-%m-%d) as day, severity, COUNT(*) as cnt FROM vulns WHERE discovered_at NOW() - INTERVAL 30 DAY GROUP BY day, severity ORDER BY dayGrafana的MySQL查询支持返回多个series这套SQL按天级别两个维度分组后Time series会自动把不同severity渲染成不同的柱子或线。图表上的图例名称如果显示的是highcritical之类的枚举值最好在SQL中用CASE WHEN转一下成中文标签否则领导看的时候会问critical是啥意思。SELECT DATE_FORMAT(discovered_at, %Y-%m-%d) as day, CASE severity WHEN critical THEN 严重 WHEN high THEN 高危 WHEN medium THEN 中危 ELSE 低危 END as severity, COUNT(*) as cnt FROM vulns WHERE discovered_at NOW() - INTERVAL 30 DAY GROUP BY day, severity ORDER BY day分布图我推荐用Bar gauge和Pie chart两个面板。资产管理分布用Bar gauge每条柱子代表一个资产柱子的长度是从高到低的漏洞总数柱子颜色按最高危级别映射这样一眼能看出哪个资产是最需要优先处理的。团队负载分布用Pie chart展示每个团队名下未修复漏洞的占比这个图在周会上非常有用谁的工作量最重一目了然。3.4 变量模板化的高级应用Grafana的变量功能是看板灵活性的关键。我在看板里定义了三个变量资产分组、团队、来源工具都是基于MySQL数据源做Query变量SELECT DISTINCT team FROM vulns WHERE team ! ORDER BY team变量定义之后所有面板的查询中都可以引用。以资产分布图为例子查询里加上AND team ${team}当下拉框选择安全组时资产分布图就只会展示安全组名下的资产漏洞情况。这个联动的价值在于同一个看板既可以用作全局总览也可以选中某个团队下钻查看不需要为每个团队单独建一块看板。变量也会有个小坑如果某个下拉框选了空值查询会变成AND team 导致部分数据被过滤掉。解决办法是在变量的Include All option中设置一个All值查询模板写成AND ($team all OR team $team)这样All的时候不做过滤选中特定团队时才过滤。3.5 安全测试看板与监控告警看板的区别最后说一个容易混淆的点。Grafana的模板库里有大量现成的服务器监控看板网络流量、CPU、磁盘全都有很多新手以为安全测试看板也能直接导入一个现成模板就行。实际上这两类看板的侧重点差别很大监控看板关注系统当前是否健康和稳定展示的是吞吐量、延迟、错误率安全测试看板关注风险暴露面是否在可控范围展示的是漏洞数量、修复时效、资产暴露情况。所以安全测试看板在导入模板时不能指望开箱即用数据模型就是你要自己先搭好的。我这套看板里的所有面板都是从头配的没有用任何现成模板。模板的参考价值在于学习布局和图表类型的搭配不在于直接套用。4. 告警与通知让安全风险主动找到人4.1 告警规则与阈值设计可视化看板解决的是被动查看的问题但真正让看板产生价值的是告警——风险出现时不用等人主动打开看板系统直接推消息到相关人。Grafana的告警规则支持基于面板查询配置。我在一个关键指标上配置了告警高危以上未修复漏洞数超过10条时触发。详细配置如下groups: - name: security_vulns rules: - alert: HighVulnCountTooHigh expr: mysql_vuln_high_count 10 for: 30m labels: severity: page team: security annotations: summary: 高危漏洞数超过10条当前值 {{ $value }} description: 当前未修复高危以上漏洞数量为 {{ $value }}已超过阈值10条请及时跟进处理。这个配置有几个设计考量。for: 30m表示持续30分钟才触发避免扫描器刚入库一批数据、团队还没来得及处理时告警反复横跳。曾经我设置的for: 5m结果每次扫描任务跑完都会产生一批告警早上一来看到几十条未读到最后大家连看都不看了。告警疲劳比没有告警更可怕。我这里用的是传统告警引擎的配置方式。如果你使用的是Grafana 9以上的版本建议使用统一告警引擎Unified Alerting直接在界面里配置规则可以按查询做区间判断也能用表达式组合多个条件灵活度更高。4.2 通知渠道接入与分级策略告警要发到合适的人手里才有效。我配置了三个通知渠道企业微信群机器人、邮件、钉钉。分组策略按严重级别区分critical级告警直接推群并安全负责人high级告警推群不人medium及以下只写进每周汇总邮件。分级方案在告警疲劳和感知度之间取了个平衡。企业微信机器人接入方法很成熟建一个群添加自定义机器人拿到Webhook地址在Grafana的通知渠道里新建一个Webhook类型URL填机器人的地址消息模板可自定义。模板中可以用{{ commonAnnotations.summary }}和{{ commonLabels.severity }}来引用告警信息。我实际用下来的消息模板长这样{ msgtype: text, text: { content: 【安全告警】{{ .CommonAnnotations.summary }}\n详情{{ .CommonAnnotations.description }}\n时间{{ .StartsAt }} } }有一点要提前注意Grafana的通知渠道和告警规则是在不同地方配置的很多新手把Webhook地址填完就以为完事了结果告警触发半天没人收到消息一查发现告警规则里没有关联这个通知渠道。在Grafana 9中通知策略Notification policies负责把告警路由到对应的联系人需要单独在Alerting→Contact points和Notification policies里各配一遍。4.3 告警静默与防抖实战告警静默是最容易被忽略但最实打实解决问题的一个功能。安全测试场景经常出现的情况夜间批量扫描任务跑完一批低危漏洞入库触发了一堆不痛不痒的告警某资产临时下线维护扫描器连续几次连不上导致检测结果异常某团队正在做集中修复高危数量在下降通道但还没降到阈值以下每天固定触发一次告警。我在实践中为这三种场景分别配置了静默规则夜间扫描时段凌晨0点到6点自动静默非critical告警对特定资产的告警设置48小时静默维护窗口通过匹配labels里的asset_ip对正在修复的团队设置了serialized告警即同一规则在6小时内只通知一次。Grafana的静默配置在Alerting→Silences中可以创建支持基于label匹配器精确匹配。创建时最关键的是设定合理的持续时间时间到了自动过期不用手动清理。我建议规则中务必带上team或asset_ip标签否则静默只能按整条规则全局匹配粒度太粗。5. 常见问题与排查技巧实录踩过的坑都在这5.1 数据时间与看板趋势对不上问题这是我最先遇到、也是咨询最多的问题MySQL里明明有数据但看板上的趋势图出现断层或数据点完全不显示。排查思路一Grafana默认使用浏览器的时区。如果浏览器时区是UTC而MySQL中存储的是北京时间那么discovered_at字段在查询时会被Grafana按UTC时间处理导致数据偏移8小时。体现在图表上就是当天数据少了几小时。解决办法是在Grafana的Preferences中把时区设置为Asia/Shanghai或者在数据源配置中指定time_zone参数。排查思路二时间字段格式不标准。MySQL的datetime类型没问题但如果你把时间存成字符串Grafana的MySQL插件能识别标准的YYYY-MM-DD HH:MM:SS格式识别不了2024-01-01T00:00:00Z这种ISO带时区的格式。我遇到过一次是因为某次导入脚本用了Python的isoformat()导致了这个问题。排查思路三查询条件中的时间范围写错。Grafana在面板中通过$__timeFrom和$__timeTo宏来限定时间范围如果SQL中忘了写WHERE discovered_at $__timeFrom就会出现在面板设定时间范围外查全部数据的风险数据量大时查询会很慢。-- 正确写法使用Grafana宏限定时间范围 SELECT DATE_FORMAT(discovered_at, %Y-%m-%d) as day, COUNT(*) as cnt FROM vulns WHERE discovered_at $__timeFrom AND discovered_at $__timeTo GROUP BY day5.2 JSON日志解析失败问题用Loki做半结构化数据解析时最常见的问题是日志推上去了但查不出来字段。这个情况十有八九是日志格式不是标准单行JSON。Promtail默认按行读取文件如果你的JSON报告是格式化过的、有换行缩进Promtail会把整个JSON按单行推上来吗不会它会按换行切成多行每一行都不完整LogQL的| json解析自然失败。解决方案有几个我最终用的是在采集端做预处理写了个小脚本把扫描器的结果先通过json.dumps压缩成单行再丢弃到一个output.json文件里Promtail只监听这个文件。这样改动只影响采集侧不影响原始报告存档。还有一个排查技巧Loki的Explore页面可以直接查看原始日志流。如果你发现某个日志流的content字段是一大段带缩进的JSON基本就能判定是格式问题。用{appsecurity-scanner} | vuln这样的查询手动确认解析效果是一条快捷的排查路径。5.3 MySQL查询性能问题随着漏洞数据不断累积vulns表到了百万行级别后看板的加载速度肉眼可见地变慢。我遇到的性能优化有几个关键动作第一个是索引。critical查询中经常使用到status、severity、discovered_at这三个字段的组合。我给vulns表创建了复合索引ALTER TABLE vulns ADD INDEX idx_status_severity_time (status, severity, discovered_at);这个索引直接让按状态级别时间范围过滤的查询从全表扫描变成索引range scan加载速度提升了大概10倍。第二个是聚合查询的优化。跟监控系统一样Grafana的趋势图是按小时甚至按分钟拉数据的存储层做了预聚合就快很多。我在MySQL里加了一张vulns_daily_summary表每天跑一个定时任务统计当天的漏洞增量、修复量、各级别数量趋势图直接查这张汇总表明细列表才去查大表。这样看板响应速度和底表数据量基本解耦了。第三个是做数据归档。超过一年的已修复漏洞定期移到vulns_archive表主表只保留活跃和近一年的数据查询性能才有保障。5.4 看板显示权限与多团队隔离当看板要开放给多个团队查看时权限隔离就成了绕不开的问题。安全团队希望所有团队都能看到自己名下的漏洞但又不希望A团队看到B团队的数据。Grafana的实现方式是通过组织和文件夹来配置权限。方案是创建安全运营组织Organization统一管理所有看板。按团队创建文件夹每个团队的看板放在对应的文件夹下。在文件夹的Permissions中添加对应团队并对齐角色为Viewer。核心敏感看板如全局总览只对管理员开放普通用户可以访问但看不到。如果同一块看板想让不同用户只看到自己团队的数据则要利用变量加数据权限控制。Grafana企业版有基于标签的权限控制但社区版没有。我的替代方案是按团队拆分变量为每个用户配置独立的首页看板URLURL中带上团队参数例如/d/vuln-board?var-teamsecurity。用户打开的就是过滤后的视图不需要进入编辑模式就能看到对应数据。这个方案不完美但胜在简单可落地。5.5 常见问题速查表问题现象可能原因解决方案趋势图时间偏移数小时时区不匹配Grafana Preferences设置为Asia/Shanghai趋势图数据断层时间字段为字符串格式统一改为datetime类型日志解析后无字段JSON多行格式预处理压缩为单行JSON看板加载很慢缺少复合索引添加(status, severity, discovered_at)索引告警反复触发for配置过短设置for为30m或更长消息没收到通知策略未关联在Alerting中检查联系点和路由部分图表显示No data变量空值过滤查询中使用($var all OR field $var)用户可以看到其他团队数据权限配置不对按文件夹设置团队权限最后分享一个个人体会看板的价值不取决于图表的数量取决于团队是否真正依赖它做日常决策。这套系统上线头一个月大家还是会习惯性地问最近漏洞情况怎么样到第二个月、第三个月他们已经开始主动盯着高危数量那个指标卡一旦变黄变红就来找安全团队问情况——这时候看板才算真正长在了流程里而不只是一个偶尔打开的展示屏。先从一个核心指标卡开始跑通数据链路再逐步增加图表和告警规则是这套方案落地时最稳妥的推进节奏。
返回列表