type
Post
status
Published
date
Aug 31, 2022
slug
127
summary
snort规则关键字使用
tags
snort
category
技术分享
icon
password
漏洞编号
No.
同步状态
状态
已完成
Author
 

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 ,由于snortcontent的默认行为是使用长度最长的一个优先进行匹配,但长度长不代表最具有对应漏洞特征,假如短 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 就派上大用场了
notion image
使用 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)比较,若该值大于 16384byte_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 → containmagicnum0x1234 ,相反则为 ABAJP13_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之后的每一个字段连同了前两位的长度,攻击流量包如下
notion image
假设要判断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_extractcontent之后偏移 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中的offset
from_end 跳转将从有效载荷的末尾开始
 

举个栗子

以检测PE文件为例
PE 文件头由 64位的DOS MZ头和不固定长度的DOS块以及DOS PE头组成,MZ头的前两位固定为MZ,末尾四位的值指向PE头。这种情况下,由于DOS块长度不固定,无法通过指定偏移定位到PE头,更不能在整个数据包中寻找PE字符,要知道,PE文件有时候会很大,上万的字符中匹配到PE两个字符的误报概率太大了。
notion image
notion image
据此可以得到检测思路为通过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. 参考链接

 
WeiPHP 5.0 sql注入漏洞bind_followyonyou nc6.5 InvokerServlet 任意类调用执行漏洞调试