一谈到NIDS,这个产品最为人所诟病的往往就是大量的误报和漏报,满屏乱滚的误报使管理员麻木和厌烦,失去使用的兴趣,漏报则会使管理员怀疑NIDS的检测能力,明明主机已经被入侵了在NIDS的日志中却找不到有用的线索。对于NIDS产品,漏报和误报产生的原因是多方面,但其中最大的来源在于检测规则定义的不严谨。
针对已知的网络攻击,当前主流网络入侵检测检测及防护系统主要还是基于规则的, [被屏蔽广告]这是因为特定的攻击,特别是基于特定攻击代码的报文比较容易抽取其报文特征进行匹配,使用规则可以快速方便地实现检测能力的扩展。
网络攻击的本质就是利用目标系统设计或实现上的问题,从被攻击进程的角度看,攻击报文或在内容结构上,或在出现的时序上,或在流量的大小上,攻击报文一定是处于服务程序无法正确处理的畸形状态。限于篇幅,本文暂不讨论比较复杂的与时序及统计相关的攻击,只考虑相对比较简单的单请求攻击,要使攻击得以成功完成,网络攻击的报文的内容和结构必须满足某些必要的条件,这些必要的报文特征用于驱使受攻击进程的处理流程走到触发漏洞的操作,我们用特征集A表示这些必要的报文特征的集合。
攻击者通过编写攻击代码来实现对安全漏洞的利用,特定的攻击代码发出的报文除了必定包含的特征集A内的特征外一般还会包含一些本攻击代码特有的特征,比如特定的shellcode、特定的填充数据等,我们用特征集B表示特定攻击代码的报文特征。
基于规则的NIDS引擎实现一个基本检测框架,它提供给用户各种匹配选项和操作符作为应用接口,用户可以通过组合选项和操作符来描述关心的网络报文特征,对满足匹配条件的报文进行告警。NIDS厂商的规则支持部门通常会跟踪分析新出现的安全漏洞及攻击代码,提取特征编写出用于检测此攻击的NIDS规则,我们用特征集C表示用规则描述的攻击报文特征。
很明显特征集A和B的关系如下图:
+----------------+
| |
| +-------+ |
| | |<---- 特征集A:要实现攻击,报文所必须具有的内容或结构方面的特征
| | ============> 匹配特征增加,满足条件的报文减少
| | | |
| +-------+ |
| |<---- 特征集B:特定攻击代码发出的攻击报文特征
+----------------+
理想的攻击检测规则描述的特征集C应该与A完全重合,由于特征集A内的特征描述了所有攻击代码攻击报文所共有的特征,因而与特定的攻击代码无关,这样就避免了漏报。如果对于检测对象的协议类型所做假设正确,由于特征集A内的每个特征是造成攻击所必要的,缺一不可,这些特征中的只要有一个不满足都无法触发漏洞而完成攻击,所以也不存在误报的可能。图示如下:
+----------------+
| |
| +=======+ |
| " "<---- 特征集A:要实现攻击,报文所必须具有的内容
| " " | 或结构方面的特征
| " "<---- 特征集C:规则所描述的报文内容或结构的特征
| +=======+ |
| |<---- 特征集B:特定攻击代码发出的攻击报文特征
+----------------+
事实上由于规则描述能力的限制,大多数情况下规则无法完全描述出攻击报文内容和结构上的完成攻击所必需的特征,也就是说,特征集A与特征集C并不是完全重叠的,而是存在各种可能的关系:
如果特征集C是如下图中这样作为A的子集,那么规则将可能导致误报,但不会产生漏报:
+----------------+
| |
| +-------+ |
| | +---+ |<---- 特征集A:要实现攻击,报文所必须具有的内容或结构方面的特征
| | | |<------ 特征集C:规则所描述的报文内容或结构的特征
| | +---+ | |
| +-------+ |
| |<---- 特征集B:特定攻击代码发出的攻击报文特征
+----------------+
如果特征集C是如下图中这样包含了特征集A,那么规则将可能导致漏报,但不会产生误报:
+----------------+
| |
| +-------+ |
| | +---+ |<---- 特征集C:规则所描述的报文内容或结构的特征
| | | |<------ 特征集A:要实现攻击,报文所必须具有的内容或结构方面的特征
| | +---+ | |
| +-------+ |
| |<---- 特征集B:特定攻击代码发出的攻击报文特征
+----------------+
如果特征集C与A互不包含,只是如下图存在一个交集,那么规则将既可能产生误报也有可能导致漏报:
+----------------+
| |
| +-------+ |
| | |<---- 特征集C:规则所描述的报文内容或结构的特征
| | +---+--+ |
| | | | |<------ 特征集A:要实现攻击,报文所必须具有的内容或结构方面的特征
| +---+---+ | |
| | | |
| +------+ |<---- 特征集B:特定攻击代码发出的攻击报文特征
+----------------+
如果特征集C与A互不包含且没有交集,那么规则本身只是在检测特定的攻击代码发出的报文,而与攻击本身无关,通过简单修改攻击代码可以非常轻易地绕过这类规则的检测,要NIDS发生漏报还是误报完成掌握在攻击者手中:
+----------------+
| +-------+ |
| | | |
| | |<------- 特征集C:规则所描述的报文内容或结构的特征
| +-------+ |
| +------+ |
| | |<------ 特征集A:要实现攻击,报文所必须具有的内容或结构方面的特征
| | | |
| +------+ |<---- 特征集B:特定攻击代码发出的攻击报文特征
+----------------+
那么如何才能使特征集C与A尽可能地重合?这主要取决于两方面的努力:一、透彻地分析漏洞的成因和利用条件,目的是归纳出准确的特征集A;二、提高NIDS规则的描述能力,使之描述出的特征集C可以尽可能地接近特征集A。NIDS厂商的实力往往体现在这两方面的水准上,如果厂商没有持续而专业的漏洞及攻击的分析能力,在没有分析清楚漏洞细节的情况下胡乱设计NIDS规则必然会带来大量的漏报和误报,NIDS产品使用效果可想而知。下面通过分析一个实际漏洞攻击的NIDS规则设计来使上述看起来有些抽象的描述具体化,突显漏洞分析在设计NIDS规则过程中的极端重要性。
Serv-U是一个Windows平台下的FTP服务器软件,去年研究人员发现Serv-U服务器进程在处理MDTM命令的参数时存在一个栈溢出漏洞,远程攻击者只要能够登录进FTP服务器无需目录的写权限就可发动溢出攻击,在FTP服务器上执行任意指令从而实现对服务器的控制。由于Serv-U的使用非常广泛,几乎是Windows平台下首选的FTP服务程序,而且一般FTP服务器上保存有大量有价值的资料,所以对此漏洞的利用攻击非常流行。
如何检测针对此漏洞的攻击,开源的NIDS工具Snort给出了如下的检测规则:
alert tcp $EXTERNAL_NET any -> $HOME_NET 21 (msg:"FTP invalid MDTM command attempt"; flow:to_server,established; content:"MDTM"; nocase; pcre:"/^MDTM \d+[-+]\D/smi"; reference:bugtraq,9751; reference:cve,2001-1021; reference:cve,2004-0330; classtype:attempted-admin; sid:2416; rev:5;)
此规则描述的报文特征:
1. 目标端口为21的TCP包
2. 包含MDTM命令
3. 参数以一个或多个数字开头
4. 数字字串后紧跟“+”或“-”字符
5. “+”或“-”字符后紧跟至少一个非数字字符
这些特征形成特征集C。
这个特征集C是否与真正的攻击特征有足够的切合度呢?通过漏洞分析我们自然会得到结论。czy82在NSFOCUS技术论坛上发表过对此漏洞的详细分析,原文见:[url]http://bbs.nsfocus.net/index.php?act=SE&f=3&t=159298&p=299648[/url]
择其分析文章中对服务器处理命令及参数的代码分析片断如下:
====================================================================
[0]判断是否是"MDTM"命令
loc_434748: ; CODE XREF: .text:0043473A
.text:00434748 push 4 //比较四个字节
.text:0043474A push edi //edi存放命令字串的首地址
.text:0043474B lea eax, [esi+354h]
.text:00434751 push eax // 得到命令列表
.text:00434752 call near ptr unk_59C008 // 相当于Strncmp
.text:00434757 add esp, 0Ch
.text:0043475A test eax, eax
.text:0043475C jnz short loc_43476D //不是MDTM的话比较下一个命令SITE
.text:0043475E push edi //第二个参数是命令字串的首地址
.text:0043475F push ebx
.text:00434760 call loc_41FAE8 //相同的话跳到MDTM命令处理函数
.text:00434765 add esp, 8
.text:00434768 jmp loc_434AC7
[2] 对时间区域进行处理检测
.text:0041FBB6 loc_41FBB6: ; CODE XREF: sub_41FAE8+9Bj
.text:0041FBB6 push 20h
.text:0041FBB8 lea edx, [ebp+var_9FC] //ebp-9fc中存放全部命令
.text:0041FBBE push edx
.text:0041FBBF call sub_59BEB1 //找命令中的空格找到后把空格后
//的地址放在ebp-78中,也就是找文件名
.text:0041FBC4 add esp, 8
.text:0041FBC7 mov [ebp+var_78], eax
.text:0041FBCA test eax, eax
.text:0041FBCC jz loc_41FE6D //没有找到文件名跳,跳过去将处理
//mdtm autoexec.bat这类看文件时间的命令
.text:0041FBD2 lea edx, [ebp+var_9FC]
.text:0041FBD8 push edx
.text:0041FBD9 call sub_59BDA4 //得到命令长度
.text:0041FBDE pop ecx
.text:0041FBDF cmp eax, 10h //命令长度小于16跳
.text:0041FBE2 jb loc_41FE6D
.text:0041FBE8 lea ecx, [ebp+var_9FC]
.text:0041FBEE mov eax, [ebp+var_78]
.text:0041FBF1 sub eax, ecx //得时间区域长度不要紧张这儿没洞洞
.text:0041FBF3 cmp eax, 0Eh
.text:0041FBF6 jl loc_41FE6D //必须是大于等于14字节
.text:0041FBFC mov [ebp+var_88], 1
.text:0041FC06 xor edi, edi
.text:0041FC08 lea esi, [ebp+var_9FC]
.text:0041FC0E
.text:0041FC0E loc_41FC0E: ; CODE XREF: sub_41FAE8+141j
.text:0041FC0E movsx eax, byte ptr [esi]
.text:0041FC11 push eax
.text:0041FC12 call sub_5A1304
.text:0041FC17 pop ecx
.text:0041FC18 test eax, eax
.text:0041FC1A jnz short loc_41FC24
.text:0041FC1C xor edx, edx
.text:0041FC1E mov [ebp+var_88], edx
.text:0041FC24
.text:0041FC24 loc_41FC24: ; CODE XREF: sub_41FAE8+132j
.text:0041FC24 inc edi
.text:0041FC25 inc esi
.text:0041FC26 cmp edi, 0Eh
.text:0041FC29 jl short loc_41FC0E
.text:0041FC2B cmp [ebp+var_88], 0
.text:0041FC32 jz loc_41FD99 //判断时间区域的前14个字母
//如果不是数字跳到41fd99
//对时间的正确性进行检验
.text:0041FD4F cmp [ebp+var_5C], 7BCh
.text:0041FD56 jl short loc_41FD91 //年小于1980跳
.text:0041FD58 cmp dword ptr [ebp-5Ch], 81Bh
.text:0041FD5F jg short loc_41FD91 //年大于2075跳
.text:0041FD61 cmp dword ptr [ebp-60h], 1
.text:0041FD65 jl short loc_41FD91
.text:0041FD67 cmp dword ptr [ebp-60h], 0Ch
.text:0041FD6B jg short loc_41FD91 //月分只能是1-12
.text:0041FD6D cmp dword ptr [ebp-64h], 1
.text:0041FD71 jl short loc_41FD91
.text:0041FD73 cmp dword ptr [ebp-64h], 1Fh
.text:0041FD77 jg short loc_41FD91 //号数只能是1-31
.text:0041FD79 cmp dword ptr [ebp-6Ch], 0
.text:0041FD7D jl short loc_41FD91
.text:0041FD7F cmp dword ptr [ebp-6Ch], 3Bh
.text:0041FD83 jg short loc_41FD91
.text:0041FD85 cmp dword ptr [ebp-70h], 0
.text:0041FD89 jl short loc_41FD91
.text:0041FD8B cmp dword ptr [ebp-70h], 3Bh //分秒只能是0-59
.text:0041FD8F jle short loc_41FD99 //时间都合法了跳到41FD99
[3] 判断时间区域后面是否有+-号
.text:0041FD99
.text:0041FD99 loc_41FD99: ; CODE XREF: sub_41FAE8+14Aj
.text:0041FD99 ; sub_41FAE8+2A7j
.text:0041FD99 cmp [ebp+var_88], 0
.text:0041FDA0 jz loc_41FE30 //对于mdtm 20020201112233+111 autexec.bat这样的命令不跳
.text:0041FDA6 movsx eax, [ebp+var_9EE] //处理时间区域后的一个字串
.text:0041FDAD cmp eax, 20h
.text:0041FDB0 jz short loc_41FE1C //为空格跳
.text:0041FDB2 movsx eax, [ebp+var_9EE]
.text:0041FDB9 cmp eax, 2Dh
.text:0041FDBC jz short loc_41FDC3 //为减号跳!
.text:0041FDBE cmp eax, 2Bh
.text:0041FDC1 jnz short loc_41FE1C //不为加号跳到41FE1C!
[4] 对时间区域有+-号的情况进行处理
.text:0041FDC3 loc_41FDC3:
.text:0041FDC3 xor edi, edi
.text:0041FDC5 lea eax, [ebp+var_84] //得到时间区域的最后两位(ebp-84)
.text:0041FDCB lea esi, [ebp+var_9EE] //得到+号开始的地址
.text:0041FDD1 jmp short loc_41FDDA
.text:0041FDD3 loc_41FDD3:
.text:0041FDD3 mov dl, [esi]
.text:0041FDD5 inc edi //edi为记数器
.text:0041FDD6 mov [eax], dl
.text:0041FDD8 inc eax
.text:0041FDD9 inc esi
.text:0041FDDA
.text:0041FDDA loc_41FDDA:
.text:0041FDDA movsx ecx, byte ptr [esi]
.text:0041FDDD cmp ecx, 20h
.text:0041FDE0 jnz short loc_41FDD3 //遇到空格退出
//----------------------上面就是漏洞代码程序本意是把时间区域加号后面的四个字节放在ebp-84变量中
//但没有对长度进行检查,所以不但会覆盖ebp-84,如果是一个超长字串的话还会把ebp-54,ebp-78等变理覆盖!
Q&A:
[1]为什么执行到loc_41FE30处会产生程序异常呢?
因为ebp-78这个变量的值本来是要改变文件时间的文件名的地址,但是由于在loc_41FDC3处
对变量ebp-84的操作中会覆盖它的值,如果我们输入命令
quote mdtm 20020102112233+aaaaaaaaaaaaaaaaaaaaaaaaaa /autoexec.bat
那么这时ebp-78的值就成了61616161,而这个地址是不能仿问的,当然就产生异常了.
[2]产生异常后我们怎么执行代码呢?
在分析刚开始的时候我们已经知道程序正常的异常处理程序入口在ebp-50中,那么我们只能把
系统中有jmp ebx的代码的地址放到ebp-50中就可以了.然后ebp-54中放入nop nop jmp 6(9090EB04)
[3]要发送多少个A才能刚好覆盖ebp-50,ebp-54呢?
84h-54h=30h=48d
====================================================================
从以上的分析中我们可以抽取出要造成攻击的FTP请求报文所必须具有的特征:
1. 目标端口为FTP服务控制端口(通常是21)的TCP包
2. 包含MDTM命令
3. 命令参数以14个数字字节开始
4. 数字字节的前4字节表示年份的数字介于1980到2075之间
5. 年份后的2字节表示月份的数字介于1到12之间
6. 月份后的2字节表示日期的数字介于1到31之间
7. 日期后的2字节表示分钟的数字介于0到59之间
8. 分钟后的2字节表示秒的数字介于0到59之间
9. 数字字节后紧跟“+”或“-”字符
10. “+”或“-”字符之后紧跟48字节以上的非空格数据
11. 非空格数据后必须有一个或多个空格以分隔第二个参数
12. 空格后的包含至少一个字节的非空格字节做为第二个参数
这些特征形成特征集A。
对照Snort规则描述的特征集C,我们可以看到这两个特征集远不是重合的关系,只能算是有一个不大的交集,以此规则检测攻击出现误报和漏报几乎是必然的。
比如,带有如下payload的报文将导致误报:
“MDTM 1+abc”
带有如下payload的有效攻击报文将导致漏报:
“MDTM 20021122334455+1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA A”
既然已经分析清楚特征集A,在正则表达式强大描述能力的支持下,我们对Snort的规则进行改进使之与特征集A尽可能地接近,可以把匹配规则修改成如下这个样子:
alert tcp $EXTERNAL_NET any -> $HOME_NET 21 (msg:"FTP MDTM command overflow attempt"; flow:to_server,established; content:"MDTM"; nocase; pcre:"/MDTM\s+\d{14}[+-][^ ]{48,}\s+.+/smi"; reference:bugtraq,9751; reference:cve,2004-0330; classtype:attempted-admin; sid:2416; rev:5;)
这个规则匹配了特征集A中1、2、3、9、10、11、12编号的特征,这个改进后的规则形成的特征集C为特征集A的一个子集,此规则可能会导致误报而不可能产生漏报。
由此可见,漏洞及攻击分析在NIDS规则设计过程中具有极端重要性,它是规则设计的起点和基础,只有清楚地定义了攻击的特征集A,才有可能有针对性的设计出准确高效的NIDS检测规则。