☰
Zeek Notice 框架完全指南:从检测上报到本地告警策略的定制实践
2026/10/8 8:17:27 网站建设 项目流程
  • 网络安全
  • 网络
  • IDS

【免费下载链接】zeek

Zeek 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 的 Notice(通知)框架展开,它是 Zeek 将"检测"与"处置"解耦的核心机制——引擎脚本只负责标记可能有趣或异常的活动,而由本地站点通过Notice::policy钩子决定哪些通知真正需要行动(记录日志、发送邮件、触发告警等)。读完本文,你将掌握如何编写本地 notice policy、利用快捷配置集批量控制通知行为、正确构造Notice::Info记录并借助identifier实现自动去重抑制,以及如何在集群环境下正确使用通知框架。

1. 框架定位:为什么 Zeek 需要"Notice"

Zeek 自带大量分析脚本,覆盖各类协议与异常行为检测。它们可以检测出数量众多的"潜在感兴趣"事件,但没有一个脚本会自行判断事件的严重程度——脚本只负责把某种情形标记为"可能有趣",是否可操作则由本地配置决定(见 doc/scripts/base/frameworks/notice/index.rst)。

这种"检测与上报解耦"的设计源于一个现实:不同站点对"什么是攻击、什么是入侵"的定义差异极大,某个站点视为恶意的活动在另一个站点可能完全可接受。Zeek 不在发行版中内置"什么是坏活动"的假设,因此把"含义判定"留给每个站点。框架的这一设计哲学也直接体现在核心脚本的文档注释中(scripts/base/frameworks/notice/main.zeek)。

当某个分析脚本看到潜在有趣的事件时,它调用全局函数NOTICE并传入一个Notice::Info记录。Notice::Info中的note字段(Notice::Type枚举)反映了所看到活动的种类,通常还会附带更多关于场景的上下文。

2. 框架组成与加载方式

通知框架由 scripts/base/frameworks/notice/ 目录下的一组脚本组成,入口是load.zeek:

@load ./main # 核心框架:Notice::Info、Action、policy 钩子、抑制机制 @load ./weird # Weird 活动处理:默认动作表与事件处理 @load ./actions/email_admin @load ./actions/page @load ./actions/add-geodata @load ./actions/pp-alarms

按官方说明,加载这些动作脚本几乎没有额外开销,所以默认全部加载。各组成部分的职责如下:

脚本职责
main.zeek框架本体:定义Notice::Type、Notice::Action、Notice::Info记录、Notice::policy钩子以及自动抑制机制
weird.zeek为 Zeek 事件引擎产生的"weird activity"事件提供默认动作集合
actions/email_admin.zeek新增ACTION_EMAIL_ADMIN动作,将通知发送给与源/目的地址所属地址空间对应的管理员
actions/page.zeek提供寻呼(pager)邮件地址配置,ACTION_PAGE动作会把通知发送到该地址
actions/add-geodata.zeek为通知中的"远程"主机附加地理位置数据(remote_location字段)
actions/pp-alarms.zeek以易读的排版定期邮件发送notice_alarm.log的"美化版",激活后取代默认的原始日志摘要邮件

关于"weird activity"的定义:指异常或例外活动,可能意味着畸形连接、不符合特定协议规范的流量、故障或配置错误的硬件,甚至是攻击者试图躲避/迷惑传感器。没有上下文时很难判断某类 weird 活动是否值得关注,weird.zeek正是为用户提供了一个起步点(见 doc/scripts/base/frameworks/notice/weird.zeek)。

3. Notice::Info 记录:通知的数据载体

Notice::Info是通知框架的核心数据类型(定义见 scripts/base/frameworks/notice/main.zeek)。当需要将通知写入notice.log时,日志流以该记录作为列定义(Log::create_stream(Notice::LOG, ...),路径为notice;另有告警流ALARM_LOG,路径为notice_alarm)。

框架在apply_policy()(main.zeek)中会自动补全字段:

  • 未设置ts时填入network_time();
  • 提供了$conn时自动填充$id(四元组)与$uid;
  • 提供了$id时自动填充$src(orig_h)、$dst(resp_h)、$p(resp_p);
  • 提供了$p时自动推导$proto;
  • 提供$f(fa_file)时通过populate_file_info()自动填充$fuid、$file_mime_type、$file_desc。

