- 网络安全
- 网络
- IDS
【免费下载链接】zeek
Zeek is a powerful network analysis framework that is much different from the typical IDS you may know.
本篇文章聚焦 Zeek 网络分析框架中 PostgreSQL 协议分析器(基于 Spicy)的两个核心状态变量——PostgreSQL::auth_ids与PostgreSQL::error_ids。它们分别将 PostgreSQL 前端/后端协议中的认证方式编号与错误/通知消息字段代码翻译为人类可读的字符串,是理解postgresql.log日志中backend_arg字段内容的关键。读完本文,你将掌握这两个表的默认映射、&default与&redef的扩展机制,以及它们与 Spicy 解析器、事件处理链路的实际调用关系,并能够据此自定义扩展自己的映射。
一、这两个常量表解决什么问题
PostgreSQL 线协议(wire protocol,版本 3.0)是一个高度结构化的二进制协议:所有消息都带有一个 1 字节的类型标签和 4 字节的长度前缀。其中有两类信息对安全监控与流量审计特别有价值,但直接以编号或单字节代码出现在报文中:
- 认证方式编号(AuthenticationRequest 消息中的 identifier 字段):服务端用 4 字节整数告诉客户端使用何种认证机制,例如
5表示 MD5 密码认证、10表示 SASL 认证。 - 错误/通知字段代码(ErrorResponse 与 NoticeResponse 消息中的字段标签):每个错误或通知由一组"单字节代码 + 字符串值"的字段组成,例如
S表示严重程度(Severity)、C表示错误码(SQLSTATE)、M表示消息正文。
[consts.zeek](https://link.gitcode.com/i/e2ab2494e6ce2a25f57044756d0d7c69)中定义的两个全局表正是这两类信息的"翻译字典":
PostgreSQL::auth_ids: table[count] of string——由认证编号到认证名称;PostgreSQL::error_ids: table[string] of string——由字段代码到字段含义名称。
两者都带&default=function ... &redef属性,即:遇到表中没有的编号或代码时,会通过默认函数生成一个UnknownAuthId<编号>/UnknownErrorId<代码>形式的占位字符串,而不会直接崩溃或丢弃数据;同时脚本可以通过redef自由补充或覆盖映射。这是 Zeek 脚本层"宽容解析 + 可扩展"设计哲学的典型体现。
二、PostgreSQL::auth_ids:认证方式编号映射
官方文档 consts.zeek.rst 给出的默认映射如下(来源为 consts.zeek):
{ [2] = "KerberosV5", [3] = "CleartextPassword", [5] = "MD5Password", [7] = "GSSAPI", [8] = "GSSAPIContinue", [9] = "SSPI", [10] = "SASL", [11] = "SASLContinue", [12] = "SASLFinal" }这些编号与 PostgreSQL 官方协议文档中AuthenticationOk之后各种AuthenticationRequest子类型的标识一一对应:
| 编号 | 认证方式 | 说明 |
|---|---|---|
| 0 | (隐含)AuthenticationOk | 无需认证即成功,不进入该表 |
| 2 | KerberosV5 | Kerberos V5 认证 |
| 3 | CleartextPassword | 明文密码认证(通过PasswordMessage发送) |
| 5 | MD5Password | MD5 加盐密码认证,服务端会附带 4 字节盐值 |
| 7 | GSSAPI | GSSAPI 认证 |
| 8 | GSSAPIContinue | GSSAPI 多轮握手的继续消息 |
| 9 | SSPI | Windows SSPI 认证 |
| 10 | SASL | SASL 认证(SCRAM-SHA-256 等机制) |
| 11 | SASLContinue | SASL 多轮握手的继续消息 |
| 12 | SASLFinal | SASL 握手完成消息 |
注意:表中并没有编号0,因为在协议中identifier == 0意味着AuthenticationOk。这一点在 Spicy 解析器中就已被特殊处理——postgresql.evt 使用两条带条件的on规则把 0 与非 0 分流:
on PostgreSQL::AuthenticationRequest if ( self.identifier != 0 )-> event PostgreSQL::authentication_request($conn, self.identifier, self.data); on PostgreSQL::AuthenticationRequest if ( self.identifier == 0 ) -> event PostgreSQL::authentication_ok($conn);而 postgresql.spicy 中AuthenticationRequest单元对identifier做了&requires=($$ <= 12)的范围校验,并检查AuthenticationOK with wrong length,超出已知范围的编号会被视为解析错误。
默认函数与未知名处理
auth_ids的完整定义还包括一个默认函数:
&default=function(id: count): string { return fmt("UnknownAuthId%s", id); }也就是说,若未来 PostgreSQL 引入编号 13 以上的新认证方式,或抓包中出现异常的编号,Zeek 不会报错,而是会在日志里以UnknownAuthId13的形式原样透出编号,保证监控不中断。这与&redef属性配合后,用户可以在自己的脚本中redef PostgreSQL::auth_ids += { [13] = "SomeNewAuth" };及时跟上协议演进。
三、PostgreSQL::error_ids:错误/通知字段代码映射
error_ids是一个table[string] of string,覆盖 PostgreSQL 官方文档《Protocol Error Fields》中定义的全部 18 个字段代码(来源为 consts.zeek):
{ ["S"] = "SeverityLocalized", // 本地化严重程度(如 "ERROR") ["V"] = "Severity", // 非本地化严重程度 ["C"] = "Code", // SQLSTATE 错误码(如 "42P01") ["M"] = "Message", // 人类可读的主错误消息 ["D"] = "Detail", // 补充细节 ["H"] = "Hint", // 提示信息 ["P"] = "Position", // 在查询串中的字符位置 ["p"] = "InternalPosition", // 内部查询中的位置 ["q"] = "InternalQuery", // 内部查询文本 ["W"] = "Where", // 出错位置的上下文 ["s"] = "Schema", // 模式名 ["t"] = "Table", // 表名 ["c"] = "Column", // 列名 ["d"] = "Data", // 数据对象名 ["n"] = "Constraint", // 约束名 ["F"] = "File", // 产生错误的源码文件名 ["L"] = "Line", // 产生错误的源码行号 ["R"] = "Routine", // 产生错误的函数名 }字段代码与含义的对照关系是协议规范(PostgreSQL 官方协议文档中 "Error and Notice Message Fields" 一节)固定的,[consts.zeek](https://link.gitcode.com/i/e2ab2494e6ce2a25f57044756d0d7c69#L4)中的注释也直接指向该规范。大小写敏感是这里的重点:S(Severity)与s(Schema)、C(Code)与c(Column)、P(Position)与p(InternalPosition)含义完全不同,因此表键区分大小写。
error_ids同样带有&default=function(c: string): string { return fmt("UnknownErrorId%s", c); },遇到未定义的字段代码会生成UnknownErrorId<code>占位值。
四、在事件处理中的实际调用链
这两个表本身不产生日志,它们是在 main.zeek 的事件处理器中被消费的。理解调用链有助于知道表中字符串最终出现在日志的哪个位置。
4.1 认证编号 →backend_arg
当服务端返回认证请求时,PostgreSQL::authentication_request事件被触发,main.zeek 使用auth_ids将编号翻译成名称并拼入backend_arg:
event PostgreSQL::authentication_request(c: connection, identifier: count, data: string) { hook set_session(c); if ( c$postgresql?$backend && ! ends_with(c$postgresql$backend, "auth") ) c$postgresql$backend += ",auth_request"; else c$postgresql$backend = "auth_request"; if ( c$postgresql?$backend_arg ) c$postgresql$backend_arg += "," + auth_ids[identifier]; else c$postgresql$backend_arg = auth_ids[identifier]; }认证成功后authentication_ok事件会把backend置为"auth_ok"、success置为T(main.zeek)。
4.2 错误/通知字段代码 → 拼接为键值对
error_response_identified_field与notice_response_identified_field都会为每个字段生成字段名=值的字符串(main.zeek):
event PostgreSQL::error_response_identified_field(c: connection, code: string, value: string) { hook set_session(c); local errors = c$postgresql_state$errors; errors += fmt("%s=%s", error_ids[code], value); } event PostgreSQL::notice_response_identified_field(c: connection, code: string, value: string) { hook set_session(c); local notice = fmt("%s=%s", error_ids[code], value); if ( c$postgresql?$backend_arg ) c$postgresql$backend_arg += "," + notice; else c$postgresql$backend_arg = notice; }随后error_response事件会把累积的字段用逗号拼接、清空状态向量,并将success置为F(main.zeek)。
这些事件本身由 Spicy 解析器通过 postgresql.evt 的规则触发:
on PostgreSQL::ErrorIdentifiedField -> event PostgreSQL::error_response_identified_field($conn, ("%c" % self.code), self.value); on PostgreSQL::NoticeIdentifiedField -> event PostgreSQL::notice_response_identified_field($conn, ("%c" % self.code), self.value);对应的 Spicy 单元定义在 postgresql.spicy:ErrorIdentifiedField/NoticeIdentifiedField由"1 字节代码 + 以\x00结尾的值"构成,ErrorResponse/NoticeResponse则是该字段的数组加上结尾空字节。
4.3 从测试基线看实际输出
仓库的 btest 基线数据直观展示了翻译结果。以 psql-create-insert-select 测试基线 为例,一条真实的postgresql.log记录长这样:
ts uid id.orig_h id.orig_p id.resp_h id.resp_p user database application_name frontend frontend_arg backend backend_arg success rows ... CHhAvVGS1DHFjwGM9 127.0.0.1 40190 127.0.0.1 5432 postgres postgres psql startup - auth_ok SASL,SASLContinue,SASLFinal T - ... CHhAvVGS1DHFjwGM9 127.0.0.1 40190 127.0.0.1 5432 postgres postgres psql simple_query DROP TABLE IF EXISTS t; - SeverityLocalized=NOTICE,Severity=NOTICE,Code=00000,Message=table "t" does not exist, skipping,File=tablecmds.c,Line=1300,Routine=DropErrorMsgNonExistent T 0 ... CHhAvVGS1DHFjwGM9 127.0.0.1 40190 127.0.0.1 5432 postgres postgres psql simple_query SELECT * from t; - - T 2可以看到:auth_ok行的backend_arg完整列出了SASL,SASLContinue,SASLFinal三个认证阶段(对应编号 10/11/12);DROP 语句产生的 NOTICE 被逐字段翻译为SeverityLocalized=NOTICE,Severity=NOTICE,Code=00000,Message=...,File=tablecmds.c,Line=1300,Routine=DropErrorMsgNonExistent——这正是error_ids将S/V/C/M/F/L/R等单字节代码展开后的结果。仓库中还有 psql-login-fail、psql-insert-fail-drop-fail 等基线,分别展示了登录失败与 SQL 执行失败时错误字段的完整翻译。
五、扩展这两个表的正确姿势
由于两个表都声明了&redef,最典型的使用场景是在站点级策略脚本中补充映射。例如在site/local.zeek或自定义脚本中:
@load base/protocols/postgresql redef PostgreSQL::auth_ids += { [13] = "CustomAuthMethod", }; redef PostgreSQL::error_ids += { ["X"] = "CustomErrorField", };Zeek 的redef table += { ... }语法会向表中追加条目,&default函数则保证任何未覆盖的编号/代码依然有兜底翻译。这种"默认值 + 可重定义"的组合让分析器既能保持前向兼容,又能被用户按需定制。
六、定位与调试建议
- 映射定义集中在 consts.zeek(该文件被 main.zeek 通过
@load ./consts加载,属于base脚本,随 Zeek 默认加载)。 - 底层解析逻辑位于 postgresql.spicy 与 postgresql.evt,事件声明见 spicy-events.zeek。
- 若要验证自己的扩展,可以对照 testing/btest/Baseline/scripts.base.protocols.postgresql.* 系列基线,或直接对真实 PostgreSQL 流量运行
zeek -r trace.pcap并检查postgresql.log的backend_arg列。
总结
PostgreSQL::auth_ids与PostgreSQL::error_ids是 Zeek PostgreSQL 分析器中体量虽小却承上启下的两张常量表:上游承接 Spicy 解析器产出的原始编号/代码,下游将翻译结果写入postgresql.log的backend_arg字段。理解它们的默认映射、&default兜底逻辑与&redef扩展机制,既能帮助你在排查日志时快速读懂SASL,SASLContinue,SASLFinal或SeverityLocalized=ERROR,Code=42P01,...这类字段的来历,也能让你在协议演进或特殊需求面前自如地扩展映射。
- 网络安全
- 网络
- IDS
【免费下载链接】zeek
Zeek is a powerful network analysis framework that is much different from the typical IDS you may know.
相关推荐
Zeek 网络流量分析框架中的 PostgreSQL 协议分析:base/protocols/postgresql 脚本深入解读
Zeek 网络流量分析框架中的 PostgreSQL 协议分析:base/protocols/postgresql 脚本深入解读 Zeek 内置的 Postgr
网络安全网络IDSZeek 的 PostgreSQL 协议分析器:postgresql.log 日志深度解析与源码级实现
Zeek 的 PostgreSQL 协议分析器:postgresql.log 日志深度解析与源码级实现 导读 Zeek 在 7.1 版本起内置了一个基于 Spi
网络安全网络IDSZeek PostgreSQL 协议分析器深度解析:从 Spicy 解析器到 postgresql.log 的完整链路
Zeek PostgreSQL 协议分析器深度解析:从 Spicy 解析器到 postgresql.log 的完整链路 本文以 Zeek 仓库中 scripts
网络安全网络IDS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考