ARTICLE DETAIL

资讯详情

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

Zeek SumStats 框架详解:用观察流、Reducer 与 Epoch 构建可伸缩的网络统计与阈值检测

Zeek SumStats 框架详解:用观察流、Reducer 与 Epoch 构建可伸缩的网络统计与阈值检测 网络安全网络IDS【免费下载链接】zeekZeek is a powerful network analysis framework that is much different from the typical IDS you may know.项目地址https://gitcode.com/gh_mirrors/ze/zeek点击查看免费下载导读本文以 Zeek 的 Summary Statistics简称 SumStats框架为核心系统讲解如何将海量、无界的网络事件数据流压缩为简单的统计度量并在此基础上实现周期汇总与阈值告警。读完本文你将掌握 SumStats 的三大核心构件Observation、Reducer、SumStat及其完整类型接口、全部计算插件SUM、AVERAGE、TOPK 等的用法、阈值与多阈值序列的配置方法以及该框架在单机与集群部署下的内置透明性——这些能力足以支撑你独立编写诸如统计每分钟连接数检测扫描行为之类的 Zeek 脚本。框架定位为什么需要 SumStats测量网络流量是 Zeek 脚本中最常见的任务之一。对于有限的小样本如指定行数的 trace 文件直接用事件处理器累加变量即可但在真实部署中会遇到两个难题集群化多个 worker 进程同时嗅探流量数据分散与无界数据集流量永不停止内存无法无限增长。SumStats 框架正是为了在这两种条件下消费无界数据集并使其可被度量而设计的见 doc/frameworks/sumstats.rst。框架给出的核心承诺是同一份脚本在单进程 Zeek 与多 worker 集群上都能正确运行集群中流量负载均衡带来的复杂性由框架内置的集群透明机制自动处理脚本开发者可以忽略。整体处理流程Observation → Reducer → SumStatSumstats 的处理流程被拆分为三个环节官方文档与源码scripts/base/frameworks/sumstats/main.zeek中的定义完全对应Observation观察事件的某个方面被观察到后作为一条数据点喂入框架。一条观察属于一个任意命名的观察流observation stream并带有一个 Key该观察是关于谁的以及观察值本身。Reducer归约器对观察流施加各种计算如求和、均值、方差把无界观察集合压缩成更小的表示。结果在每个 Reducer 内部按 Key 分别收集因此需要控制被跟踪的 Key 总数避免内存失控。SumStat汇总统计最终定义把一个或多个 Reducer 在一个时间区间称为epoch即纪元/周期内汇总。在 SumStat 层可以配置阈值threshold与越过阈值时的回调也可以为每个 epoch 结束时提供按 Key 访问结果的回调。核心类型接口全解SumStats 全部定义在SumStats命名空间中module SumStats;见 main.zeek。以下类型、字段与默认值均以当前仓库源码为准。SumStats::Key —— 度量针对谁Key是一个 record表示正在被收集汇总结果的对象只有两个可选字段字段类型说明strstring optional非地址型度量的键或地址型度量的子键。例如按客户端 IP 统计成功 SSH 连接时IP 是 host 键而按 Host 头统计 HTTP 请求数则是非主机型度量同一 Host 值可能对应多个 IP此时用str作键。hostaddr optional该度量所适用的主机地址。源码中key2str()main.zeek#L285-L293把 Key 格式化为sumstats_key(host..., str...)这样的可读字符串方便打印或作为日志字段。SumStats::Observation —— 单条数据点Observation是单次观察加入的数据字段如下一次只能提供单个字段字段类型说明numcount optional计数类值。dbldouble optional浮点值。strstring optional字符串值。注意在observe()的实现中main.zeek#L492-L498如果观察只带字符串、没有num/dbl框架会把数值val回退为1.0——这正是SUM计算对字符串值统计数量的底层原因。SumStats::Reducer —— 计算挂在哪里Reducer是连接的枢纽它声明自己挂接在哪个观察流上、对该流施加哪些计算字段类型说明streamstringReducer 挂接的观察流标识符必填。applyset[SumStats::Calculation]要对数据点执行的计算集合必填。predfunction(key, obs): bool optional谓词函数按 Key 决定是否接受该数据点返回F则跳过。normalize_keyfunction(key): Key optional键归一化函数可用来聚合或归一化整个 Key。源码中create()会把 Reducer 登记到reducer_store按流 ID 索引的 Reducer 集合main.zeek#L246observe()正是通过reducer_store[id]找到所有订阅该流的 Reducer 逐一处理main.zeek#L439-L445。SumStats::Result / ResultTable / ResultVal —— 结果怎么存Result是table[string] of ResultVal按观察流 ID 索引多个 Reducer 的结果main.zeek#L78。ResultTable是table[Key] of Result按 Key 索引的 SumStats 结果总表main.zeek#L81。ResultVal是单个流的计算结果记录基类自带三个字段main.zeek#L63-L74字段类型/默认值说明begintime第一条观察被加入该结果的时间。endtime最后一条观察被加入的时间observe()每次都会更新见 main.zeek#L490。numcount default0收到的观察总数。其余字段average、max、min、sum、variance、std_dev、hll_unique、unique、last_elements、samples、topk等全部由计算插件通过redef record ResultVal 追加详见下文插件一节。SumStats::SumStat —— 汇总的最终定义SumStat记录把 Reducer 集合、epoch 周期、阈值机制与各回调组装在一起main.zeek#L91-L144字段类型说明namestringSumStat 的任意名称后续可据此引用。epochinterval周期区间。每个 epoch 结束时触发epoch_result回调同时重置结果——因此基于阈值的检测值应设为本 epoch 内预期出现的量级。设为0 secs即切换到手动 epoch需自行调用SumStats::next_epoch结束周期。reducersset[Reducer]该 SumStat 使用的 Reducer 集合。threshold_valfunction(key, result): double optional对每条观察调用、从Result中提取用于阈值比较的值。只要设置了threshold或threshold_series就必须提供create()中会校验并报错见 main.zeek#L394-L397。thresholddouble optional触发threshold_crossed回调的阈值。需要多个阈值时改用threshold_series。threshold_seriesvector of double optional阈值序列必须按升序排列因为某个阈值只有在前一个被越过之后才会被检查。threshold_crossedfunction(key, result) optional阈值被越过时调用的回调。越过条件threshold_val的返回值大于等于阈值且一个 epoch 内每个 Key 只触发第一次。epoch_resultfunction(ts, key, result) optional每个分析周期结束时接收各 Key 结果的回调对每个 Key 各调用一次。epoch_finishedfunction(ts) optional一个收集周期整体完成时调用ts为该周期开始时间。一个重要的集群提示main.zeek#L86-L90不要在回调中访问传入参数之外的任何全局状态因为集群中无法保证回调在哪个节点上执行。五个全局函数函数签名作用SumStats::createfunction(ss: SumStat)创建一个汇总统计main.zeek#L392-L437。内部完成校验阈值配置、登记到stats_store、初始化threshold_tracker、把每个 Reducer 注册进reducer_store、解析计算依赖、调用reset()并非手动 epoch 时调度finish_epoch事件。SumStats::observefunction(id: string, key: Key, obs: Observation)向观察流添加数据点应在脚本测得某个度量值时调用main.zeek#L439-L503。SumStats::request_keyfunction(ss_name: string, key: Key): Result动态请求某个 SumStat Key 的当前结果。文档与源码均强调应谨慎使用不能替代SumStat的回调机制且只能在when语句中作为异步函数使用见 non-cluster.zeek#L90-L100。SumStats::key2strfunction(key: Key): string把 Key 转成简单字符串的辅助函数。SumStats::next_epochfunction(ss_name: string): bool手动结束某 SumStat 的当前 epoch仅当该 SumStat 以 0 周期创建为手动 epoch 时可用。结束不是即时的——集群中需要节点间交换多条消息集群中必须在 manager 上调用worker 上调用无效。失败场景SumStat 不存在或未按手动 epoch 创建main.zeek#L272-L283。计算插件体系Calculation 枚举与 ResultVal 字段所有计算类型都以插件形式实现Calculation枚举基类只有一个PLACEHOLDER成员main.zeek#L10-L12每个插件通过redef enum Calculation 扩展枚举、redef record ResultVal 扩展结果字段并注册到register_observe_plugins钩子。所有插件由 plugins/load.zeek 默认加载。以SUM为例plugins/sum.zeek插件注册了观察函数rv$sum valsum.zeek#L39-L45通过init_resultval_hook初始化$sum并通过compose_resultvals_hook支持集群中两个结果的合并。全部插件及其语义、相关字段如下表计算插件文件语义涉及的 ResultVal 字段/默认值AVERAGEplugins/average.zeek数值的平均值average: double optionalHLL_UNIQUEplugins/hll_unique.zeek用 HyperLogLog 估计唯一值数量hll_unique: count default0、card: opaque of cardinality、hll_error_margin、hll_confidenceLASTplugins/last.zeek在队列中保留最近 X 条观察last_elements: Queue::Queue不要直接访问该字段应使用SumStats::get_last取回元素向量Reducer 侧配置num_last_elements: count default0MAXplugins/max.zeek最大值max: double optionalMINplugins/min.zeek最小值min: double optionalSAMPLEplugins/sample.zeek从观察流中均匀随机采样samples: vector of Observation default[]、sample_elements: count default0、num_samples: count default0Reducer 侧配置num_samplesSTD_DEVplugins/std-dev.zeek标准差std_dev: double default0.0SUMplugins/sum.zeek数值求和字符串值时统计字符串个数sum: double default0.0TOPKplugins/topk.zeek保留 top-k 列表topk: opaque of topk可传给内置函数取结果Reducer 侧配置topk_size: count default500UNIQUEplugins/unique.zeek精确统计唯一值数量unique: count default0、unique_vals: set[Observation]Reducer 侧配置unique_max: count optional最大存储的唯一值个数VARIANCEplugins/variance.zeek数值方差variance: double optional、prev_avg: double optional、var_s: double default0.0另有 Reducer 上由插件追加的配置字段hll_error_marginHLL 误差率默认0.01、hll_confidenceHLL 置信度默认0.95、num_last_elementsLAST 保留条数默认0、num_samplesSAMPLE 采样条数默认0、topk_sizeTOPK 列表长度默认500、unique_maxUNIQUE 上限。插件还支持依赖解析add_observe_plugin_dependency()main.zeek#L300-L305声明某个计算依赖另一个计算例如 VARIANCE 可能依赖 SUMcreate()时add_calc_deps()main.zeek#L370-L390递归展开依赖并去重最终按依赖顺序执行calc_store中的观察函数main.zeek#L499-L500。实战一统计周期内连接数以下完整示例来自 doc/frameworks/sumstats-countconns.zeek运行时需load base/frameworks/sumstatsload base/frameworks/sumstats event connection_established(c: connection) { # Make an observation! # This observation is global so the key is empty. # Each established connection counts as one so the observation is always 1. SumStats::observe(conn established, SumStats::Key(), SumStats::Observation($num1)); } event zeek_init() { # Create the reducer. # The reducer attaches to the conn established observation stream # and uses the summing calculation on the observations. local r1 SumStats::Reducer($streamconn established, $applyset(SumStats::SUM)); # Create the final sumstat. # We give it an arbitrary name and make it collect data every minute. # The reducer is then attached and a $epoch_result callback is given # to finally do something with the data collected. SumStats::create([$name counting connections, $epoch 1min, $reducers set(r1), $epoch_result(ts: time, key: SumStats::Key, result: SumStats::Result) { # This is the body of the callback that is called when a single # result has been collected. We are just printing the total number # of connections that were seen. The $sum field is provided as a # double type value so we need to use %f as the format specifier. print fmt(Number of connections established: %.0f, result[conn established]$sum); }]); }要点拆解观察每个connection_established事件调用一次observe()流 ID 为conn established因为是全局度量Key 为空SumStats::Key()每次计数 1所以Observation($num1)。归约Reducer 订阅该流并applyset(SumStats::SUM)对数值做累加。汇总epoch 1min每分钟触发一次epoch_result回调通过result[conn established]$sum取该流结果。由于$sum是double打印用%.0f格式符。官方文档给出的运行效果对 Zeek 测试集 PCAP 执行$ zeek -r workshop_2011_browse.trace sumstats-countconns.zeek Number of connections established: 6实战二用阈值检测扫描主机下面的玩具级扫描检测演示了阈值机制完整源码见 doc/frameworks/sumstats-toy-scan.zeekload base/frameworks/sumstats # We use the connection_attempt event to limit our observations to those # which were attempted and not successful. event connection_attempt(c: connection) { # Make an observation! # This observation is about the host attempting the connection. # Each established connection counts as one so the observation is always 1. SumStats::observe(conn attempted, SumStats::Key($hostc$id$orig_h), SumStats::Observation($num1)); } event zeek_init() { # Create the reducer. # The reducer attaches to the conn attempted observation stream # and uses the summing calculation on the observations. Keep # in mind that there will be one result per key (connection originator). local r1 SumStats::Reducer($streamconn attempted, $applyset(SumStats::SUM)); # Create the final sumstat. # This is slightly different from the last example since were providing # a callback to calculate a value to check against the threshold with # $threshold_val. The actual threshold itself is provided with $threshold. # Another callback is provided for when a key crosses the threshold. SumStats::create([$name finding scanners, $epoch 5min, $reducers set(r1), # Provide a threshold. $threshold 5.0, # Provide a callback to calculate a value from the result # to check against the threshold field. $threshold_val(key: SumStats::Key, result: SumStats::Result) { return result[conn attempted]$sum; }, # Provide a callback for when a key crosses the threshold. $threshold_crossed(key: SumStats::Key, result: SumStats::Result) { print fmt(%s attempted %.0f or more connections, key$host, result[conn attempted]$sum); }]); }本示例与上一例的三个关键差异Key 携带主机SumStats::Key($hostc$id$orig_h)每个发起连接的主机成为一个独立 Key结果按 Key 分开统计。阈值三元组$threshold 5.0给出阈值$threshold_val从Result中提取比较值这里就是求和值$threshold_crossed在 Key 越过阈值时被回调。触发语义阈值越过判断发生在每次观察插入时check_thresholds()main.zeek#L507-L549会先检查threshold_tracker确保同一 epoch 内每个 Key 只触发一次源码注释见 main.zeek#L129-L132threshold_crossed()负责递增跟踪计数并调用回调main.zeek#L551-L570。官方文档中的运行效果对含 nmap 主机的 PCAP 执行$ zeek -r nmap-vsn.trace sumstats-toy-scan.zeek 192.168.1.71 attempted 5 or more connections多阈值序列若需多个告警档位改用threshold_series。注意其约束main.zeek#L123-L127阈值必须按升序排列实现上get_threshold_index()main.zeek#L226-L234记录每个 Key 已越过的阈值偏移只有前一个阈值被越过后才检查下一个main.zeek#L539-L546因此阈值序列天然形成阶梯式告警。手动 epoch把$epoch设为0 secs即进入手动模式create()不再调度finish_epoch事件main.zeek#L434-L436脚本可在任意时刻例如某个业务事件发生时调用SumStats::next_epoch(sumstat 名称)触发周期收尾集群中该调用必须在 manager 上执行。底层机制create 与 observe 的源码级流程create() 做了什么main.zeek#L392-L437校验设置了threshold/threshold_series但未提供threshold_val时报错。将 SumStat 存入stats_store按 name 索引。若配置了阈值初始化该 SumStat 的threshold_tracker。遍历 Reducer记录其所属 SumStat 名内部字段ssname解析计算依赖得到有序的calc_funcs并把 Reducer 加入reducer_store按 stream 索引。调用reset()清空result_store与threshold_tracker。若 epoch 非 0schedule ss$epoch { SumStats::finish_epoch(ss) }周期性触发。observe() 做了什么main.zeek#L439-L503流 ID 不在reducer_store中则直接返回无订阅者零开销。对每个订阅该流的 Reducer先用normalize_key若配置归一化 Key再跑pred谓词返回F则跳过。命中阈值且无epoch_result回调时跳过后续计数——源码注释说明这是为了避免在度量唯一性时产生状态管理问题而做的优化main.zeek#L456-L474。初始化该 Key/流的结果ResultValinit_resultval()会调用init_resultval_hook让各插件初始化自己的字段见 main.zeek#L313-L318递增$num、更新$end。将数值num或dbl缺省回退 1.0依次喂给calc_funcs对应的插件观察函数。调用data_added()在非集群实现中触发check_thresholds()与threshold_crossed()non-cluster.zeek#L84-L88。epoch 收尾与集群合并单机non-cluster.zeekfinish_epoch事件驱动do_finish_epoch()按每批 50 个 Key 调度process_epoch_result事件逐批调用epoch_result回调全部处理完后调用epoch_finished随后reset()并调度下一周期zeek_done()时以priority10处理遗留的自动 epoch。集群cluster.zeek框架通过事件与消息在 worker 与 manager 之间同步数据compose_resultvals()main.zeek#L320-L331与compose_results()main.zeek#L333-L351把多个 worker 的ResultVal/Result合并取最早的begin、最晚的end、累加num并逐个调用插件的compose_resultvals_hook合并插件字段。加载逻辑在load.zeek 中按Cluster::is_enabled()分支选择 cluster 或 non-cluster 实现这也是脚本无需关心集群差异的机制来源。使用注意事项总结Observation 一次只填一个字段num、dbl、str三选一只给字符串时数值按 1.0 处理。控制 Key 数量Reducer 按 Key 分开收集结果Key 过多会耗尽内存必要时用normalize_key聚合或pred过滤。阈值必须配threshold_val否则create()直接报错。threshold_series必须升序后一档阈值依赖前一档被越过。回调内不要访问外部全局状态集群环境下回调执行节点不确定。手动 epochnext_epoch仅对 epoch 为 0 的 SumStat 有效且集群中只能在 manager 调用。request_key慎用只应作为when语句中的异步函数使用不要用它替代回调机制。参考资源框架 API 文档本文主体来源doc/scripts/base/frameworks/sumstats/main.zeek.rst框架用户指南与术语、示例说明doc/frameworks/sumstats.rst核心实现scripts/base/frameworks/sumstats/main.zeek集群/单机支持scripts/base/frameworks/sumstats/cluster.zeek、scripts/base/frameworks/sumstats/non-cluster.zeek插件目录scripts/base/frameworks/sumstats/plugins/load.zeek完整可运行示例doc/frameworks/sumstats-countconns.zeek、doc/frameworks/sumstats-toy-scan.zeek赞分享网络安全网络IDS【免费下载链接】zeekZeek is a powerful network analysis framework that is much different from the typical IDS you may know.项目地址https://gitcode.com/gh_mirrors/ze/zeek点击查看免费下载相关推荐Zeek SumStats 框架实战指南面向集群与无界流量数据的流式统计与阈值检测Zeek SumStats 框架实战指南面向集群与无界流量数据的流式统计与阈值检测 本篇技术指南围绕 Zeek 内置的 SumStatsSummary St网络安全网络IDSbigdata_analyse 数据处理性能优化10个提升效率的技巧bigdata_analyse 数据处理性能优化10个提升效率的技巧 在大数据分析项目中数据处理性能优化是提升整体效率的关键环节。无论你是在处理百万级的用户网络安全网络IDSZeek SumStats 框架 MIN 计算插件深入解析用 SumStats::MIN 追踪数值流的最小值Zeek SumStats 框架 MIN 计算插件深入解析用 SumStats::MIN 追踪数值流的最小值 SumStats::MIN 是 Zeek 汇总统网络安全网络IDS上一篇OpenStar基于 OpenResty 的高性能 WAF 实战指南——规则体系、防护模块与 CC 攻击防护算法解析下一篇5步快速上手终极Total War模组制作工具RPFM完全指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表