3.1 常用字段速查

字段说明
note必填,枚举值,表示通知类型
msg人类可读的消息,提供该通知实例的更多信息
sub人类可读的子消息,常被用于在Notice::policy中做模式匹配
conn若通知与某连接相关且手头有连接记录,可在此给出;id/src等字段会自动填充
id若手头有conn_id记录,可在此给出;src等会自动填充
src若通知描述的是单台主机的属性,通常只需填此字段
n通常表示一个数字(如计数或状态码),最常用于Notice::policy中的数值判断
identifier该通知的唯一标识字符串,用于自动抑制(详见第 6 节)
suppress_for该通知应被抑制的时长,默认取default_suppression_interval(默认1hrs)
actions已应用到该通知的动作集合(Notice::ActionSet),默认空集
email_dest通知邮件发送地址集合,默认空集
email_body_sections字符串向量,发送邮件时逐字包含进邮件正文(可扩展邮件内容)
email_delay_tokens字符串 token 集合,可让内置邮件功能延迟发送直到 token 被移除或达到max_email_delay(默认15secs)

3.2 动作类型(Notice::Action)

main.zeek定义了如下动作枚举:

动作说明
ACTION_NONE不采取任何动作
ACTION_LOG写入Notice::LOG日志流(即notice.log)
ACTION_EMAIL将通知以邮件发送到Notice::Info$email_dest中的地址
ACTION_ALARM写入Notice::ALARM_LOG(notice_alarm.log),该日志流每小时轮转并将内容以邮件发送给$email_dest中的地址
ACTION_DROP触发 drop 动作,具体行为取决于已加载的策略脚本(如 NetControl)

加载 actions/page.zeek 后还会新增ACTION_PAGE(发送到Notice::mail_page_dest寻呼地址);加载 actions/email_admin.zeek 后新增ACTION_EMAIL_ADMIN(按Site::get_emails基于源/目的地址解析管理员邮箱);加载 actions/add-geodata.zeek 后新增ACTION_ADD_GEODATA(为"远程"主机附加remote_location地理信息,要求已定义Site::local_nets)。

4. 编写 Notice Policy:本地处置策略

Notice::policy钩子是框架提供给用户的"处置决策点"。钩子可视为多体函数,用法类似事件处理,区别在于不经过事件队列。用户可以直接修改传入的Notice::Info记录字段来改变通知处理流程。

4.1 入门示例:为 SSH 密码猜测告警发送邮件

完整参考文档见 doc/frameworks/notice.rst。下面示例让 Zeek 对所有SSH::Password_Guessing类型通知、且猜测目标为192.168.56.103时发送邮件:

@load protocols/ssh/detect-bruteforcing redef SSH::password_guesses_limit=10; hook Notice::policy(n: Notice::Info) { if ( n$note == SSH::Password_Guessing && /192\.168\.56\.103/ in n$sub ) { add n$actions[Notice::ACTION_EMAIL]; n$email_dest = "ssh_alerts@example.net"; } }

离线回放验证(ssh/sshguess.pcap位于仓库测试资源中,也可改用你自己的 pcap):

$ zeek -C -r ssh/sshguess.pcap notice_ssh_guesser.zeek $ cat notice.log

输出中可以看到notice.log的一行典型记录,其中actions列为Notice::ACTION_EMAIL,Notice::ACTION_LOG,email-dest为ssh_alerts@example.net,suppress_for为3600.000000(即默认 1 小时抑制)。

注意:SSH::Password_Guessing通知仅在 Zeek 启发式检测到失败登录时才会产生。该通知由 scripts/policy/protocols/ssh/detect-bruteforcing.zeek 基于 SumStats 统计触发:当某源主机在guessing_timeout(默认 30 分钟)内累计失败登录数超过password_guesses_limit(默认 30,可 redef)时,调用NOTICE(...)上报(见 detect-bruteforcing.zeek)。

4.2 钩子优先级与中断

钩子可以像事件一样设置优先级,默认优先级为 0,数值越大越先执行:

