type
Post
status
Published
date
Aug 31, 2022
slug
127
summary
snort规则关键字使用
tags
snort
category
技术分享
icon
password
漏洞编号
No.
同步状态
状态
已完成
Author
0. Snort简介1. 常见关键字1.1 content1.2 pcre2 byte系列2.1 byte_test格式举个栗子2.2 byte_extract格式举个栗子2.3 byte_jump格式举个栗子3. 总结4. 参考链接
0. Snort简介
作为入侵检测届大名鼎鼎的前辈,snort是每一个从事入侵检测工作的安全人员成长过程中所绕不开的。snort体量非常轻便,但出色的扩展性又成就了强大的流量检测功能,可谓真正的小而美,开源的代码被后来的众多入侵检测产品当作模板所借鉴、学习。
snort是基于模式匹配的入侵检测系统,指的是系统预先定义一些入侵的特征码,在数据包到达时将其与特征码相匹配,以此来判断数据包是否包含了入侵行为。而本文探讨的正是决定其检测能力的重要因素——规则的编写。
1. 常见关键字
一条简单的snort规则(检测uri中包含有
phpinfo.php
字符串的数据包)alert tcp $EXTERNAL_NET any -> $HOME_NET $HTTP_PORTS (content:"phpinfo.php"; http_uri;)
规则分为规则头和规则选项,规则头即检测动作
(alert,drop,pass,reject)
和五元组加上流量流向,这部分没有太多可琢磨的选项,重点是后续的规则选项不仅可以检测固定字符的数据,而且通过关键字的组合搭配可以检测复杂、多变的数据包。1.1 content
作为模式匹配,最重要、最常用、最好用、也最简单的就是
content
关键字,当执行content选项进行模式匹配时,会调用snort的字符串搜索算法Boyer-Moore
,对数据包内容进行测试。如果在payload中指定位置捕获到与content
字符串完全匹配的数据,则匹配成功,此处的完全匹配就是content
最大的特点了,即大小写敏感且完全作为字符串,其内容只有两种形式——一种是与特征码一模一样的字符串,另一种是十六进制,需要用竖杠包裹。例如,上述
content
也可以写成content:"phpinfo|2e|php";
除此之外,content唯一可以变通的就是取反,例如,想检测
uri
中不包含phpinfo.php
字符串的情况可以编写如下content:!"phpinfo|2e|php"; http_uri;
想要其大小写不敏感,只需要在其后加入
nocase;
即可,以上是最基础和简单的用法接下来是content 的一个重要修饰词
fast_pattern
,由于snort
对content
的默认行为是使用长度最长的一个优先进行匹配,但长度长不代表最具有对应漏洞特征,假如短 content
更具特殊性,fast_pattern
便大有用处。fast_pattern
用于仅选择其修饰的content
用于评估,若payload命中该content
才会对规则进行评估,否则跳过,这样显然可以通过提取每条规则中的独特特征大大缩短snort
的性能开销。同时,
only
可用于指定 fast_pattern
修饰的content
仅用于快速模式,而不作为规则评估注:fast_pattern:only;
修饰的content (1)不区分大小写;(2)不能取反;(3)不能有位置修饰符
1.2 pcre
pcre
允许使用与perl
兼容的正则表达式进行评估,所有的语法与普通正则一致,可在 该网站上查询到所有可用语法,并可在线校验正则有效性
pcre是应对复杂多变的流量特征时不可或缺的一个关键字,同时也比较占用性能资源,但倘若拥有一条严谨、完美的正则,往往令评估事半功倍。
2 byte系列
在HTTP协议中不常用,但是在其他TCP/UDP协议中常用且非常高效的几个关键字,也正是这些关键字使得简短的规则变得变幻莫测。这里举例三个通过动态获取数据包中不固定位置字符并能据此进行其他操作的关键字。
2.1 byte_test
顾名思义,是取指定位置上的特定值,使用运算符与给定值进行测试字节字段。 能够测试二进制值或将具有代表性的字节字符串转换为等效的二进制并对其进行测试,结果为true则命中
格式
byte_test:<bytes_to_convert>,[!]<operator>,<value>,<offset>[,<relative>]\ [,<endian>][,string,<number type>][,dce][,bitmask<bitmask_value>];
其中:
byte_to_convert
从数据包取得的字节数 = 1-10
ps:不使用dce时可使用的值为1-10,使用dce时的值为1,2,4
operator
对测试值执行的操作 = < = > <= >= & ^
也就是等于还是小于大于.....value
与测试值进行比较的转换后的值 = 0-4294967295
offset
偏移量 = -65535-65535
relative
使用相对于上一个模式匹配的相对偏移endian
正在读取的数字的字节序类型,有默认 big
大端和 little
小端string
数据以字符串形式存在数据包中number type
转换后的字符串数据以何种形式表示:hex - 十六进制;dec - 十进制;oct - 八进制dce
使用DCE/RPC 2 预处理器执行 SMB 分段和 DCE/RPC 碎片整理,依此确定要转换的值的字节顺序bitmask
对转换的字节进行 与
运算举个栗子
OpenSSL心脏滴血(HeartBleed)漏洞CVE-2014-0160,漏洞利用是攻击者改造带有特殊
payload_length
的心跳包发送给服务器,由于HeartBeat
扩展不能正确识别恶意请求,导致服务器返回了超出设定值的信息,产生了溢出,造成数据泄露。可见,关键在于
payload_length
的长度是否异常,但这是一个阈值,使用content
或者pcre
无法准备判断其值大小,这时候 btye_test
就派上大用场了使用
byte_test
取相应偏移位置上的payload_length
值,比较其是否大于限定的 16k
正常请求响应字节即可判断是否为恶意请求content:"|18 03|"; depth:2; content:"|01|"; distance:3; within:1; byte_test:2,>=,16384,0,relative,big;
即相对前一个
content
匹配到的|01|
后面,偏移0个字节后,取其后2个字节(payload_length
)转换成十进制与16384
(16k)比较,若该值大于 16384
,byte_test
返回true
,即命中。2.2 byte_extract
取得指定位置上的数据保存到一个变量中,此变量可在后续规则中使用,而不是使用硬编码值,一般与byte_test结合在一起
格式
byte_extracr:<bytes_to_convert>,<offset>,<name>,[,<relative>]\ [, multiplier <multiplier value>][, <endian>][, string][, hex][, dec][, oct] \ [, align <align value>][, dce][, bitmask <bitmask>];
byte_to_convert
从数据包取得的字节数offset
偏移量name
给取得的变量命名relative
使用相对于上一个模式匹配的相对偏移举个栗子
Apache Tomcat AJP连接器任意文件访问CVE-2020-1938,又称GhostCat幽灵猫。
AJP协议是定向包(面向包)协议,采用二进制形式代替文本形式,协议由
magic number
+ length
+ AJP13_FORWARD_REQUEST
组成,其中,由 server → contain
的 magicnum
为 0x1234
,相反则为 AB
,AJP13_FORWARD_REQUEST
的结构如下:AJP13_FORWARD_REQUEST := prefix_code (byte) 0x02 = JK_AJP13_FORWARD_REQUEST method (byte) protocol (string) req_uri (string) remote_addr (string) remote_host (string) server_name (string) server_port (integer) is_ssl (boolean) num_headers (integer) request_headers *(req_header_name req_header_value) attributes *(attribut_name attribute_value) request_terminator (byte) OxFF
而且,version之后的每一个字段连同了前两位的长度,攻击流量包如下
假设要判断payload中是什么协议,如果仅用
content
在整个数据包中任意位置匹配,结果必定是不可靠的,但是通过对AJP协议的了解,可知 version
在相对magic num
后4位,虽然不同协议version
的长度不同,但可以根据数据包中在协议之前记录的长度,来判断其后这段长度内的payload中是否有 HTTP
字符,示例规则content:"|12 34|"; depth:2; content:"|02|"; within:1; distance:2; byte_extract:2,1,protocol_len,relative; content:"HTTP"; within:protocol_len;
byte_extract
对content
之后偏移 1 个字节取 2 个字节的值即 0x08
,命名为protocol_len
,后续的content
判断HTTP
是否在其后 8 个字节内,结果为true,规则命中。2.3 byte_jump
取对应偏移位置上的值,跳过payload中对应该值数目的字节
格式
byte_jump:<bytes_to_convert>, <offset> [, relative][, multiplier <mult_value>] \ [, <endian>][, string, <number_type>][, align][, from_beginning][, from_end] \ [, post_offset <adjustment value>][, dce][, bitmask <bitmask_value>];
byte_to_convert
从数据包取得的字节数offset
偏移量multiplier value
将计算的字节数乘以 <value> 并向前跳过该字节数。relative
使用相对于上一个模式匹配的相对偏移align
将转换的字节数四舍五入到下一个 32 位边界from_beginning
从数据包payload的开头而不是从数据包中的当前位置向前跳过,类似content中的offsetfrom_end
跳转将从有效载荷的末尾开始举个栗子
以检测PE文件为例
PE 文件头由 64位的
DOS MZ
头和不固定长度的DOS
块以及DOS PE
头组成,MZ头的前两位固定为MZ
,末尾四位的值指向PE头。这种情况下,由于DOS块长度不固定,无法通过指定偏移定位到PE头,更不能在整个数据包中寻找PE
字符,要知道,PE文件有时候会很大,上万的字符中匹配到PE
两个字符的误报概率太大了。据此可以得到检测思路为通过MZ头末尾四位的值找到对应位置上的字符判断是否为PE头,这时候
byte_jump
出马再合适不过了示例检测代码
content:"|4d 5a|"; byte_jump:4,58,relative,little; content:"PE|00 00|"; distance:-64; within:4;
首先定位MZ头中的
magic number
,去除这2位再去掉最后4位的长度,64字节的MZ头中间还有58字节,跳过这58字节,后续的4个字节即是存储了PE头在DOS头中绝对偏移位置的数值,并且在数据中是以小端存储的,接着匹配四位PE头 content:"PE|00 00|";
,由于数据中记录的偏移位置是绝对偏移,从0开始,而byte_jump
取完数后,此时的偏移在DOS MZ头之后,跳转之后并不直接指向实际PE头的位置,而是在其后多出了64字节的位置,此时便需要使用 distance:-64;
去掉多出来的字节,使得偏移回到PE头实际正确的位置,在4字节内匹配到 PE|00 00|
,规则命中。3. 总结
本文主要重点讲述snort规则中Byte系列关键字的用法,通过三个具体的实例分析关键字及每个修饰符的含义,帮助使用者理解用法。在实际的入侵检测中,如果能够用好这几个关键字,可以在特殊字节流协议,例如SMB、OpenSSL、RPC等漏洞方面,显著提升检测率、降低误报率,并且可以节省性能资源,不仅事半功倍,还能使snort的能力发挥到最大化。
4. 参考链接
- 作者:3R1CCHENG
- 链接:https://notion-3r1c.vercel.app//article/127
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。