- 网络安全
- 网络
- IDS
【免费下载链接】zeek
Zeek is a powerful network analysis framework that is much different from the typical IDS you may know.
Zeek 的 Input Framework(输入框架)提供了一条把"此前已存储的数据"重新引入 Zeek 运行环境的标准化通道,既可以把数据读入 Zeek 表(table),也可以直接转化为事件流(event stream)交给脚本处理,其模块化的 reader 架构支持从文件、数据库等多种数据源摄取数据。读完本文,你将掌握Input::add_table/Input::add_event的完整用法、三种读取模式(MANUAL/REREAD/STREAM)的差异、谓词过滤与变更事件机制,以及 ASCII、Raw、Binary、Benchmark、SQLite 等内置 reader 的选型与实战配置。
本文以 input 框架包索引文档 和同主题的完整教程 Input Framework 为骨架,结合 框架脚本实现 与 btest 测试用例 展开。需要说明的是,Input Framework 本身对 Zeek 的集群架构并无感知,任何集群节点上都可直接使用下述机制;config 框架和 intelligence 框架正是基于 Input Framework 构建,由它们在 manager 节点上执行摄取、再通过事件把数据分发到集群各处。
Input Framework 是什么
Input Framework 的核心目标,是把外部数据(文件、数据库查询结果等)以两种方式送进 Zeek:
- 读入表(table):数据被填充到一张 Zeek 表中,之后可以像普通表一样被查找、遍历,适合构建 denylist、情报库、主机/用户映射等"按索引查询"的数据结构;
- 读入事件(event):每一行数据触发一个 Zeek 事件,由脚本自行决定如何处理,适合数据结构复杂、数据集过大或需要逐条即时处理的场景。
框架采用模块化 reader 架构,不同数据源由不同的 reader 负责解析;默认的 reader 是 Input::READER_ASCII,它理解 Zeek 自身的 TSV 日志格式。此外内置了 Raw、Benchmark、Binary、SQLite 等 reader,Zeek 包与插件还可以提供更多自定义 reader。
从实现上看,框架入口定义在 scripts/base/frameworks/input/main.zeek,它导出了Input::TableDescription、Input::EventDescription两种流描述记录,以及Input::add_table、Input::add_event、Input::remove、Input::force_update等核心函数;底层则通过@load base/bif/input.bif暴露的__create_table_stream、__create_event_stream、__remove_stream、__force_update等内置函数与 C++ 实现(src/input/ 目录下的 Manager、ReaderBackend、ReaderFrontend 等)对接。
把数据读入 Zeek 表:核心流程
读取文件到表是 Input Framework 最常用的场景。默认情况下,框架读取的数据格式与 Zeek 日志框架写出的格式一致:以 Tab 分隔的 ASCII 文件。
假设我们要导入一份 denylist,其中包含服务器 IP 地址、封禁时间戳和封禁原因。示例输入文件(注意所有字段必须以 Tab 分隔,且首行为#fields行声明列名)见 doc/frameworks/input/denylist.file:
#fields ip timestamp reason 192.168.17.1 1333252748 Malware host 192.168.27.2 1330235733 Botnet server 192.168.250.3 1333145108 Virus detected要把文件读入表,需要定义两个 record 类型:
- 一个描述构成表**键(key)**的列的类型与名称;
- 一个描述构成表**值(value)**的列的类型与名称。
以"按 IP 查询"为目的,键记录只包含服务器 IP,其余字段存入值记录:
type Idx: record { ip: addr; }; type Val: record { timestamp: time; reason: string; };注意:record 字段名必须与输入文件#fields行中的列名一一对应(本例为ip、timestamp、reason);列的顺序无关紧要,因为每列按名称识别。
随后在zeek_init()中调用Input::add_table把文件读入表:
global denylist: table[addr] of Val = table(); event zeek_init() { Input::add_table([$source="denylist.file", $name="denylist", $idx=Idx, $val=Val, $destination=denylist]); Input::remove("denylist"); }这三行代码做了三件事:先创建一个接收数据的空表;然后指示框架打开名为 "denylist" 的输入流并把数据读入该表;最后立即调用Input::remove移除该流——因为数据读完之后不再需要它。之所以可以这样"连续两行"调用,得益于框架的异步排队机制(见下文)。
关于键/值 record 与 &optional 字段的细节
键与值 record 都可以使用&optional字段,但省略列(通常用 "-" 字符表示)需要格外小心:
- 键 record 的列会展开为一张值列表,用于对目标表进行索引(上例中
denylist就是被一个普通的addr索引的)。由于索引所需的全部值都必须存在,实践中不能省略键中的值; - 值 record 中省略列是有意义的,但仅允许对带
&optional属性的列进行省略,框架会跳过违反此规则的行并发出警告。
另请注意版本差异:Zeek 4.1 之前框架曾不安全地接受此类输入;从 4.1 起行为收紧。从旧版本升级、且输入数据中存在省略字段的用户,可能会观察到加载数据集与以往不一致。
异步处理与 end_of_data 事件
由于部分数据文件可能很大,Input Framework异步工作:每条新的输入流都会创建一个新线程,该线程负责打开数据文件、把数据转换成内部格式并送回 Zeek 主线程。因此数据不会立即立即可用——根据数据源规模,可能需要从几毫秒到几秒不等。这一点意味着:如果 Zeek 在没有输入源或处理非常短的抓包文件时运行,可能在导入线程完成前就处理完所有报文并退出,导致数据尚未进表程序就已结束。
对同一输入源的后续调用会被排队,直到前一个动作完成——这正是上文add_table与remove可以写在同一事件里连续两行的原因:remove 会一直排队等到首次读取完成。
当框架从某个数据源读完数据后,会触发 Input::end_of_data 事件。收到该事件时,输入文件的所有数据已全部进入表:
event Input::end_of_data(name: string, source: string) { # now all data is in the table print denylist; }在读取进行期间表也可以被使用——只是在该事件触发前,表里可能还没有输入文件的全部行。表填充完成后即可像任何普通 Zeek 表一样使用,例如测试某个 IP 是否命中 denylist:
if ( 192.168.18.12 in denylist ) # take action用 set 代替表:纯成员判定
当数据的核心用途只是"判断是否属于某个集合"时,键/值二元结构并不适用。此时可以把目标数据类型换成set,并在Input::add_table中省略$val:
type Idx: record { ip: addr; }; global denylist: set[addr] = set(); event zeek_init() { Input::add_table([$source="denylist.file", $name="denylist", $idx=Idx, $destination=denylist]); Input::remove("denylist"); }从 TableDescription 定义 看,$val是&optional的:当它未定义时,$destination必须是 set,这正是该用法的接口级依据。
三种读取模式:MANUAL、REREAD 与 STREAM
许多数据源(例如大量 denylist)是持续变化的。Input Framework 提供了几种应对机制,其模式枚举 Input::Mode 定义如下:
| 模式 | 枚举值 | 行为 |
|---|---|---|
Input::MANUAL | 0 | 文件读完后不自动重读(默认模式) |
Input::REREAD | 1 | 每次检测到文件变化时重读整个文件 |
Input::STREAM | 2 | 文件被追加时从文件末尾读取新数据 |
手动刷新:Input::force_update
最基础的方法是显式刷新输入流。只要输入流还处于打开状态(尚未被Input::remove移除),就可以调用 Input::force_update:
Input::force_update("denylist");这会触发一次完整的表刷新:文件中变化的元素被更新、新元素被添加、已不在输入数据中的元素被删除。更新完成后照例会触发Input::end_of_data事件。
REREAD 模式:文件变更自动重读
也可以在Input::add_table中通过$mode指定非默认读取模式,让框架在检测到输入文件变化时自动刷新表内容:
Input::add_table([$source="denylist.file", $name="denylist", $idx=Idx, $val=Val, $destination=denylist, $mode=Input::REREAD]);使用 REREAD 模式时,Zeek 会持续检查输入文件是否变化;若变化则重新读取整个文件,并更新表中数据以反映当前状态。每次检测到变化、且所有新数据都读入表后,都会触发Input::end_of_data事件。
STREAM 模式:追读追加型文件
使用$mode=Input::STREAM时,Zeek 假定输入是**只追加(append-only)**的文件,新数据不断追加到末尾。Zeek 会持续检查文件末尾是否有新数据并把新行加入表;同时检测被跟踪的文件是否被改名或轮转(rotation)——当tail发现正在读取的文件名的 inode 号变了,就会关闭并重新打开该文件。若新行与表中已有行具有相同的表索引,新值会覆盖旧值。
由于流式读取的特性(数据持续不断地进表),STREAM 模式下永远不会触发Input::end_of_data事件。
变更检测的底层机制
变更是通过周期性的"心跳"事件检测的,默认频率为每秒一次,由全局常量 Threading::heartbeat_interval 相关机制控制。reader 判定输入文件"已变化"的依据是:自上次检查以来文件的 **inode 或修改时间(mtime)**发生了改变。
接收变更事件:精确追踪每一行数据的增删改
重读文件时,我们往往还想知道源文件中具体哪些行发生了变化。为此,框架支持在数据项被添加、移除或修改时逐条触发事件。事件定义如下(可以在你自己的 Zeek 脚本中改名):
event entry(description: Input::TableDescription, tpe: Input::Event, left: Idx, right: Val) { # do something here... print fmt("%s = %s", left, right); }在Input::add_table调用中通过$ev指定该事件:
Input::add_table([$source="denylist.file", $name="denylist", $idx=Idx, $val=Val, $destination=denylist, $mode=Input::REREAD, $ev=entry]);各参数含义(与 Input::Event 枚举 对应):
description:最初传给Input::add_table的参数记录,可通过description$name等访问流名等信息;tpe:发生的变化类型枚举;left:发生变化的表项的索引;right:变化前后的值(视tpe而定)。
三种变化类型的行为:
tpe值 | 触发时机 | left | right |
|---|---|---|---|
Input::EVENT_NEW | 表中原本没有的行被添加 | 新增表项的索引 | 新增表项的值 |
Input::EVENT_CHANGED | 已有表项在重读/流式读取中被修改 | 被修改表项的索引 | 修改前的值 |
Input::EVENT_REMOVED | 表项在重读后不再出现而被移除 | 被移除表项的索引 | 被移除表项的值 |
注意EVENT_CHANGED的一个微妙之处:事件触发时表已经被更新,因此right携带的是旧值;如需比较新旧值,可再通过当前表值查询获得新值。
导入时过滤与改写数据:谓词函数(predicate)
Input Framework 允许在导入过程中过滤数据,依靠的是谓词函数(predicate)。谓词函数会在新元素被添加/修改/移除之前被调用,通过返回T(接受)或F(拒绝)来决定是否执行这次变更;此外它还可以在数据写入表之前改写数据。
下面的示例过滤会拒绝"生成时间超过一个月"的新条目,同时接受所有对已存在值的修改与移除:
Input::add_table([$source="denylist.file", $name="denylist", $idx=Idx, $val=Val, $destination=denylist, $mode=Input::REREAD, $pred(tpe: Input::Event, left: Idx, right: Val) = { if ( tpe != Input::EVENT_NEW ) { return T; } return (current_time() - right$timestamp) < 30day; }]);谓词函数签名在 TableDescription 中被定义为pred: function(typ: Input::Event, left: any, right: any): bool &optional,参数含义与变更事件一致。若要改写导入中的元素,谓词可以直接操作left和right。由于谓词在变更提交到表之前被调用,当发生EVENT_CHANGED时,left与right携带的是新值,而目标表(本例中的denylist)里仍是旧值——这使得谓词能够在决定是否放行之前,先对比新旧版本。
损坏输入数据的处理与报错
数据摄取期间出现问题,框架会通过两种途径通知你:
其一,reporter 消息,最终进入reporter.log,指明问题类型与出错文件,例如:
#fields ts level message location 0.000000 Reporter::WARNING denylist.file/Input::READER_ASCII: Did not find requested field ip in input data file denylist.file. (empty)其二,错误事件:Input::TableDescription与Input::EventDescription记录都提供$error_ev成员,用于触发携带上述相同消息与严重级别的事件(见 main.zeek 中的 error_ev 定义),用法与变更事件一致。当错误级别为 error 时,流会被自动关闭。
无论哪种方式,框架都会抑制针对同一文件的重复消息,因此大文件中的错误不会引发消息洪泛。
此外,ASCII reader 提供了对摄取健壮性的粗粒度控制:常量 InputAscii::fail_on_invalid_lines 与 InputAscii::fail_on_file_problem 决定问题是仅触发警告、还是导致处理失败,两者默认都是仅警告(F)。相关 btest 测试见 testing/btest/scripts/base/frameworks/input/ 下的invalid-lines.zeek、invalid-lines-duplicate.zeek、errors.zeek、missing-file.zeek等用例。
把数据读入事件流:Input::add_event
第二种摄取模式是直接从数据生成 Zeek 事件,而不是写入表。事件流的工作方式与表流非常相似,上文讨论的大多数特性(例如谓词过滤)同样适用于事件流。用事件流读取前文的 denylist:
type Val: record { ip: addr; timestamp: time; reason: string; }; event denylistentry(description: Input::EventDescription, tpe: Input::Event, data: Val) { # do something here... print "data:", data; } event zeek_init() { Input::add_event([$source="denylist.file", $name="denylist", $fields=Val, $ev=denylistentry]); }事件流与表流有两点关键区别:
- 无需分别声明索引与值记录——所有源数据类型放在一个 record 定义中(
$fields); - 没有"数据基线"概念:框架把输入视为持续不断的事件流,无法像表那样与新数据做基线对比,因此变化事件类型(上例中的
tpe)永远是Input::EVENT_NEW。
除此之外,事件流与表流的工作方式完全相同,并支持大部分表流支持的选项。值得注意的还有$want_record选项:在 EventDescription 中它默认为T(事件收到包含全部字段的单个 record 值);若设为F,事件会把$fields中的每个值作为独立参数接收。
内置 Reader 全景
Input Framework 支持不同种类的 reader 以应对不同格式的数据源。默认摄取的是符合 Zeek 日志文件格式的 ASCII 文件(Tab 分隔值 +#fields头行)。reader 的选择过程如下:
- 全局变量 Input::default_reader 定义默认 reader,即
Input::READER_ASCII;调用Input::add_table/Input::add_event时自动使用; - 可以在传给这些调用的描述记录中设置
$reader成员来覆盖默认值; - 相关示例可参见 testing/btest/scripts/base/frameworks/input/ 目录下的测试用例。
reader 枚举由 C++ 侧生成(见 src/input/input.bif 及生成的头文件),脚本侧则在 main.zeek 引用。下面逐一介绍各 reader 的脚本接口。
ASCII Reader(默认)
ASCII reader 默认启用(或通过Input::READER_ASCII选择),理解 Zeek 的 TSV 日志格式。它实际上理解这些日志文件前导部分的全套指令(例如定义列分隔符的指令),但很少被用到;最常见的输入文件就是前文展示的那样:以一行 Tab 分隔的#fields头行开始。
其可调接口定义在 scripts/base/frameworks/input/readers/ascii.zeek,关键常量如下:
| 常量 | 默认值 | 含义 |
|---|---|---|
InputAscii::separator | Input::separator("\t") | 字段分隔符(必须恰好 1 个字符) |
InputAscii::set_separator | Input::set_separator(",") | set/vector 元素分隔符(必须恰好 1 个字符) |
InputAscii::empty_field | Input::empty_field("(empty)") | 空字段的表示字符串 |
InputAscii::unset_field | Input::unset_field("-") | 未设置的&optional字段的表示字符串 |
InputAscii::fail_on_invalid_lines | F | 无效行是仅报告警告(跳行)还是致命错误(中止读取) |
InputAscii::fail_on_file_problem | F | 文件读取问题(权限、文件缺失等)是仅警告还是失败 |
InputAscii::path_prefix | "" | 为无路径/相对路径的源文件名添加前缀(绝对路径不受影响) |
全局的Input::separator、Input::set_separator、Input::empty_field、Input::unset_field都带有&redef属性(main.zeek),各 reader 可以覆盖它们;fail_on_invalid_lines/fail_on_file_problem也可通过$config表按流覆盖。
一个重要警告:ASCII reader没有文件锁概念(包括 UNIX 的 advisory locking)。对大型文件而言,框架可能处理一个仍在被写入的文件;reader 能稳健地处理由此产生的错误(例如通过 reporter.log,如前文所述),但错误仍会发生。为避免这类问题,最佳实践是先在旁边生成新的输入文件,再原子性地 rename 到框架监控的文件名。
Benchmark Reader
Benchmark reader(Input::READER_BENCHMARK)用于帮助 Zeek 开发者优化输入框架的速度:它可以按任意数量生成 Input Framework 所支持的全部 Zeek 数据类型上的半随机数据。其可调参数定义在 scripts/base/frameworks/input/readers/benchmark.zeek:factor(每秒乘法因子,默认 1.0)、spread(行间散布因子)、autospread(自动散布)、addfactor(每个心跳的加法因子)、stopspreadat(每个心跳散布到多少行停止)、timedspread(启用定时散布)。
Binary Reader
Binary reader(Input::READER_BINARY)用于文件分析输入流(file analysis input streams)以摄取文件内容,并且是这类流的默认 reader 类型。它按块读取文件内容并转发给文件分析框架。其接口在 scripts/base/frameworks/input/readers/binary.zeek:InputBinary::chunk_size(每次读取的数据块大小,默认 1024 字节)与InputBinary::path_prefix(路径前缀,默认空)。
对应地,框架还提供了Input::add_analysis函数(main.zeek),它创建一条文件分析输入流,读取的数据会自动转发给文件分析框架,其AnalysisDescription的$reader默认为Input::READER_BINARY。
Raw Reader
Raw reader(Input::READER_RAW)读取按指定记录分隔符(record separator,默认换行符"\n")切分的文件,内容逐行作为字符串返回。它可用于读取配置文件等,通常只在事件模式下有用,不适合读入表。其接口在 scripts/base/frameworks/input/readers/raw.zeek:InputRaw::record_separator定义记录分隔符;同时定义了InputRaw::process_finished事件,用于 raw reader 创建的子进程退出时通知脚本(name、source、exit_code、signal_exit)。
读取 JSON Lines:Raw Reader + from_json
ASCII reader 目前不支持原生 JSON 摄取,但可以组合 raw reader 与内置函数from_json读取 JSON Lines 格式的文件并实例化 Zeek record 值(自 Zeek 6.0 起可用)。
下面示例演示了完整做法——维护两张状态表以便支持对已读数据的移除更新。示例输入文件 denylist.jsonl:
{"ip": "192.168.17.1", "timestamp": 1333252748, "reason": "Malware host"} {"ip": "192.168.27.2", "timestamp": 1330235733, "reason": "Botnet server"} {"ip": "192.168.250.3", "timestamp": 1333145108, "reason": "Virus detected"}加载脚本 input_json_1.zeek 的核心逻辑:
module Denylist; type JsonLine: record { s: string; }; type Entry: record { ip: addr; timestamp: time; reason: string; }; global staged_denies: table[addr] of Entry; global active_denies: table[addr] of Entry; event Input::end_of_data(name: string, source: string) { if ( name != "denylist" ) return; # Switch active and staging tables when input file has been read. active_denies = staged_denies; staged_denies = table(); print network_time(), "end_of_data() active:", table_keys(active_denies); } event Denylist::json_line(description: Input::EventDescription, tpe: Input::Event, l: string) { local parse_result = from_json(l, Entry); # Parsing of JSON may fail, so ignore anything invalid. if ( ! parse_result$valid ) return; local entry = parse_result$v as Entry; # ...and populate staging table. staged_denies[entry$ip] = entry; } event zeek_init() { Input::add_event([ $source="denylist.jsonl", $name="denylist", $reader=Input::READER_RAW, $mode=Input::REREAD, $fields=JsonLine, $ev=Denylist::json_line, $want_record=F, ]); }要点:
- 用 raw reader +
$mode=Input::REREAD打开流,$want_record=F使事件按独立参数接收; - 每行 JSON 通过
from_json(l, Entry)解析为Entryrecord,先填充staged_denies临时表; - 在
Input::end_of_data中原子切换active与staged两张表,从而在 REREAD 模式下实现整表替换式的"移除+新增"更新语义。
如果你的输入数据已经是(或可以方便地转换成)JSON Lines 格式,上述方案即可将其加载进 Zeek。相关测试可参考 testing/btest/scripts/base/frameworks/input/from_json.zeek。
SQLite Reader
SQLite reader(Input::READER_SQLITE)让 Zeek 可以直接访问 SQLite 数据库。SQLite 是简单、基于文件、被广泛使用的 SQL 数据库;由于其事务性特性,同一个数据库可被多个应用同时使用,因此非常适合把持续演化的数据集持续提供给 Zeek。与 Zeek 的日志支持一样,SQLite 数据读取已内置于 Zeek,无需额外配置。其脚本接口在 scripts/base/frameworks/input/readers/sqlite.zeek,提供了set_separator、unset_field、empty_field三个可 redef 常量。
使用 SQLite reader 时,必须在$config表中设置query键,给出返回所需数据的 SQL 查询。完整入门介绍见 doc/frameworks/logging-input-sqlite.rst。
场景一:把查询结果读入表
假设数据库保存了各主机 IP 与允许登录该机器的用户账号。建表与填充数据的 SQL:
create table machines_to_users ( host text unique not null, users text not null); insert into machines_to_users values ( '192.168.17.1', 'johanna,matthias,seth'); insert into machines_to_users values ( '192.168.17.2', 'johanna'); insert into machines_to_users values ( '192.168.17.3', 'seth,matthias');把上述内容存入hosts.sqlite后,即可读取结果表:
type Idx: record { host: addr; }; type Val: record { users: set[string]; }; global hostslist: table[addr] of Val = table(); event zeek_init() { Input::add_table([$source="/var/db/hosts", $name="hosts", $idx=Idx, $val=Val, $destination=hostslist, $reader=Input::READER_SQLITE, $config=table(["query"] = "select * from machines_to_users;") ]); Input::remove("hosts"); } event Input::end_of_data(name: string, source: string) { if ( name != "hosts" ) return; # now all data is in the table print "Hosts list has been successfully imported"; # List the users of one host. print hostslist[192.168.17.1]$users; }这里值得注意几点:
$source是数据库文件的路径,$name是流名,二者不必相同;Val中的users: set[string]对应数据库中的逗号分隔字符串——SQLite reader 会用set_separator(默认",")把字段解析为集合;- 表流模式下,
users列的逗号分隔值通过 InputSQLite::set_separator 拆分。
之后hostslist表就可以用于对照可用用户列表检查主机登录行为。
场景二:把查询结果变成事件流
把输入数据作为事件输出通常有两个理由:一是输入数据结构过于复杂,不便直接导入表——可在事件中于脚本层构建所需数据结构;二是数据集太大,无法全部驻留内存——事件驱动式摄取可以按需进行即时检查。
以"大型恶意软件哈希数据库"为例:数据库结构如下(哈希值唯一且非空):
create table malware_hashes ( hash text unique not null, description text not null); insert into malware_hashes values ('86f7e437faa5a7fce15d1ddcb9eaeaea377667b8', 'malware a'); insert into malware_hashes values ('e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98', 'malware b'); insert into malware_hashes values ('84a516841ba77a5b4648de2cd0dfcb30ea46dbb4', 'malware c'); insert into malware_hashes values ('3c363836cf4e16666669a25da280a1865c2d2874', 'malware d'); insert into malware_hashes values ('58e6b3a414a1e090dfc6029add0f3555ccba127f', 'malware e'); insert into malware_hashes values ('4a0a19218e082a343a1b17e5333409af9d98f0f5', 'malware f'); insert into malware_hashes values ('54fd1711209fb1c0781092374132c66e79e2241b', 'malware g'); insert into malware_hashes values ('27d5482eebd075de44389774fce28c69f45c8a75', 'malware h'); insert into malware_hashes values ('73f45106968ff8dc51fba105fa91306af1ff6666', 'ftp-trace');下面的脚本结合文件分析框架获取网络传输文件的 sha1 哈希,对每个哈希向 SQLite 发起一次 SQL 查询,命中则输出匹配的哈希:
@load frameworks/files/hash-all-files type Val: record { hash: string; description: string; }; event line(description: Input::EventDescription, tpe: Input::Event, r: Val) { print fmt("malware-hit with hash %s, description %s", r$hash, r$description); } global malware_source = "/var/db/malware"; event file_hash(f: fa_file, kind: string, hash: string) { # check all sha1 hashes if ( kind=="sha1" ) { Input::add_event( [ $source=malware_source, $name=hash, $fields=Val, $ev=line, $want_record=T, $config=table( ["query"] = fmt("select * from malware_hashes where hash='%s';", hash) ), $reader=Input::READER_SQLITE ]); } } event Input::end_of_data(name: string, source:string) { if ( source == malware_source ) Input::remove(name); }注意此处为每个哈希创建独立的事件流($name=hash),并在Input::end_of_data中按source匹配后逐一Input::remove清理,避免流累积。如果对testing/btest/Traces/ftp/ipv4.trace运行该脚本,会得到一次命中。
更多源码级细节与测试佐证
为了让上述用法有据可查,这里补充几个关键的实现与验证入口:
- 框架脚本接口:scripts/base/frameworks/input/main.zeek 定义了
TableDescription(含$reader、$mode、$idx、$val、$want_record、$ev、$pred、$error_ev、$config等字段)、EventDescription与AnalysisDescription三种描述记录,以及add_table/add_event/add_analysis/remove/force_update五个全局函数和end_of_data事件; - C++ 实现:src/input/ 目录下的
Manager.cc、ReaderBackend.cc、ReaderFrontend.cc等实现了流的创建、后台线程调度、reader 生命周期管理;Input::force_update通过__force_update触发对应流的一次强制刷新; - btest 测试用例:testing/btest/scripts/base/frameworks/input/ 目录下覆盖了大部分场景:
basic.zeek(基础表导入)、event.zeek(事件流)、reread.zeek(重读模式)、predicate.zeek/predicate-stream.zeek/predicatemodify.zeek(谓词过滤与改写)、set-event-reread.zeek/set-event-stream.zeek(set 目标 + 变更事件)、invalid-lines.zeek/errors.zeek/missing-file.zeek(损坏输入)、optional.zeek(&optional字段)、from_json.zeek(JSON Lines 摄取)、bignumber.zeek/port.zeek/patterns.zeek(类型解析边界)等,是验证各机制实际行为的第一手资料; - 默认常量:
Input::default_reader = READER_ASCII、Input::default_mode = MANUAL(main.zeek),以及accept_unsupported_types = F(遇到暂不支持的类型如file/function时默认中止,可 redef 为T改为仅警告)。
实战选型建议
综合上文,可以给出如下选型思路:
- 构建按索引查询的静态/半静态数据(denylist、情报库、映射表):用
Input::add_table+ ASCII/SQLite reader,数据一次性读入后Input::remove清理流; - 数据持续变化、需要整表同步:
$mode=Input::REREAD,必要时结合$ev变更事件或$pred谓词做增量感知与过滤;也可在end_of_data中配合双表切换实现原子整表替换; - 只追加的日志/流水数据:
$mode=Input::STREAM追读文件末尾,但注意它不会触发end_of_data; - 格式灵活、需要逐行即时处理(JSON Lines、配置文件、命令输出):
Input::add_event+ Raw reader +from_json(Zeek 6.0+); - 查询外部数据库且结果庞大/结构复杂:
Input::READER_SQLITE+add_event,按需发起查询、即时处置; - 向文件分析框架供数:
Input::add_analysis,默认搭配Input::READER_BINARY逐块摄取字节流。
以上机制均以当前仓库(Zeek 源码树)中的文档、脚本与测试为准;实际部署时请结合你自己的 Zeek 版本核对Input::Mode、from_json等能力(from_json自 6.0 起可用)的可用性。
- 网络安全
- 网络
- IDS
【免费下载链接】zeek
Zeek is a powerful network analysis framework that is much different from the typical IDS you may know.
相关推荐
Zeek Input Framework 实战指南:从 TSV 表格导入到 SQLite 数据库查询
Zeek Input Framework 实战指南:从 TSV 表格导入到 SQLite 数据库查询 Zeek(原 Bro)提供了一套灵活强大的输入框架(Inp
网络安全网络IDSZeek SQLite 输入读取与日志写入实战指南:基于 Input/Logging 框架的数据库集成
Zeek SQLite 输入读取与日志写入实战指南:基于 Input/Logging 框架的数据库集成 SQLite 是一款简单、基于文件、被广泛使用的嵌入式
网络安全网络IDSDLSS Swapper 完整上手指南:不更新游戏即可更换 DLSS、FSR、XeSS 版本
DLSS Swapper 完整上手指南:不更新游戏即可更换 DLSS、FSR、XeSS 版本 DLSS Swapper 是一款面向 Windows 的免费开源工
桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考