hook Notice::policy(n: Notice::Info) &priority=5 { # Insert your code here. }

钩子还可以用break关键字中止后续钩子体的执行。当你想完全抢占较低优先级Notice::policy钩子的处理时,这一机制尤其有用。框架自带的默认策略钩子优先级为&priority=10(见 main.zeek),其内部首先检查ignored_types并用break直接短路处理。

5. 策略快捷配置:集合与表驱动的批量控制

虽然Notice::policy提供了极大的灵活性和可配置性,但很多场景并不需要完整表达力。框架内置了一个默认Notice::policy钩子体,以Notice::Type枚举为索引的集合/表向用户提供快捷配置(实现见 main.zeek)。

变量说明
Notice::ignored_types加入该集合的类型会被直接忽略,不应用任何其他动作(连ACTION_LOG也不加)
Notice::emailed_types加入该集合的类型会被应用ACTION_EMAIL
Notice::alarmed_types加入该集合的类型会被应用ACTION_ALARM
Notice::not_suppressed_types加入该集合的类型不再执行常规抑制。生产环境慎用——可能导致被处理的通知数量剧增
Notice::type_suppression_intervals以Notice::Type为索引、值为 interval 的表,可为整个类型便捷地扩展默认抑制间隔,而无需手写整个Notice::policy条目并设置$suppress_for字段

注意一个隐含行为:记录日志(ACTION_LOG)是默认动作,由默认钩子无条件添加;如果某个类型进入ignored_types才会整体跳过。因此即使某个通知类型只出现在emailed_types中,它同时也会被写入notice.log,除非你在自定义钩子中移除该动作。

6. 如何正确地上报一条 Notice

脚本应当对任何用户可能希望被通知或采取行动的事件上报通知。通知的产生流程见全局函数NOTICE(main.zeek):

function NOTICE(n: Notice::Info) { if ( Notice::is_being_suppressed(n) ) return; # Fill out fields that might be empty and do the policy processing. Notice::apply_policy(n); # Generate the notice event with the notice. hook Notice::notice(n); }

即:先检查是否处于抑制期(抑制则直接返回),然后apply_policy补全默认字段并执行全部Notice::policy钩子,最后触发Notice::notice钩子完成邮件发送、日志写入与抑制登记。

6.1 上报示例:SSH 暴力破解检测

scripts/policy/protocols/ssh/detect-bruteforcing.zeek 中的真实上报代码:

NOTICE(Notice::Info($note=Password_Guessing, $msg=fmt("%s appears to be guessing SSH passwords (seen in %d connections).", key$host, r$num), $sub=sub_msg, $src=key$host, $identifier=cat(key$host)));

这个例子展示了上报的最佳实践:由于该通知描述的是"一台主机"的属性(扫描/猜测主机),而非某个连接,因此只填$src而不给连接记录或conn_id;同时通过$identifier=cat(key$host)让相同源主机的通知可被去重抑制。

6.2 字段选择的权衡

编写上报脚本时,应思考"这条通知代表什么、该提供哪些数据"。经验法则:

  • 如果通知代表多连接汇聚后的主机属性(如扫描主机),最合理的做法是只填$src,不要携带连接信息;
  • 如果通知代表某个连接的属性(如一次明显的 SSH 登录),则应基于手头数据填$conn或$id;
  • 如果通知代表文件属性(如下载到已知恶意文件),则应填$f或$fuid/$file_mime_type等文件字段(框架提供Notice::create_file_info()/populate_file_info()辅助填充)。

填入不完整或多余的数据会给后续分析造成困扰。例如,当检测到 SSL 服务器证书即将过期时,如果强行带上完整连接信息,日志会非常混乱——因为发现证书的连接只是"侧题"。在很多场景下,一条事件可能需要拆成两条甚至更多条通知(一条针对过期证书的检测,另一条针对客户端不顾过期证书继续连接的行为)。

7. 自动抑制机制:用 identifier 去重

通知框架支持自动抑制,前提是上报脚本的作者告诉框架如何识别"本质上相同"的通知。实现方式是利用Notice::Info$identifier可选字段——一个简单字符串。当两条通知的$identifier与$note都相同时,框架认为它们是同一件事,可在可配置的时段内抑制重复上报。

如果省略identifier,则完全不会发生抑制(框架无法识别重复)。如果确实不存在任何重复可能,这是完全合法的用法。

7.1 真实案例:SSL 证书校验失败

scripts/policy/protocols/ssl/validate-certs.zeek 中对会话协商中证书校验失败的上报:

NOTICE(Notice::Info($note=Invalid_Server_Cert, $msg=message, $sub=c$ssl$cert_chain[0]$x509$certificate$subject, $conn=c, $fuid=c$ssl$cert_chain[0]$fuid, $identifier=cat(c$id$resp_h,c$id$resp_p,hash,c$ssl$validation_code)));

这里identifier由响应方 IP、响应方端口、证书哈希、校验结果码四段拼接而成。选这些字段的原因是:同一主机不同端口可能使用不同证书;证书可能因不同原因校验失败;同 IP+端口组合也可能通过 SNI 提供多张服务器证书(因此加上证书哈希)。结果是:只有当四项数据全部匹配时,该通知才在默认抑制期内不再重复上报。

7.2 抑制的底层实现

框架维护一张内部抑制表(main.zeek):

global suppressing: table[Type, string] of time = {} &create_expire=0secs &expire_func=per_notice_suppression_interval;

该表以[Type, identifier]为键、抑制截止时间为值,并通过&expire_func实现逐条目到期。用户若不想抑制,可把suppress_for设为0secs,或在Notice::policy钩子中直接删除identifier字段的值。

8. 扩展框架:邮件配置与内容定制

8.1 收件人配置

设置Notice::mail_dest后,带邮件动作的通知会被发送到该地址。需要更细的控制时,可在Notice::policy中直接改写$email_dest字段。例如下面的钩子会生成三封独立邮件(集合中有三个元素):

hook Notice::policy(n: Notice::Info) { n$email_dest = set( "snow.white@example.net", "doc@example.net", "happy@example.net,sleepy@example.net,bashful@example.net" ); }

此外框架还提供一批邮件相关配置(main.zeek):

配置默认值说明
Notice::sendmail/usr/sbin/sendmail本地 sendmail 程序(会被 ZeekControl 的 SendMail 选项覆盖)
Notice::mail_dest""邮件默认收件地址(会被 ZeekControl MailTo 或$email_dest覆盖)
Notice::mail_from"Zeek <zeek@localhost>"发件地址(被 ZeekControl MailFrom 覆盖)
Notice::reply_to""回复地址
Notice::mail_subject_prefix"[Zeek]"所有外发邮件主题前缀(被 ZeekControl MailSubjectPrefix 覆盖)
Notice::max_email_delay15secs插件可延迟发送邮件的最大时长

8.2 扩展邮件正文

Notice::Info中有一个字符串向量email_body_sections,Zeek 发送邮件时会逐字包含其内容。下面示例在通知涉及 HTTP 连接时把 Host 头加入邮件正文:

hook Notice::policy(n: Notice::Info) { if ( n?$conn && n$conn?$http && n$conn$http?$host ) n$email_body_sections[|n$email_body_sections|] = fmt("HTTP host header: %s", n$conn$http$host); }

8.3 告警邮件的美化输出

加载 actions/pp-alarms.zeek 后,激活pretty_print_alarms = T(默认即激活)会用可读排版替代默认的原始日志摘要邮件。可配置项包括:mail_dest_pretty_printed(美化报告收件地址,默认回落到mail_dest)、flag_nets(命中该网络集合的条目会以>标记,方便邮件客户端高亮)、以及可重写的pretty_print_alarm()渲染函数。实现上它以Log::WRITER_NONE过滤器替换默认的alarm-mail过滤器,并注册pp_postprocessor在轮转时发送邮件。

9. Weird 日志:异常活动的统一入口

Zeek 检测到的大量"weird"活动会触发相应事件通知脚本层,包括conn_weird、flow_weird、net_weird、file_weird等不同粒度。构建在通知框架之上的 weird.zeek 实现了将这些 weird 事件导入常规通知框架处理链的事件处理器。

9.1 动作表:Weird::actions

Weird::actions表(weird.zeek)为各种 weird 类型定义了默认动作,默认值为ACTION_LOG。动作枚举包括:

  • ACTION_IGNORE:忽略
  • ACTION_LOG/ACTION_LOG_ONCE/ACTION_LOG_PER_CONN/ACTION_LOG_PER_ORIG:记录日志(每次都记 / 只记一次 / 每连接记一次 / 每源主机记一次)
  • ACTION_NOTICE/ACTION_NOTICE_ONCE/ACTION_NOTICE_PER_CONN/ACTION_NOTICE_PER_ORIG:生成通知(同样有次数/连接/源主机三种去重粒度)

典型条目如FIN_storm = ACTION_NOTICE_PER_ORIG、successful_RPC_reply_to_invalid_request = ACTION_NOTICE_PER_ORIG、bad_TCP_checksum = ACTION_LOG_PER_ORIG,而unsolicited_SYN_response、FIN_after_reset、spontaneous_FIN、spontaneous_RST等被设为ACTION_IGNORE。

9.2 处理链与动态过滤

核心处理函数do_weird()(weird.zeek)依次执行:查动作表 → 计算 identifier(缺省用连接四元组)→ 检查weird_ignore与ignore_hosts过滤 → 按"限流动作"去重(did_log/did_notice状态集,过期时间 1 天)→ 对通知类动作构造Notice::Info($note=Activity、$msg=weird 名)并调用NOTICE→ 登记weird_ignore(&create_expire=10min,用于临时抑制高频 weird)→ 写入Weird::LOG流(weird.log)。

动态过滤配置:

# 忽略特定主机上特定 weird option Weird::ignore_hosts: set[addr, string] = {}; # 不忽略重复:例如想持续跟踪集群内校验和错误 option Weird::weird_do_not_ignore_repeats = { "bad_IP_checksum", "bad_TCP_checksum", "bad_UDP_checksum", "bad_ICMP_checksum", };

需要理解的是:weirds 通常不代表安全相关活动——它们只是"怪异"的事件,比如异常的 TCP 状态机违例、意外的 HTTP 头组合、超出 RFC 规范的 DNS 消息属性等。不要把 weirds 当作 IDS 意义上的可操作检测,但它们可能为安全事件调查提供有价值的额外线索。weird 对应的通知类型为Weird::Activity。

10. 集群环境下的注意事项

在集群中运行时,前面的大部分机制保持不变:通知在产生它的节点(通常是 worker)上生成、执行Notice::policy钩子并运行相应动作。对用户/开发者尤其重要的是:运行通知动作所需的任何文件或访问权限必须对相应节点可用。

管理节点(manager)的角色是接收并分发抑制信息,避免重复通知被生成。需要留意:

  1. 这种同步天然带有一定延迟,因此快速连续产生的通知仍可能是重复的——此时动作也会执行多次(每个产生通知的节点各执行一次);
  2. worker 向 manager 上报抑制信息默认采用批量发送以降低集群事件洪泛:suppression_batch_period默认10msec、suppression_batch_max_size默认 50 条,设为0可分别禁用对应维度的批量逻辑(相关实现见 main.zeek 及gbatch_send_and_reset())。

11. 总结

Notice 框架是 Zeek 定制化运维中最常打交道的子系统:分析脚本只管"上报事实",本地站点通过Notice::policy钩子与快捷配置表决定"如何处置"。掌握它意味着你可以:

  • 用add n$actions[...]为特定通知类型追加邮件/告警/寻呼等动作;
  • 用ignored_types、emailed_types、alarmed_types、type_suppression_intervals做批量策略;
  • 编写上报脚本时用$identifier精确表达"什么是重复",控制抑制粒度;
  • 利用email_body_sections、pp-alarms、add-geodata 等扩展把通知变成符合站点流程的告警制品。

建议结合 doc/frameworks/notice.rst(完整用户指南)与 scripts/base/frameworks/notice/main.zeek、weird.zeek 源码阅读,并在testing/btest/下搜索 notice 相关测试用例验证实际输出格式。

  • 网络安全
  • 网络
  • IDS

【免费下载链接】zeek

Zeek is a powerful network analysis framework that is much different from the typical IDS you may know.

项目地址:https://gitcode.com/gh_mirrors/ze/zeek
点击查看免费下载
上一篇:swagger-codegen 模型文档解析:以 jersey1 生成的 ReadOnlyFirst 为例读懂只读属性约定
下一篇:substrate 依赖的 AWS EventStream 协议模块解析:aws-sdk-go-v2 eventstream 消息格式、编解码实现与版本演进

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询