深入解读Zeek脚本架构与扩展机制

7 人参与

在网络流量监控的细枝末节里,Zeek 之所以被视为“可编程的 IDS”,根本原因在于它的脚本层几乎可以把协议解析、状态机、日志输出全部重新拼装。一个 Zeek 脚本实际上是一段声明式的 DSL,核心由 eventredefmodule 三类构件交织而成。脚本的加载顺序遵循 @load 指令的先后,且每一次 zeek_init() 触发的时机恰好在所有全局变量初始化完毕后,这点在实现跨协议关联时尤为关键。

模块化与命名空间

Zeek 通过 module 将功能划分为独立的命名空间,类似于其他语言的包。模块内部可以 export 自定义的记录或全局变量,外部则通过 module::symbol 访问。这样做的好处是,多个脚本可以共享同一个数据结构而不会产生冲突。例如,module Files 中定义的 Info 记录可以在任意脚本里通过 Files::Info 引用,从而实现统一的文件元信息收集

事件驱动的扩展点

几乎所有的协议解析都映射为事件,例如 dns_requesthttp_requestfile_new 等。开发者只需要声明对应的 event 处理函数,Zeek 引擎会在捕获到相应的网络包时自动调度。值得注意的是,事件的优先级可以通过 &priority 修饰符微调,这在需要先行过滤噪声再做深度分析的场景里非常实用。举个例子,想在 HTTP 请求到达前先判断 Host 是否在白名单里,只需在 event http_request(c: connection, method: string, host: string) 上加上 &priority=5,确保它在默认日志写入之前执行。

重新定义(redef)机制

Zeek 为每个全局变量提供了 redef 关键字,允许在加载脚本的任何阶段覆盖默认值。这一点在调优性能或适配特定网络环境时显得尤为灵活。比如把默认的 Log::default_rotation_interval 从 1 天改为 6 小时,只需在自定义脚本里写 redef Log::default_rotation_interval = 6hrs;,无需触碰核心代码。

自定义日志过滤器

Zeek 的日志系统本身支持基于布尔表达式的过滤器。通过 Log::remove_default_filter 删除默认过滤,再使用 Log::add_filter 添加自定义 Log::Filter,可以把只关心的记录挑出来写入独立文件。这样一来,原本几百 GB 的原始日志在过滤后可能只剩几 GB,后端存储压力骤降。

# 示例:最小化 DNS 日志,仅保留外部查询
@load base/protocols/dns
redef Analyzer::disable_all = T
event zeek_init() {
    Analyzer::enable_analyzer(Analyzer::ANALYZER_DNS)
    Log::remove_default_filter(DNS::LOG)
    local filter: Log::Filter = [$name="external_dns",
                                $path="/var/log/zeek/dns_external.log",
                                $pred=|rec: DNS::Info| { return !Site::is_local_name(rec?$query); } ]
    Log::add_filter(DNS::LOG, filter)
}

上述代码展示了从关闭所有分析器、仅开启 DNS、再通过自定义过滤器剔除内部域名的完整链路。它说明了 Zeek 脚本的“即插即用”特性:只要把这段文件放进 policy 目录,重启 Zeek 后立刻生效,无需重新编译二进制。

扩展到外部系统

在实际运维中,常常需要把 Zeek 捕获的事件同步到 SIEM、威胁情报平台或自研的响应系统。Zeek 提供了 @load scripts/policy/frameworks/intel/ 以及 Broker 通道,开发者可以在 event intel_match 或自定义的 event zeek_done 中调用外部 API。这样一来,文件哈希、异常域名甚至自定义的攻击指纹都能实现“实时推送”。

参与讨论

7 条评论
  • 飞翔的熊猫

    Zeek的脚本架构真够灵活的,以前用Snort要改C代码,这个直接写脚本就能定制协议解析了。

    回复
  • 外滩风云

    module命名空间这个设计挺那啥的,能避免冲突,写大项目的时候应该很有用。

    回复
  • RustlingDarkness

    事件优先级能调这个细节不错,做流量清洗的时候可以先把白名单放前面过滤。

    回复
  • 熊孩子

    redef改全局变量确实方便,部署的时候根据环境调参数不用动源码。

    回复
  • 紫晶心语

    日志过滤器示例很实用,我们这边日志量太大,回头试试这个只保留外部DNS查询的方法。

    回复
  • 碧海潮生曲

    想问下Broker通道推送事件到外部系统,延迟大概在什么水平?

    回复
  • 彗星尾巴

    之前配过类似的过滤,内部域名太多,脚本没写好差点把正常业务日志也滤掉了,折腾半天。

    回复