- 网络安全
【免费下载链接】suricata
Suricata is a network Intrusion Detection System, Intrusion Prevention System and Network Security Monitoring engine developed by the OISF and the Suricata community.
SDP(Session Description Protocol)是 SIP 信令中承载媒体会话描述的载体,通常作为 SIP 请求(如 INVITE)或响应(如 200 OK)的消息体出现,描述会话发起人、会话名称、媒体类型、端口、编解码器、带宽等关键信息。Suricata 将 SDP 字段实现为一组sdp.*检测关键字,它们以 sticky buffer 形式工作,让规则可以直接匹配结构化解析后的 SDP 字段,而不是退化为对原始字节流的盲搜。读完本文,你将掌握全部 17 个sdp.*关键字的语法与语义、会话级与媒体级字段的区分、底层 Rust 解析器与检测注册机制的工作原理,并能编写针对 VoIP 会话劫持、媒体参数篡改等场景的检测规则。
一、SDP 关键字概览
SDP 关键字全部实现为sticky buffer(粘性缓冲),用于匹配 SDP 消息中的各个字段。Suricata 会先由 Rust 解析器将 SDP 消息体解析为结构化对象(对应 rust/src/sdp/parser.rs 中的SdpMessage结构体),再把各字段按需提供给检测引擎,规则中的content等关键字随后在这些字段内容上执行匹配。
当前仓库实现的关键字及其方向支持如下(所有关键字方向均为 Both,即对 SIP 请求与响应均可匹配):
| 关键字 | 方向 | 匹配字段(SDP 行) |
|---|---|---|
sdp.origin | Both | o= 发起人 |
sdp.session_name | Both | s= 会话名称 |
sdp.session_info | Both | i= 会话信息 |
sdp.uri | Both | u= 会话 URI |
sdp.email | Both | e= 邮箱地址 |
sdp.phone_number | Both | p= 电话号码 |
sdp.connection_data | Both | c= 连接数据 |
sdp.bandwidth | Both | b= 带宽信息 |
sdp.time | Both | t= 会话激活时间 |
sdp.repeat_time | Both | r= 重复时间 |
sdp.timezone | Both | z= 时区调整 |
sdp.encryption_key | Both | k= 加密密钥 |
sdp.attribute | Both | a= 会话属性 |
sdp.media.media | Both | m= 媒体描述(媒体级) |
sdp.media.session_info | Both | i= 媒体标题(媒体级) |
sdp.media.connection_data | Both | c= 连接数据(媒体级) |
sdp.media.encryption_key | Both | k= 加密密钥(媒体级) |
说明:原文档的关键字汇总表未单列
sdp.phone_number,但正文中该关键字有完整的语法与示例章节,且源码 rust/src/sdp/detect.rs 中明确注册了sdp.phone_number缓冲区,因此上表将其补全为 17 个关键字。
二、通用语法:sticky buffer 的书写方式
所有 SDP 关键字的用法一致:以sdp.xxx作为缓冲区修饰符(sticky buffer),后接content关键字指定要匹配的内容,以分号结尾。基本语法为:
sdp.<field>; content:<匹配内容>;content中可同时指定多个内容片段(此时它们按顺序依次匹配),也可配合nocase、depth、offset、within、distance等标准的 Suricata content 修饰符细化匹配行为。由于字段值是解析后的精确内容(不含o=等行前缀),content必须与解析器产出的字段值一致——例如sdp.origin匹配的是o=行去掉前缀后的完整字符串。
三、会话级字段:逐个掌握全部关键字
3.1 sdp.origin
匹配 SDP 请求或响应中的发起人(Originator),对应 SDP 的o=行。该行包含用户名、会话 ID、会话版本、网络类型、地址类型和单播地址六段信息。
sdp.origin; content:<origin>;其中<origin>是遵循 SDP Origin(o=)格式的发起人字符串。示例:
sdp.origin; content:"SIPPS 105015165 105015162 IN IP4 192.168.1.2";从解析器看,o=行由parse_origin_line解析(rust/src/sdp/parser.rs),解析后将六个字段以空格重新拼接为完整字符串。测试用例o=Clarent 120386 120387 IN IP4 200.57.7.196得到"Clarent 120386 120387 IN IP4 200.57.7.196",与规则中content的写法完全对应。
3.2 sdp.session_name
匹配会话名称,对应s=行。
sdp.session_name; content:<session_name>;其中<session_name>是遵循 SDP Session name(s=)格式的名称。示例:
sdp.session_name; content:"SIP call";parse_session_name(rust/src/sdp/parser.rs)允许字母、数字和空格字符组成会话名,测试用例s=Clarent C5CM解析为"Clarent C5CM"。
3.3 sdp.session_info
匹配会话信息,对应可选的i=行,提供人类可读的会话说明。
sdp.session_info; content:<session_info>;其中<session_info>是遵循 SDP Session information(i=)格式的描述。示例:
sdp.session_info; content:"Session Description Protocol";3.4 sdp.uri
匹配会话描述中的 URI,对应可选的u=行。
sdp.uri; content:<uri>;其中<uri>是遵循 SDP scheme 的 URI。示例:
sdp.uri; content:"https://www.sdp.proto"原文档示例结尾未带分号;为保持规则语法一致,建议写作sdp.uri; content:"https://www.sdp.proto";。解析器通过parse_uri(rust/src/sdp/parser.rs)按 URI 合法字符集(is_request_uri_char)提取内容。
3.5 sdp.email
匹配联系人邮箱地址,对应可选的e=行,格式通常为地址 (姓名)。
sdp.email; content:<email>其中<email>是遵循 SDP scheme 的邮箱地址(e=)。示例:
sdp.email; content:"j.doe@example.com (Jane Doe)";3.6 sdp.phone_number
匹配联系人电话号码,对应可选的p=行。该关键字在原文档表格中未列出,但在正文与源码注册(rust/src/sdp/detect.rs)中均有完整定义。
sdp.phone_number; content:<phone_number>其中<phone_number>是遵循 SDP scheme 的电话号码(p=)。示例:
sdp.phone_number; content:"+1 617 555-6011 (Jane Doe)";3.7 sdp.connection_data
匹配连接数据,对应c=行,声明会话或媒体的连接地址(网络类型、地址类型、连接地址,可选 TTL 与地址数量)。
sdp.connection_data; content:<connection_data>;其中<connection_data>是遵循 SDP scheme 的连接信息(c=)。示例:
sdp.connection_data; content:"IN IP4 192.168.1.2"解析器parse_connection_data(rust/src/sdp/parser.rs)对地址做了类型校验:IP 地址必须是合法 IPv4/IPv6(通过IpAddr::from_str校验);多播地址要求至少带 TTL(如IN IP4 224.2.36.42/127),单播 IPv4 带 TTL/数量时格式为addr/ttl或addr/ttl/count,测试用例c=IN IP4 224.2.36.42/127/2解析为"IN IP4 224.2.36.42/127/2",而裸的多播地址c=IN IP4 224.2.36.42会被判为解析失败。
3.8 sdp.bandwidth
匹配带宽信息,对应b=行。SDP 规范允许会话级出现零到多条b=行,因此该关键字支持多值匹配(见第五节源码原理)。
sdp.bandwidth; content:<bandwidth>其中<bandwidth>是遵循 SDP scheme 的带宽信息(b=)。示例:
sdp.bandwidth; content:"AS:64"parse_bandwidth(rust/src/sdp/parser.rs)目前仅接受CT、AS、TIAS三种带宽修饰符,输出形如"AS:64"的修饰符:值字符串。
3.9 sdp.time
匹配会话激活时间,对应t=行,格式为起始时间与结束时间两个十进制数字(NTP 时间戳形式)。
sdp.time; content:<time>其中<time>是遵循 SDP scheme 的时间(t=)。示例:
sdp.time; content:"3034423619 3042462419"3.10 sdp.repeat_time
匹配重复时间,对应r=行,说明会话的重复周期,包含间隔、时长等四个参数,支持d/h/m/s后缀(如7d 1h 0 25h)。
sdp.repeat_time; content:<repeat_time>其中<repeat_time>是遵循 SDP scheme 的重复时间(r=)。示例:
sdp.repeat_time; content:"604800 3600 0 90000"3.11 sdp.timezone
匹配时区调整,对应z=行,格式为时间 偏移对的序列。
sdp.timezone; content:<timezone>其中<timezone>是遵循 SDP scheme 的时区(z=)。示例:
sdp.timezone; content:"2882844526 -1h 2898848070 0"3.12 sdp.encryption_key
匹配加密密钥,对应k=行,用于在 SDP 承载于安全通道时传递加密密钥信息。
sdp.encryption_key; content:<encryption_key>其中<encryption_key>是遵循 SDP scheme 的密钥(k=)。示例:
sdp.encryption_key; content:"prompt"3.13 sdp.attribute
匹配会话属性,对应a=行。属性是 SDP 的扩展点,可为零到多条,因此同样支持多值匹配。属性可带属性名:属性值形式(如rtpmap:8 PCMA/8000/1),也可只有属性名(如sendrecv)。
sdp.attribute; content:<attribute>其中<attribute>是遵循 SDP scheme 的属性(a=)。示例:
sdp.attribute; content:"sendrecv"parse_attributes(rust/src/sdp/parser.rs)会把a=sendrecv解析为"sendrecv"、a=rtpmap:8 PCMA/8000/1解析为"rtpmap:8 PCMA/8000/1"。
四、媒体描述级字段:m= 行的子字段
SDP 会话可以包含零或多个媒体描述(Media Description),每个媒体描述以m=行开始,其后可跟媒体级的i=、c=、b=、k=、a=行。Suricata 提供四个媒体级子字段关键字,用于区分"会话级"与"媒体级"的同类信息(例如会话级c=与媒体级c=含义不同,匹配时必须使用对应关键字)。
4.1 sdp.media.media
匹配媒体描述中的媒体子字段,对应m=行,包含媒体类型、端口(可带/端口数)、传输协议及格式列表。
sdp.media.media; content:<media>其中<media>是遵循 SDP scheme 的媒体(m=)。示例:
sdp.media.media; content:"audio 30000 RTP/AVP 0 8 97 2 3"解析器parse_media_description(rust/src/sdp/parser.rs)对m=行做了较严格的约束:媒体类型仅接受audio、video、text、application、message五种;端口为 1~5 位数字(测试中70000因超范围解析失败);传输协议仅接受udp、RTP/AVP、RTP/SAVP。测试用例m=audio 40392 RTP/AVP 8 0解析为"audio 40392 RTP/AVP 8 0"。
4.2 sdp.media.session_info
匹配媒体描述中的会话信息子字段,对应媒体级i=行(媒体标题)。
sdp.media.session_info; content:<session_info>其中<session_info>是遵循 SDP scheme 的描述(i=)。示例:
sdp.media.session_info; content:"Session Description Protocol"4.3 sdp.media.connection_data
匹配媒体描述中的连接数据子字段,对应媒体级c=行。
sdp.media.connection_data; content:<connection_data>其中<connection_data>是遵循 SDP scheme 的连接(c=)。示例:
sdp.media.connection_data; content:"IN IP4 192.168.1.2"4.4 sdp.media.encryption_key
匹配媒体描述中的加密密钥子字段,对应媒体级k=行。
sdp.media.encryption_key; content:<encryption_key>其中<encryption_key>是遵循 SDP scheme 的密钥(k=)。示例:
sdp.media.encryption_key; content:"prompt"五、源码级原理:解析、注册与匹配链路
理解底层实现有助于判断匹配内容的边界条件与适用场景。SDP 支持链路贯穿以下几个文件:
1. Rust 解析器:rust/src/sdp/parser.rs 基于 nom 解析库实现。入口函数sdp_parse_message(rust/src/sdp/parser.rs)严格按照 RFC 4566 的会话描述格式顺序解析:v=(协议版本)→o=→s=→ 可选i=/u=/e=/p=/c=/b=→ 一个或多个t=/r=时间描述 → 可选z=/k=/a=→ 可选多个媒体描述(每个m=行后可跟媒体级i=/c=/b=/k=/a=)。解析结果保存在SdpMessage结构体中,其中bandwidths、attributes、media_description为列表字段,time_description为TimeDescription结构体数组(每个包含time与可选的repeat_time)。同文件内还包含覆盖各字段的单元测试(rust/src/sdp/parser.rs),可直接作为字段解析语义的参考。
2. SIP 消息体挂接:SDP 解析发生在 SIP 事务层。在 rust/src/sip/parser.rs 中,parse_request与parse_response在解析完请求行/状态行与头部后,对消息体调用opt(sdp_parse_message),因此Request.body/Response.body的类型是Option<SdpMessage>。这也印证了 SDP 关键字对 SIP 请求与响应(Direction: Both)均可生效。
3. 检测引擎注册:rust/src/sdp/detect.rs 中的SCDetectSdpRegister(rust/src/sdp/detect.rs)完成全部关键字的注册。每个关键字注册时:
- 使用
SigTableElmtStickyBuffer声明名称、描述与文档锚点 URL; - 调用
SCDetectSignatureSetAppProto(s, ALPROTO_SIP)将签名绑定到 SIP 应用层协议,意味着使用sdp.*关键字的规则只会在 SIP 流量上生效; - 通过
SCDetectHelperBufferMpmRegister或SCDetectHelperMultiBufferMpmRegister注册缓冲获取回调(*_get函数),后者依据方向从tx.request.body或tx.response.body取出对应字段内容交给 MPM 匹配。
值得注意的差异:sdp.origin、sdp.session_name、sdp.session_info、sdp.uri、sdp.email、sdp.phone_number、sdp.connection_data、sdp.timezone、sdp.encryption_key属于单值字段,使用helper_keyword_register_sticky_buffer注册;而sdp.bandwidth、sdp.time、sdp.repeat_time、sdp.attribute、sdp.media.media、sdp.media.session_info、sdp.media.connection_data、sdp.media.encryption_key由于字段本身可多次出现,注册为多缓冲区(multi buffer):local_id参数用于索引第 N 个字段值,规则可以对列表中的每个值分别匹配(如多组b=或a=行、多个媒体描述)。
4. 日志联动:解析出的 SDP 结构化数据同样用于 EVE JSON 日志输出。在 rust/src/sip/log.rs 中,SCSipLogJson会为请求与响应分别调用sdp_log输出sdp对象;其字段结构(attributes、bandwidths、connection_data、email、encryption_key、media_descriptions、origin、session_name、time_description等)定义在 etc/schema.json。检测与取证日志共享同一份解析结果。
六、实战:组合规则编写示例
将多个sdp.*关键字与 SIP 关键字组合,可以精确刻画 VoIP 会话特征。以下规则检测携带特定会话名、发起人与媒体类型组合的 SIP INVITE 请求:
alert sip any any -> any any (msg:"SDP: suspicious SIP call setup"; sdp.session_name; content:"SIP call"; sdp.origin; content:"SIPPS 105015165 105015162 IN IP4 192.168.1.2"; sdp.media.media; content:"audio 30000 RTP/AVP 0 8 97 2 3"; sid:1000001; rev:1;)编写时需注意的约束:
- 必须匹配解析后的完整字段值:
content匹配的是去掉行前缀(o=、s=等)之后的字符串,且多字段以空格分隔。例如sdp.origin匹配"SIPPS 105015165 105015162 IN IP4 192.168.1.2",而不是o=SIPPS ...。 - 字段边界由解析器决定:如连接数据中的地址必须合法(IP 校验)、媒体端口必须为 1~5 位数字、带宽修饰符仅
CT/AS/TIAS。与解析器语义不符的报文可能直接解析失败,规则自然不会命中。 - 仅作用于 SIP 流量:
sdp.*规则自动绑定ALPROTO_SIP,不会在其他协议上误报。 - 区分会话级与媒体级:同一字段名(如
i=、c=、k=)在会话级与媒体级存在两套关键字,规则需要按目标信息所在层级选择。 - 多值字段的匹配语义:
sdp.bandwidth、sdp.attribute、sdp.media.media等会针对字段列表中的每一项逐一提供缓冲,因此content命中任意一条即视为命中。
七、适用前提与版本说明
本文所述关键字以当前仓库文档 doc/userguide/rules/sdp-keywords.rst 为准,实现代码位于 rust/src/sdp/detect.rs 与 rust/src/sdp/parser.rs,属于 Rust 实现的 SIP/SDP 解析与检测模块(源码注释标明编写于 2024 年)。要使用这些关键字,需保证构建的 Suricata 包含 Rust 支持且启用了 SIP 应用层协议解析。规则调试时,可结合suricata -T(规则语法检查)与 EVE JSON 日志中输出的sip.sdp对象核对字段实际解析值,再据此调整content内容。
- 网络安全
【免费下载链接】suricata
Suricata is a network Intrusion Detection System, Intrusion Prevention System and Network Security Monitoring engine developed by the OISF and the Suricata community.
相关推荐
aiortc SDP 协议深度解析:会话描述协议的生成与解析终极指南
aiortc SDP 协议深度解析:会话描述协议的生成与解析终极指南 aiortc 是 Python 中基于 asyncio 的 WebRTC 和 ORTC 实
音视频通信Suricata 规则编写指南:integer-keywords 整数匹配关键字全解析
Suricata 规则编写指南:integer keywords 整数匹配关键字全解析 本文是一份面向 Suricata 规则编写者的技术指南,系统讲解基于无符
网络安全Suricata 解码层事件匹配:decode-event 规则关键字完全指南
Suricata 解码层事件匹配:decode event 规则关键字完全指南 decode event 是 Suricata 规则语言中用于匹配 解码层(de
网络安全
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考