【转载】Snort 2.x数据区搜索规则选项的改进
一直在学用的Snort。感觉还不错,可惜很多地方用不明白。Snort 2.x数据区搜索规则选项的改进
创建时间:2005-09-27
文章属性:原创
文章提交:stardust (stardust_at_xfocus.org)
Snort 2.x的规则选项与2.0以前的版本相比有了相当大的改进,有必要介绍和分析一下。
首先翻译一下Snort使用手册中相关的规则选项说明。由于这个手册写的极烂,很多地方意思表述重复冗长,用词不准确,而且还有些明显的错误,应该仔细说明的地方反而没有说明,叙述顺序上也混乱的很,还有让人不可理解的是手册中的示例规则本来就是简单的文本竟然采用了图片的格式,简直就是一个垃圾手册的范例,所以在翻译时没有完全参照,而是融入和补充了我认为正确的说法,还调整了一下某些选项说明的顺序。这个文档写于一年多前,当时的原文链接为 http://www.snort.org/docs/snort_manual/node14.html ,对于当前的网站,对应的链接差不多是 http://www.snort.org/docs/snort_manual/node21.html 。
----------------------------------------------------------------------------------------
2.5.1 content选项
content选项是Snort提供的多个选项中比较重要的一个。它可以使用户搜索数据包净载中特定的内容并相引发相应的动作。当执行一个content选项匹配时,程序调用Boyer-Moore模式匹配例程检查包内容中是否包含了此内容,如果选项中指定的数据正好包含在数据包的净载中,那么检查返回成功,规则中其他的选项会继续得以执行。需要小心的是默认的content选项匹配是关心大小写的。
选项中指定的数据可能会比较复杂;它可能包含混和的文本和二进制数据。二进制数据通常用管道(|)符号括起来并以字节码的方式指定。字节码是以十六进制的格式表示的,这样可以很方便地描述复杂的二进制数据。示例的Snort规则中就包含了混合的文本及二进制数据。
在一条规则可以指定多个content选项,这样可以使规则尽可能少地出现误报的情况。
如下字符在content选项内容中出现时必须被转义(如何转义至少在这个手册里的此节没说,我补充一下,有两个方法:1. 使用前导“\”字符 2. 使用字节的二进制表示方式,比如用“|3A|”表示“:”):
: ; \ "
如果content选项内容之前放置了“!”字符,则表示在数据中不包含选项内容时引发报警。
2.5.1.1 格式
content: [!] "";
content选项可以带有多个修饰选项。修饰选项可以影响紧接之前的content选项的工作方式,这些修饰选项如下:
1.
nocase
2.
rawbytes
3.
depth
4.
offset
5.
distance
6.
within
2.5.1.2 示例
alert tcp any any -> any 139 (content:"|5c 00|P||00|I|00|P|00|E|00 5c|";)
混和了二进制数据和文本的content选项内容
alert tcp any any -> any 80 (content:!"GET";depth:3;nocase;)
取反匹配,此规则匹配数据包净载的前三个字节不是“GET”的情况
2.5.2 nocase修饰选项
nocase选项修饰紧接之前的content选项,使之在匹配时忽略大小写。
2.5.2.1 格式
nocase;
2.5.2.2 示例
alert tcp any any -> any 21 (msg:"FTP ROOT"; content:"USER root"; nocase;)
带有nocase修饰选项的content规则
2.5.3 rawbytes修饰选项
rawbytes选项修饰紧接之前的content选项,使之只匹配最原始的数据,忽略那些预处理器所做的解码。
2.5.3.1 格式
rawbytes;
2.5.3.2 示例
alert tcp any any -> any 21 (msg: "Telnet NOP"; content: "|FF F1|"; rawbytes;)
上面的示例使content选项匹配最原始的网络流量,而不是经过telnet解码器解码后的数据。
2.5.4 depth修饰选项
depth选项修饰紧接之前的content选项,允许用户指定在数据区中搜索特定模式的深度。如果设置depth等于5,则表示在数据包净载中从某个起点(如果没有下面论及的另一个offset修饰选项,则默认为数据区净载的开头)开始的5个字节中匹配content选项内容指定的模式。
2.5.4.1 格式
depth: ;
2.5.5 offset修饰选项
offset选项修饰紧接之前的content选项,允许用户指定从数据包净载的哪个字节开始进行匹配。如果设置offset等于5,Snort会在数据区净载的前5个字节以后开始搜索content选项内容指定的模式。
2.5.5.1 格式
offset: ;
alert tcp any any -> any 80 (content: "cgi-bin/phf"; offset:4; depth:20;)
组合使用了content,offset,depth选项的规则,从数据包净载的第5个字节开始的20字节内查找“cgi-bin/phf”字串。
2.5.6 distance
distance选项修饰紧接之前的content选项,允许用户指定相对于上一个content选项匹配成功的串尾再加多少字节开始搜索distance所修饰的这个content选项指定的匹配内容。此修饰选项与offset类似,只不过是相对于上一个content选项匹配成功的串尾而不是相对于数据区净载的开头。
2.5.6.1 格式
distance: ;
2.5.6.2 示例
alert tcp any any -> any any (content:"ABC"; content: "DEF";distance:1;)
此规则示例匹配的目的相当于正则表达式:/ABC.{1}DEF/
2.5.7 within
within选项修饰紧接之前的content选项,允许用户指定从上一个content选项匹配成功的串尾加上distance选项指定的字节数开始多少字节内搜索当within所修饰的这个content选项指定的匹配内容。此修饰选项与depth类似,只不过是相对于上一个content选项匹配成功的串尾加distance选项指定的字节数而不是相对于数据区净载的开头。
2.5.7.1 格式
within: ;
2.5.7.2 示例
alert tcp any any -> any any (content:"ABC"; content: "EFG"; within:10;)
2.5.8 uricontent
uricontent选项用于匹配正规化处理后URI字段。这意味着如果你写规则试图去匹配那些可能会被正规化处理掉的数据,比如常见用于遍历目录的“%2f”串,那么匹配就会失败。
举例如下,URI:
/scripts/..%c0%af../winnt/system32/cmd.exe?/c+ver
会被正规化为:
/winnt/system32/cmd.exe?/c+ver
/cgi-bin/aaaaaaaaaaaaaaaaaaaaaaaaaa/..%252fp%68f?
会被正规化为:
/cgi-bin/phf?
用uricontent选项写规则的时候必须指定匹配正规化以后的内容,不能包含遍历目录的字串,因为这些会被正规则化掉。如果想匹配正规化以前的数据请使用content选项。
uricontent与HTTP检测预处理器结合使用。
2.5.8.1 格式
uricontent:[!];
2.5.9 isdataat选项
此选项用于确认在指定的位置有多少字节的数据,有修饰选项指定相对于上一个content选项匹配成功的串尾开始。
2.5.9.1 格式
isdataat:[,relative];
2.5.9.2 示例
alert tcp any any -> any 111 (content:"PASS"; isdataat:50,relative; content:!"|0a|"; distance:0;)
此规则在数据区净载中搜索“PASS”字串,然后再确认至少在后面还有50字节的数据,并且不包含换行符。
2.5.10 pcre选项
pcre选项允许用户使用与PERL语言相兼容的正则表达式。相关正则表达式的具体细节参看PCRE的Web站点:http://www.pcre.org
2.5.10.1 格式
pcre:[!]"(//|m)";
在表达式后的修饰符设置编译正则表达式的一些标志。
Perl兼容的修饰符
+-+---------------------------------------------+
|i| 对大小不敏感 |
+-+---------------------------------------------+
|s| 在点转义符号中包含换行符 |
+-+---------------------------------------------+
| | 一般情况下被匹配的缓冲区是作为一个大字符串|
|m| 的,^和$分别匹配串头和串尾。当设置了m修饰 |
| | 符,^和$匹配跟紧跟换行符和紧先导于换行符的|
| | 情况。 |
+-+---------------------------------------------+
|x| 要匹配的模式中的空格符被忽略,除非是被转义|
| | 过的或在一个字符集中。 |
+-+---------------------------------------------+
PCRE兼容的修饰符
+-+---------------------------------------------+
|A| 模式必须在缓冲区的开头匹配到(同^) |
+-+---------------------------------------------+
| | 设置指定的$必须匹配到缓冲区末尾。如果不用E|
|E| 修饰符,$则可能只匹配到串尾之前换行符。 |
+-+---------------------------------------------+
|G| 在默认情况下不使用“贪婪”模式,只有在模式后|
| | 面跟了“?”字符的情况下贪婪。 |
+-+---------------------------------------------+
Snort特定的修饰符
+-+---------------------------------------------+
|R| 此匹配相对于前一个匹配成功串尾开始 |
| | (类似于distance:0) |
+-+---------------------------------------------+
|U| 匹配解码后的URI缓冲区(类似于uricontent) |
+-+---------------------------------------------+
|B| 不使用解码后的缓冲区(类似于rawbytes) |
+-+---------------------------------------------+
修饰符R和B不能同时使用。
2.5.10 示例
alert ip any any -> any any (pcre:"/BLAH/i";)
2.5.11 byte_test修饰符
把数据包净载中特定位置的字串转换为数值类型与指定的值进行比较。
2.5.11 格式
byte_test: , , ,[, ,,,,,,]
bytes_to_convert
抽取的字节数
operator
要执行的操作符(,=,!,&)
value
与从转换出来的数据进行比较的值
relative
相对于从上次成功匹配的串尾开始
big
以big endian字节序方式处理
little
以little endian字节序方式处理
string
被转换的数据在包里是以字符串方式储存的
hex
转换出来的数据以十六进制表示
dec
转换出来的数据以十进制表示
oct
转换出来的数据以八进制表示
2.5.11.2 示例
alert udp any any -> any 1237 (byte_test: 10, =, 1234567890, 0, string, dec; msg:"got 1234567890!";)
alert udp any any -> any 1238 (byte_test: 8, =, 0xdeadbeef, 0, string, hex; msg:"got DEADBEEF!";)
2.5.12 byte_jump选项
byte_jump选项用来从数据包的某个位置获取一些字节,转换成数值类型,跳过数值里显示的字节数到数据包里新位置进行进一步的匹配操作。这样可以利用网络数据包里的数据进行相对位置的跳转。
2.5.12.1 格式
byte_jump: ,[, ,,,,,,,]
bytes_to_convert
要转换的字节数
offset
从数据区净载的哪个位置开始处理
relative
从上次成功匹配的串尾开始
big
以big endian字节序方式处理(默认)
little
以little endian字节序方式处理
string
被转换的数据在包里是以字符串方式储存的
hex
转换出来的数据以十六进制表示
dec
转换出来的数据以十进制表示
oct
转换出来的数据以八进制表示
align
把转换出来的数值圆整到32-bit边界的值
2.5.12.2 示例
alert udp any any -> any 32770:34000 (content: "|00 01 86 b8|"; content: "|00 00 00 01|"; distance: 4; within: 4; byte_jump: 4, 12, relative, align; byte_test: 4, >, 900, 20, relative; msg: "statd format string buffer overflow";)
2.5.13 regex
regex选项已经被PCRE选项所代替
2.5.14 content-list
content-list选项已经被废弃。
----------------------------------------------------------------------------------------
下面对新版Snort的规则选项作些分析。
与之前版本的Snort相比,新版规则主要有以下几个具体的改进:
1. 给content选项增加了distance和within修饰选项
2. 增加了isdataat选项
3. 增加了byte_jump选项
4. 增加了byte_test选项
5. 改进了正则表达式
其增加的核心功能就是引入了数据匹配相对偏移的概念。以往写Snort规则碰上的最大限制就是对于数据包内容的匹配我们必须硬编码进模式可能在数据包净载中可能出现的位置,由于网络协议的灵活性及数据包分片的可能性,我们所要匹配的数据在数据包净载中的偏移并不容易确定,这就会引起很大的漏报或误报。
比如对于MDaemon form2raw.cgi溢出攻击的检测,一个攻击请求的数据包净载可以用如下的正则表达来匹配:
\s+\S*/form2raw.cgi[?|\s]\S*From=[^&]{100,}
如果使用以前版本的Snort规则选项,由于要匹配的模式是只能实现如下的规则:
alert tcp !$HOME_NET any -> $HOME_NET 3000 (msg:"MDaemon form2raw.cgi overflow attack!";content:"/form2raw.cgi";nocase;content:"From=";nocase;offset:17;depth:15;content:!"&";offset:30;depth:120;)
这样的规则只能检测如下比较“正常”的攻击净载情况:
"GET /form2raw.cgi?From=AAA...AAA&To=me@hello.org&Subject=hi&Body=hello HTTP/1.0"
不考虑各种对抗IDS的URI变形方式,就是仅仅在“/form2raw.cgi”前带了比较长目录的情况就会导致漏报,但我们也不能过度扩大对“From=”串的搜索范围,因为那样可能会带来更多的误报。
content选项新增的distance和within修饰选项,可以使匹配多个有一定顺序和固定间隔的模式成为可能,这对于匹配网络流量中那些结构相对固定的协议头结构(比如SMB,Sun-RPC,DNS等)非常有用,可以很方便地检查结构中的某些字段。
对于上面的form2raw.cgi检测例子,借助于content的新修饰选项,我们可以把规则改进为如下的形式:
alert tcp !$HOME_NET any -> $HOME_NET 3000 (msg:"MDaemon form2raw.cgi overflow attack!";content:"/form2raw.cgi";nocase;content:"From=";nocase;distance:1;content:!"&";within:120;)
这样,不管“From=”串在数据包净载中的偏移如何,我们可以都以保证检测到接于其后超长的参数串了,极大地提高了检测的准确性。
新增的isdataat主要用于判断相对某个匹配后面还有多少的数据,主要用于类似FTP的协议命令后的参数是否超长,由于isdataat判断开始的偏移是相对的,这比以前判断整个净载长度的办法要精确的多,因为一个净载里可能包含了多个命令,而且在命令之前可能还有数据。
新增的byte_jump选项是非常强大的,使用它可以使我们动态地根据网络数据包中值来选择匹配开始的偏移成为可能,实际上这样已经部分地实现了协议解码的能力,我们可以根据协头中包含的字段偏移信息来直接定位我们关心的字段在数据包中的位置,可以实现非常精确地匹配。有关此选项会在下面结合例子详细介绍。
新增的byte_test选项主要用于检查可能的协议数据结构中某些字段的值大小,最常见的就是某个串的长度。有关此选项会在下面结合例子详细介绍。
新版的Snort加强了正则表达式,在很多规则里普遍使用了正则表达式。
下面介绍一个结合使用了多个选项的rpc.statd格式串攻击的规则来演示新规则选项的强大特性。
一个rpc.statd攻击数据包可能是如下这个样子,攻击者使用一个带有超长的格式串参数的rpc.statd包来利用格式串攻击来获取Linux系统的root用户权限:
Frame 3 (1118 bytes on wire, 1118 bytes captured)
Ethernet II, Src: 00:06:5b:9f:95:37, Dst: 00:01:02:86:a6:f3
Internet Protocol, Src Addr: 192.168.5.223 (192.168.5.223), Dst Addr: 202.99.11.220 (202.99.11.220)
User Datagram Protocol, Src Port: 816 (816), Dst Port: 32770 (32770)
Remote Procedure Call, Type:Call XID:0x42688ef6
XID: 0x42688ef6 (1114148598)
Message Type: Call (0)
RPC Version: 2
Program: STAT (100024)
Program Version: 1
Procedure: STAT (1)
The reply to this request is in frame 4
Credentials
Flavor: AUTH_UNIX (1)
Length: 32
Stamp: 0x3db04376
Machine Name: localhost
length: 9
contents: localhost
fill bytes: opaque data
UID: 0
GID: 0
Auxiliary GIDs
Verifier
Flavor: AUTH_NULL (0)
Length: 0
Network Status Monitor Protocol
Program Version: 1
V1 Procedure: STAT (1)
Name: ????????????????8x%8x%8x%8x%8x%8x%8x%8x%8x%236x%n%137x%n%10x%n%192x%n??????????????????????????????????????????????????????????????????????????
length: 999
contents: ????????????????8x%8x%8x%8x%8x%8x%8x%8x%8x%236x%n%137x%n%10x%n%192x%n????????????????????????????????????????????????????????????????????????
fill bytes: opaque data
000000 01 02 86 a6 f3 00 06 5b 9f 95 37 08 00 45 00 ........[..7..E.
001004 50 00 00 40 00 01 11 d8 d6 c0 a8 05 df ca 63 .P..@..........c
00200b dc 03 30 80 02 04 3c 58 79 42 68 8e f6 00 00 ...0... any 32770:34000 (content: "|00 01 86 b8|"; content: "|00 00 00 01|"; distance: 4; within: 4; byte_jump: 4, 12, relative, align; byte_test: 4, >, 900, 20, relative; msg: "statd format string buffer overflow";)
在分析规则之前贴一下来自《TCP/IP Illustrated》的Sun-RPC协议的头结构:
+----------------------+
| transaction ID (XID) |4字节
+----------------------+
| call(0) |4字节
+----------------------+
| RPC version(2) |4字节
+----------------------+
| program number |4字节
+----------------------+
| version number |4字节
+----------------------+
| procedure number |4字节
+----------------------+
| |
| credentials |
| |
+----------------------+
| |
| verifier |
| |
+----------------------+
| |
| procedure |
| parameters |
| ... |
| |
+----------------------+
Snort的演示规则由两个content选项、一个byte_jump选项、一个byte_test选项组成。
第一个content选项:content: "|00 01 86 b8|",用于匹配SunRPC头结构中的program number:\x00\x01\x86\xb8 代表网络字节序的100024,100024是statd的程序号。这个串在上例攻击包中的偏移是0x36 。
第二个content选项:content: "|00 00 00 01|"; distance: 4; within: 4; ,表示从上一次匹配成功的串尾开始,在上例中攻击包的0x3a开始,跳过4个字节,从那儿开始匹配4个字节,也就是说从偏移为0x3e的地方开始匹配4个字节是不是\x00\x00\x00\x01。从上面的结构看跳过的是version number,因为我们不关心那个,匹配的目标就是procedure number,如果是0x00000001,则表示是STAT过程。
第三个是byte_jump选项:byte_jump: 4, 12, relative, align; ,表示从上一次匹配成功的串尾开始,在上例中攻击包的0x3e+0x04=0x42开始,跳过12个字节,也就是0x42+12=0x4e的偏移开始取4个字节,转换成整数并圆整到4的倍数,然后从0x4e+0x04=0x52开始跳过转换出来的整数个字节。从上例的攻击包来看偏移0x4e开始的四个字节转换为网络字节序的值是0x00000009,圆整到4的整数为12,也就是说引擎下一次匹配开始的偏移会跳到0x4e+4+12=0x5e地方开始。这个byte_jump操作的目的是跳过长度不确定的STAT过程的主机名字段。
第四个是byte_test选项:byte_test: 4, >, 900, 20, relative; ,表示当前的指针开始,也就是上面byte_jump跳到的0x5e偏移开始,跳过20字节,也就是从0x5e+20=0x72偏移开始,取4个字节转换成整数与900比较,如果大于则满足本规则的最后一个条件。从上例的攻击包数据来看,从偏移0x72开始取的四个字节转变出来的值是0x000003e7=999,大于900,所以是一个攻击。跳过的那20个字节从ethereal解码来看分别是UID、GID、Auxiliary GIDS、Flavor、Length字段,它们都是4字节长的,总共20字节。
以下是规则对ethereal解码的数据包的匹配要点:
Remote Procedure Call, Type:Call XID:0x42688ef6
XID: 0x42688ef6 (1114148598)
Message Type: Call (0)
RPC Version: 2
Program: STAT (100024) ; content: "|00 01 86 b8|" , 0x0186b8==100024
Program Version: 1 ; 跳过这4个字节,由第二个content选项的 distance:4 修饰选项表示这个意思
Procedure: STAT (1) ; content: "|00 00 00 01|"; distance: 4; within: 4;
The reply to this request is in frame 4
Credentials
Flavor: AUTH_UNIX (1)
Length: 32
Stamp: 0x3db04376
Machine Name: localhost
length: 9 ; byte_jump: 4, 12, relative, align; 从这取四个字节转换成整数跳过
contents: localhost
fill bytes: opaque data
UID: 0
GID: 0
Auxiliary GIDs
Verifier
Flavor: AUTH_NULL (0)
Length: 0
Network Status Monitor Protocol
Program Version: 1
V1 Procedure: STAT (1)
Name: ????????????????8x%8x%8x%8x%8x%8x%8x%8x%8x%236x%n%137x%n%10x%n%192x%n??????????????????????????????????????????????????????????????????????????
length: 999 ; byte_test: 4, >, 900, 20, relative; 跳过之前的20字节,在这取四个字节转变成整数,与900比较,如果大于,则认定为攻击。
contents: ????????????????8x%8x%8x%8x%8x%8x%8x%8x%8x%236x%n%137x%n%10x%n%192x%n????????????????????????????????????????????????????????????????????????
fill bytes: opaque data
总结:
新版Snort规则选项引入的相对偏移概念很大程度上提高了攻击检测规则的灵活性和准确性,特别是byte_jump选项的引入部分弥补了Snort不进行应用层协议解码的弱点。
页:
[1]