网络扫描技术揭秘:原理、实践与扫描器的实现
上QQ阅读APP看书,第一时间看更新

第1章 绪论

网络在人们生活中占据了越来越重要的位置,人们以各种方式访问网络,在享受网络带来的方便的同时,也越来越多地感受到网络安全所潜在的和已带来的问题。

作为一个用户,自己的主机是否安全;作为网络管理员,所管理和维护的网络是否安全,解决这些问题除了及时打上操作系统的最新补丁以及安装防火墙和防病毒软件之外,是否还有别的方法呢?答案很简单,就是以一个访问者的角度,全面审视一下自己的网络有什么,没有什么,还有哪些缺陷或漏洞,这正是网络扫描的主要功能之一。

网络扫描的使用者不同,扫描的目的和范围也不同,如前所述,用户和网络管理员所关注的扫描目的和范围就有很大的不同。扫描的目的不同,扫描的原理当然也不相同,同时,客观上由于扫描程序所在的主机、要扫描的主机不同,操作系统不同,配置不同,而使用的扫描器、扫描方式也会不尽相同。正因为此,可以发现网络扫描是一个综合的技术领域,当然,根据实际情况,具体问题可以具体分析;同时,也可以看出任何一项扫描技术都具有一定的局限性。

网络扫描不是自网络出现就专门设计的一种应用服务,而是当网络发展到一定阶段后,不同程序设计者根据不同的需求而产生的。同时,由于网络扫描不是一种通用的应用服务,所以并没有产生相应的行业标准。纵观整个网络扫描史,可以分为手工扫描、使用通用扫描工具、设计专用扫描工具三个阶段。

最后,本章对当前仍然广泛存在的几个与网络扫描有关的漏洞进行了简述。

1.1 网络安全的概念

网络安全是指网络中的硬件系统、软件系统及其系统中涉及的数据因受到预设的保护,而可以连续、可靠、正常地运行,并不因偶然事故或者恶意处理而遭受破坏、非法更改、信息泄漏。网络安全从其本质上来讲就是网络上的信息安全和服务安全,从广义来说,凡是涉及网络上信息的保密性、完整性、可用性、真实性和可控性的相关技术和理论都是网络安全的研究领域。网络安全涉及计算机技术、网络技术、通信技术等多种技术,是一门汇集信息论、密码学、数论等多种学科的综合性学科。

网络安全更多看重的是应用实践,因为实践才是检测网络安全的唯一标准。同时,由于网络安全涉及网络的所有方面,故网络安全的关键在于培养网络用户的安全意识,只有用户具备良好的安全意识,各网络安全工具才能更好地结合,产生最佳效果。

1.2 网络扫描的概念

网络扫描是根据对方服务所采用的协议,在一定时间内,通过自身系统对对方协议进行特定读取、猜想验证、恶意破坏,并将对方直接或间接的返回数据作为某指标的判断依据的一种行为。在上述定义中,网络扫描的概念包含以下意思:

❑网络扫描器几乎全部是客户端一方的程序,所针对的对象绝大多数是服务器方。

❑网络扫描通常是主动的行为,绝大多数网络扫描器的扫描行为都是在或希望在服务器不知情的情况下偷偷进行,通常在扫描器的设计中,扫描行为应尽可能地避免被服务器察觉。所以扫描器通常不会对被扫描的主机有过多的要求,只能主动适应服务器的各项要求。

❑网络扫描通常具有时限性。该时限虽然没有一个明确的界限,但一般来说都是接近扫描的最快速度。如果某个用户每隔几个小时访问一下公司的网站主页,则不能算是扫描。

❑扫描几乎都是要用工具进行,因为操作系统提供的程序并不都具有扫描的各项要求。

❑扫描的目的一般是,对预先的猜想进行验证或采集一些关心的数据。

❑不可回避的一点就是,网络扫描更多地被黑客用于选择攻击目标和实施攻击,并且由于扫描自身的特点,通常被认为是网络攻击的第一步。

1.2.1 服务和端口

生活中的“服务”是指为他人做事,并使他人从中受益的一种有偿或无偿的活动,该活动通常不以实物形式,而是以提供“活劳动”(指物质资料的生产过程中劳动者的脑力和体力的消耗过程)的形式满足他人某种特殊需要。

在网络中,“服务”是指某主机按预先定义的协议和一些国际标准、行业标准,向其他主机提供某种数据的支持,并且称服务提供者为“服务器”(Server),称服务请求者为“客户端”(Client)。与生活中的服务相比,网络上的服务更强调的是协议,即双方必须具有相同的协议,才能进行交流。

一台主机可以安装多个服务,这些服务可以是相同的服务,也可以是不同的服务。为了区分这些服务,引入“端口”(Port)这个概念,即每一个服务对应于一个或多个端口。端口具有独占性,一旦有服务占用了某个端口,则通常情况下,另外的服务不能再占用这个端口。

根据Berkeley套接字的约定,端口名称用一个2字节(16位)的无符号整数来表示,范围为:0~65535,共65536个。其中,端口名称在0到1023之间的端口习惯上称为“熟知端口”(well-known port),主要用于一些公用的并得到国际组织IANA(The Internet Assigned Numbers Authority,互联网数字分配机构)公认的服务;端口名称在1024至49151之间的端口称为“登记端口”,主要用于服务类,而又不属于熟知端口的程序使用;端口名称在49152至65535之间的端口称为“临时端口”,是指任何程序都可以临时使用的端口。原则上,1024至65535之间的端口,只要不出现冲突,用户程序可以根据情况随时使用。

有关端口更详细的说明,参见3.1.1节“端口的概念”。

需要说明的是,由于习惯问题和历史原因,有很多术语产生了混淆。比如在计算机的整机设计和生产时,常常将主机区分为“服务器”(Server)和“工作站”(Work Station),二者从整体设计、部件生产、整体检测方式上都不同。比如服务器通常是无人值守,并需要每周7×24小时正常运转,所以优先考虑的是CPU性能、内存容量、网络吞吐量;而工作站则主要是由用户来操作的,因此更多关注的是显示效果、音频效果等方面。

这种和工作站所对应的、硬件上的服务器(下面称为主机)与上述中和客户端相对应的服务器(下面称为服务端或服务器端)没有必然的对应关系。即TCP/IP协议虽然规定了各个协议的实现细节,但却并没有硬性地规定应用程序应如何与协议软件进行交互。因此实际上所说的服务端和主机之间没有一个一一对应关系。主机是一个硬件的概念,是一台物理设备,而服务端是指一组软件系统,一台主机上可以装多个服务器软件来提供服务,而某个服务端也可以由几台计算机通过软件进行捆绑后实现,因此,可以看出,一个普通主机装上服务端软件,即可以作为一个服务端向客户端提供服务,一台服务器的主机向另一台服务端请求服务的时候,它是作为一个客户端的身份出现的。甚至一台主机,既可以是服务端,也可以作为一个客户端。

另一个软件和硬件名称出现混淆的就是“端口”。即使是在计算机的物理硬件上,也有两个混用的“端口”,一个是计算机连接其他的外部设备的外部物理接口,一般来说统称为端口,如计算机或交换机、路由器等物理设备面板上的RJ-11端口(接电话线的Modem口)、RJ-45端口(即以太网网口)、RS232端口(早期的串行设备接口)等;另一个是专指计算机上的RS232/RS482接口,并且都使用端口(Port)作为其名称。而本处所指的端口则是逻辑上的端口,是专指通过RJ-45以太网网口连接以后,利用协议进行区分的逻辑上的一个值。由此可见,访问网络上的一个指定的服务至少需要知道IP地址和端口两个要素,即

Socket地址=(IP地址:Port端口号)

服务器要想让客户端访问自身,必须同时将二者公布于众,缺一不可;否则客户端将不知道该到哪台主机或某台主机的哪个端口上访问服务,即:

连接=(Socket地址1,Socket地址2)=(IP地址1:Port端口1,IP地址2:Port端口2)

在对端口的状态描述中,各种称呼都有,有的用“开”和“不开”,有的用“激活”和“关闭”,有的用“开”和“关”,鉴于所表示的意义完全一致,因此,后续部分中,统一称为“开”和“关”。“开”即表示有对应的服务程序通过该端口向外界提供相应服务,只要外界使用满足这一端口的协议访问该端口,就可以得到相应的服务;而“关”则表示对应的服务程序没有安装或当前没有处于运行状态,即使在客户端运行相应访问请求的程序,仍无法得到结果。比如运行浏览器IE(Internet Explorer)并在地址栏输入一个不提供WWW服务的IP地址后,IE就会得到回复:访问出错。

1.2.2 网络扫描

扫描源于物理术语,是通过对一定范围内的光或电信号进行检测处理,然后以数值或图形方式进行展示的一个操作。网络扫描也一样,是通过对一定范围内的主机的某种属性进行试探性地连接和读取操作,最终将结果展示出来的一种操作。

端口具有独占性,一旦一个服务使用了某个端口,则另外的服务不能再使用这个端口。端口的占用原则是:先申请的先使用,后申请的在申请时报错。同时,上述的这种对应关系,只是一种约定,但任何操作系统都没有强制软件遵照执行,因此在使用时,存在如下几个情况:

❑某个主机不向外界提供WWW服务,所以该主机的80端口是空闲的。但该主机上的另一个不提供WWW服务的程序使用了80端口。因此,该主机80端口是对外打开的,但不提供WWW服务。

❑某主机虽然提供WWW服务,但该主机并不想让别的人都知道该主机提供该服务,于是该主机的管理员将该主机上的WWW服务的端口由默认的80端口,改为了其他的端口8000,并将该端口告诉了他允许访问的用户。在这种情况下,需要通过其他联系方式通知所有允许访问的主机。

❑某主机对外界想同时提供基于ASP的WWW服务和基于JSP的WWW服务,二者虽然同为WWW服务,但运行机制、配置等各不相同,并且没有一个通用的软件能同时提供,且二者占用了同一个端口80,于是该主机的管理员将ASP设定为80端口,而将JSP的端口设定为8080端口,并在双方主页上互相告知对方端口的存在。

有了服务,就相当于打开了某一个或几个指定的端口,则客户端就可以通过Socket连接到服务器端的该端口,并获取服务。例如某一台计算机配置了WWW服务,该服务默认的端口是80,则在客户端上打开浏览器(如Microsoft的Internet Explorer),输入该服务器的网址,就可以访问该服务器的WWW服务。

但在某些时候,扫描有三种应用:

1)我们不知道远端的服务器是否提供WWW服务,或者我虽然知道远端的服务器提供WWW服务,但该服务使用的不是默认的80端口,而是使用自己定义的端口,那么怎么知道对方是否提供WWW服务,并且该服务在哪个端口呢?该操作就叫WWW服务扫描。

2)我并不想知道远端的服务器是否有某一个具体的服务,我只想知道对方服务器都有哪些服务,可以通过扫描对方所有打开的端口实现,这时,该操作叫做端口扫描。

3)对于一批计算机,我想知道这些计算机是否提供某个服务,或这些计算机都打开了哪些端口,这种操作叫做批量扫描。

由此可见,所谓的网络扫描,就是一方通过某种协议,在目标主机不需知情的情况下,对其实施的一种获取想要的信息或通过读到的信息验证预想的行为过程。在此概念中有两个部分:

1)网络扫描中,目标主机可以知情,也可以不知情,因此这不是一种双方预商量的行为。

2)扫描的目的无论是读取信息,还是想验证某一个事先的预想,其目的都是作为下一步行动的参考。因此网络扫描往往不是一次单独的行动,之后通常会有下一步的操作。

1.3 网络扫描原理概述

根据扫描的概念可以发现,当一个主机向一个远端服务器的某个端口提出建立连接的请求时,如果对方有此项服务就会应答;如果对方未安装此项服务,即使向相应的端口发出请求,对方仍无应答。客户端向服务端发出请求的过程见图1.1。利用这个原理,如果对所有熟知端口或自己选定的某个范围内的熟知端口分别建立连接,并记录下远端服务器所给予的应答,通过查看记录就可以知道目标服务器上都安装了哪些服务,这个过程就叫做端口扫描,所使用的程序叫做扫描程序。通过端口扫描可以搜集到很多关于目标主机的很有参考价值的信息,例如,对方是否提供FTP服务、WWW服务或其他服务。

图1.1 网络TCP/UDP扫描示意图

1.4 扫描编程与客户端编程的区别

同样是网络编程,同样采用C/S(客户端/服务器)模式,在网络通信中的角色也相同,所以说网络扫描编程属于客户端编程的一种,但又不完全等同于客户端编程。为了区分,下面分别用扫描程序和客户端程序称呼二者。

与客户端程序相比,网络扫描程序具有一定的特殊性,主要体现在如下几个方面:

❑对服务端的要求不同。从实际的编程角度来看,如果是普通客户端程序的开发,客户端的开发者与服务器的开发者首先需要共同协商以决定通信时采用什么协议等细节;而扫描程序的开发者则需要通过已有的协议或猜测、试探等方式决定采用什么技术,故扫描程序对服务器端是没有要求的。同时,扫描程序的扫描结果也常具有不可预知性。

❑扫描具有全部或局部的“遍历”性,客户端具有针对性。普通客户端的开发者一旦确定了需求,剩下的就是按需求去实现各功能的细节;而扫描程序的开发者则通常通过对全部或局部进行功能遍历,以期验证自己的猜测或获得更多更详细的数据。

❑对服务器的服务支持程度不同。客户端连接服务器的目的是为了让客户端用户能远程地使用服务器所提供的各项功能,因此,通常客户端程序要支持所有的命令,有些命令哪怕只有极少数机会被用到,客户端也必须提供支持;而扫描程序则只需要支持和使用所需要的最少的几个命令。

1.5 网络扫描的目的

网络服务本身就是一种“广而告之”的通信方式,任何网络服务,如果提供者不将自身提供的服务告诉别人,别人当然也无所知晓,更谈不上使用。但实际应用中,则有各种可能情况,如网络服务的提供者只想让一部分人访问,而不是所有人都能访问;再如随着计算机系统越来越复杂,有时运行一项功能,会默认自动地打开某个服务。

不同的扫描目的,对扫描器的要求也不一样。有些用户只需要知道某个端口是开或是关,或者只想了解某一段端口内,有哪些是开着的,从而判断对方主机的大概作用。而有些用户则不但要知道某一端口是否开,而且还要知道对方所开的这个端口是否提供了默认情况下该端口所应该提供的服务。还有些用户需要知道若干的指定端口是否同时开或同时关,因为有些服务同时占用了几个端口,如NetBIOS服务同时占用了137、138、139三个端口,如果只是其中部分端口开着,不但表明对方没有提供NetBIOS服务,还表明对方有不符合标准的程序正在使用熟知端口。

扫描的目的一般是:

❑获取某范围内的端口某未知属性的状态。这种情况下,一般是不知道对方情况,只是想通过扫描进行查找。例如,通过扫描检测某个网段内都有哪些主机是开着的。

❑获取某已知用户的特定属性的状态。这种情况下,一般是有明确的目标,有明确要做的事,下面只是查找一下某些属性。例如,通过扫描检测指定的主机中哪些端口是开的。

❑采集数据。在明确扫描目的后,主动地采集对方主机的信息,以便进行下一步的操作。例如,没有预定目的地扫描指定的主机,判断该主机都有哪些可采集的数据。

❑验证属性。在明确扫描目标,并且知道对方具有某个属性的情况下,只是通过扫描验证一下自己的想象,然后判断下一步的操作。例如通过扫描指定的服务,验证对方是否是Windows类操作系统。

❑发现漏洞。通过漏洞扫描,主动发现对方系统中存在的漏洞。如扫描对方是否具有弱密码。

扫描的用户主要为三大类:网络管理员、黑客、普通用户,他们对网络使用的角度不同,所以扫描的目的也各不相同,见表1.1。

表1.1 端口扫描用户类别和扫描目的

1.6 网络扫描算法

在实际扫描器编写过程中,除了有各种技术的选择之外,还需要选择合适的扫描算法。使用哪些扫描算法,也完全取决于扫描的目的,因为这些算法有些可以提高扫描效率,有些可以增加扫描准确度或扫描隐蔽性,有些甚至可能牺牲某些优点而获得所需要的特性。

1.6.1 非顺序扫描

参考已有的扫描器,会发现几乎所有的扫描器都无一例外地使用增序扫描,即对所扫描的端口自小到大依次扫描,殊不知,这一效果可以被对方的防火墙或IDS(Intrusion Detection System,入侵检测系统)作为判断正被扫描的特征。虽然通过多线程会使这一特征发生少量的变化,但从整体效果上看,仍然显示增序现象。

改变增序特征并不难,一般有如下几种非顺序扫描算法。

1. 逆序扫描算法

顾名思义,逆序扫描就是在扫描的时候,采用从大到小的逆序扫描方式。

2. 随机重排扫描算法

随机重排扫描即重新排列要扫描端口的顺序。在新排的顺序中,为了避免漏掉或重复使用某一端口,可以采用互换位置的方式进行。这个过程可以用一个数组和随机数产生函数rand来实现:

        #define MAXPORTCOUNT 65536
        WORD *NewSort(WORD wBegin,WORD wEnd)
        {
        WORD buff[MAXPORTCOUNT]={0};          //建立一个数组
        WORD count=wEnd-wBegin,i,wTemp,wRand;
        if (count<0) return NULL;              //判断范围的合理性
        srand(time(NULL));                       //随机种子
        for (i=0;i<=count;i++)                  //先将所有值复制到数组中
            buff[i]=wBegin+i;
            for (i=0;i<=count;i++)
            {//顺序地让每一个端口值与另一个值对换
                wRand=rand()%count;           //读取一个随机位置
                //将当前位置的数据与随机位置的数据互换
                wTemp=buff[i];
                buff[i]=buff[wRand];
                buff[wRand]=wTemp;
            }
            return (WORD *)buff;
        }

利用这段程序可以保证打乱后的端口顺序不被遗漏,也不会重复,而新的顺序完全是随机分布的。采用这个顺序进行扫描的时候,对方防火墙在监测到某个端口连接时,无法立即对下一个要连接的端口进行预测,从而使“基于通过连续端口被连接算法进行扫描判断”的方式失效(详见12.3节“基于哨兵的端口扫描监测”)。

3. 线程前加延时扫描算法

为了提高扫描速度,很多扫描算法都采用多线程扫描。在Windows中,同级别的扫描线程通过抢占方式获得CPU的优先使用权,各个线程理论上没有先后之别,但考虑到创建时总要有一定顺序,因此即使在运行中偶尔相邻的两个线程顺序会做出调整,整体上各线程之间也有先后的顺序。

一个简单的算法就是在每一个线程中,扫描函数开始之前挂起(Halt)一个随机的时间,这样会在不影响各线程创建时间的前提下,调整各线程中扫描的顺序。具体的方法就是在线程的开始加一句:

        Sleep(rand()%5000);//假设每个线程挂起时间是5秒以内的一个随机数

1.6.2 高速扫描

随着网络的迅速发展,一个部门内部的网络系统规模迅速扩大,有的达到成百上千个节点。对如此规模的目标进行全面的扫描,要求扫描工具的速度非常快,于是出现了各种各样的高速扫描技术。常见的高速扫描算法有多线程并行扫描技术、基于KB(Knowledge Base,知识库)技术、将扫描和判断分离的技术。

多线程的扫描技术几乎贯穿整本书和应用于大多数的扫描器中,所以不再详述。

KB技术是指把扫描过的主机信息存储起来,当下次扫描的时候,首先以上次的扫描结果作为参考,先对用户最关心的方面进行重新扫描,然后对其余部分进行扫描,这样既能提高扫描速度,又能有效降低占用的带宽。例如,某次扫描中,用户只关心原有“开”的端口是否仍处于开的状态,则只需要扫描上次记录中“开”状态的端口即可。

并不是所有扫描都可以使用多线程,在一些特殊扫描中,常规的API函数无法满足扫描的要求,这时需要采用非常规的方式。例如扫描UDP某端口时,通过sendto函数发送数据包后,需要通过嗅探(Sniffer)技术读取ICMP协议,而嗅探可以接受任意数据包,因此采用多线程时,各线程的嗅探会因相互接对方的回复包,而导致扫描结果出错。如果采用单线程,则需要不停地重复“发出探测包→接收结果→分析结果”这一流程,其中最耗时间的是发出探测包和接收结果。如果将发送和接收分开,发送只负责发送,接收操作统一进行,那么,由发出探测包到接收结果之间的等待时间成为并行,从而大大提高扫描速度。在第6章的6.6节“编程实例:快速多IP的ICMP扫描器”中,将详细介绍此算法及其具体实现方式。

1.6.3 分布式扫描

高速扫描主要依靠多线程实现,而分布式扫描则主要使用多台主机同时对目标主机进行扫描,参与的主机可以事先约定后主动加入,也可以被入侵后植入扫描程序。在实施扫描的时候,由主控主机向各参与的主机发送要扫描的主机IP地址和端口范围,然后所有主机同时向被测主机进行扫描。

这种扫描方式最大的优点是速度快,而且由于扫描信息包来自不同的IP地址,所以被扫主机的防火墙会因为不像一次扫描行为而无法判断。如果不考虑扫描的目的,DDOS其实也可以认为是分布式扫描方法之一。

1.6.4 服务扫描

端口扫描器只能扫描出端口的状态是否开放,而不会判断端口所对应的服务是否为该端口所具有的默认服务。服务扫描则是直接对服务进行扫描,并通过服务的存在与否,间接地判断端口是否处于“开”状态。同时,服务扫描本身也是一种需求,本书第7章和第8章是与服务扫描相关的介绍与编程实现。

1.6.5 指纹识别算法

现在,操作系统种类繁多,版本更新的速度越来越快,想要了解某个远程主机的更多信息,则可以通过操作系统指纹识别算法判断对方所用的操作系统类型,甚至是版本号。所谓指纹识别技术就是与目标主机建立连接,并发送某种请求,由于不同操作系统以及相同操作系统不同版本所返回的数据或格式不同,这样,根据返回的数据就可以判定目标主机的操作系统类型及版本。

通常的指纹识别算法有几大类,一类是通过操作系统提供的服务进行判断,各主流操作系统都内嵌一些服务器软件,常见的如FTP、Telnet、HTTP和DNS服务器,这些软件都会在欢迎信息、版权声明、命令回复中或多或少地透露自身的版本号,这也可以间接地反映出操作系统的类型和版本号。但这种方式也有不足之处,就是这些信息有些不准,甚至是错误的。

另一类就是根据一些协议实现上各操作系统的细微差别进行判断,如通过TTL值进行操作系统识别(详见第6章的6.2.1.2节“通过TTL值判断对方操作系统的简易方法”中的内容)、TCP FIN扫描(详见第3章的3.2.2节“TCP扫描”中的内容)等方法。

1.6.6 漏洞扫描

网络扫描大部分的目的是获得对方信息,为下一步操作做出判断,漏洞扫描则直接提供了攻击对方的方法。漏洞扫描器是所有扫描器中应用针对性最强、时效性最差的一种扫描器,前者是说通常某漏洞扫描器只针对某一应用,甚至要精确到某一软件的某一版本;后者是说一旦这种扫描算法或原理公开,则该漏洞很快会被开发人员补上,导致该扫描器失效。但由于其针对性最强,所以也最具攻击性。漏洞扫描的另外一种表现形式就是“安全扫描”,因为有些安全选项设置错误,本身也相当于漏洞,如“允许匿名登录”、“网络某服务不需要认证”等选项设置。

第11章“漏洞扫描器的设计”中,详细说明了此类扫描器的相关内容。

1.6.7 间接扫描

间接扫描的思想是利用第三方的IP(欺骗主机)来隐藏真正扫描者的IP。由于扫描主机会对欺骗主机发送回应信息,所以这种扫描的使用者必须具有监控欺骗主机的能力,以便获得原始扫描的结果。

1.6.8 秘密扫描

正常情况下的扫描有时会被对方的防火墙或入侵检测系统(IDS)监测到,所以有些扫描器通常采用秘密扫描方式进行扫描。最典型的例子就是,扫描程序通过采用非正常和非常规的方式,试探协议中在网络较差情况下的容错技术,通过这些容错技术的不同反馈达到扫描的目的。这种方法由于没有完成正常的操作,所以对方不会认为是一种扫描或攻击,而只会认为是一次网络错误的发生,从而不会被记录下来,这相当于绕过了对方的安全机制,故名秘密扫描。

在第3章3.2.2节“TCP扫描”中,详细分析了“TCP SYN扫描”、“TCP ACK扫描”、“TCP NULL扫描”、“TCP FIN+URG+PSH扫描”、“TCP FIN扫描”等几种常见的秘密扫描方式。

由于部分反馈方式在协议中没有明确的规定,所以不同的操作系统在实现的时候并不完全相同,因而扫描效果也不尽相同。如上述多数秘密扫描方式通常适用于目标主机是UNIX/Linux操作系统,而对于Windows系列的操作系统,由于Windows不论目标端口是否打开,操作系统都发送相同的反馈数据包,因而导致上述大部分秘密扫描算法无效,但这也同时给指纹识别扫描算法提供了素材。

1.6.9 认证扫描

认证扫描则是利用认证协议的特性,通过判断获取到的监听端口的进程特征和行为,获得扫描端口的状态。认证扫描尝试与一个TCP端口建立连接,如果连接成功,扫描器发送认证请求到目标主机的TCP113端口,同时获取运行在某个端口上进程的用户名(userid),从而达到扫描的作用。

在第3章的3.2.2节“TCP扫描”中,有对认证扫描较详细的分析。

1.6.10 代理扫描

当前的很多企业内部网,考虑到各种因素影响,需要通过代理服务器访问外网,因而也就有了代理扫描。代理扫描需要在原有所有算法的基础上再加上一个与代理服务器的通信,当前代理服务器的协议主要是SOCK5。在代理扫描方式下,所有的扫描看上去像是对代理服务器的扫描,因为所有数据都通过SOCK5封装后发给了代理服务器,而代理服务器会将这些数据转发给被扫描的目标主机。这种扫描方式,在被扫主机看来,是由代理服务器本身在扫描自己,因而代理扫描难以反向跟踪。

与“间接扫描”不同的是,代理扫描源主机将数据发给代理服务器,代理服务器将“扫描”转发给目标主机,而代理服务器将目标主机的反馈也原封不动地转发回源主机,由源主机进行判断;间接扫描则是源主机控制了中间主机,由中间主机代为扫描,并判断出结果,源主机通过其他方式获得扫描结果。

1.6.11 手工扫描

手工扫描是指在没有任何专用扫描器的前提下,只利用操作系统提供的命令或自带的程序文件进行扫描。当前主流操作系统都提供ping、nbtstat、netstat、net等命令,这些命令虽然都不具有扫描的特性,但通过这些命令都可或多或少地获得很多信息。

严格上说,手工扫描根本就不能算是一种扫描算法。因为这些程序本身算法各异,并且大部分命令只针对某一应用而做,很难显现“扫描”的特点。但考虑其在某种特殊场合下也许是唯一的选择,故也将其列为一种算法。

在第3章的3.3节“手工扫描”中,专述如何通过手工扫描达到临时扫描器的作用。同时,在第2章的2.3节“嵌入外部程序”中,专述在有现成工具的前提下,如何通过嵌入命令或外部程序,而将结果嵌入到自身系统。

1.6.12 被动扫描

以上几乎所有的扫描都属于主动探测模式,即发送刺探信息,然后根据对主机的反馈做出判断。这种模式最大的缺点就是首先要向对方发出刺探信息,这种信息本意是探测别人,但对方根据所发的信息,又能反向获得发送方的信息。这种模式在网络攻防“此消彼长、不进则退”的大环境中,有时不但检测不到对方真实的信息,反而会被对方反向监测。

被动扫描模式则不发送刺探消息,而只是监听。在这种模式下,扫描方从不或极少主动发送任何信息,而只是按协议被动地搜集被扫描主机的敏感信息,最终达到扫描的目的,由于不主动发信息,所以对方无法反向监测,因此这是一种非常安全的方式。

通常情况下,被扫描的主机是不会主动联系扫描主机的,所以被动扫描只能通过截获网络上散落的数据包进行判断。这些散落的数据包有多种形式,主要有:

❑被扫描主机通过广播方式向所有主机发送的数据包。

❑有些主机之间,通过组播方式进行数据通信,而要扫描的主机只要加入到该组播中,便可监听各主机之间通过组播方式进行的所有通信。

❑即使是交换环境中,仍有很多数据包散布至各交换机端口,只是主机网卡在发现不是给本机的数据时直接扔掉。只要将网卡设成混杂模式(Promiscuous Mode),便可收到这些数据包。

在第10章“服务发现扫描器的设计”中,将介绍如何通过被动扫描方式获取当前活动的主机,在第11章的11.4节“明文密码嗅探”中,介绍如何通过被动扫描获得网上明文传输的密码。

1.7 网络扫描器的分类

能够进行扫描的软件称为扫描器,不同的扫描器,所采用的技术、算法、效果各不相同。根据扫描过程和结果不同,可以把扫描器分成几个类别:

❑根据扫描软件运行环境可以分为UNIX/Linux系列扫描器、Windows系列扫描器、其他操作系统扫描器。其中UNIX/Linux由于操作系统本身与网络联系紧密,使得此系统下的扫描器非常多,编制、修改容易,运行效率高,但由于UNIX/Linux图形化操作较为复杂,故其普及度不高,因此只有部分人会使用。Windows系统普及度高,使用方便,极易学习使用,但由于其编写、移植困难而数量不太多。其他操作系统下的扫描器因为这些操作系统不普及而使得这类扫描器难以普及。

❑根据扫描端口的数量可以分为多端口扫描器和专一端口扫描器。多端口扫描器一般可以扫描一段端口,有的甚至能把六万多个端口都扫描一遍,这种扫描器的优点是显而易见的,它可以找到多个端口从而找到更多的漏洞,也可以找到许多网管刻意更换的端口。而专一端口扫描器则只对某一个特定端口进行扫描,并给出这一端口非常具体的内容,一般特定端口都是非常常见的端口,比如21、23、80、139。

❑根据向用户提供的扫描结果可以分为只扫开关状态和扫描漏洞两种扫描器。前者一般只能扫描出对方指定的端口是“开”还是“关”,没有别的信息。这种扫描器一般作用不是太大,比如,非熟知端口即使知道开或关,但由于不知道提供什么服务而没太大的用途。而扫描漏洞扫描器一般除了告诉用户某一端口状态之外,还可以得出对方服务器版本、用户、漏洞。

❑根据所采用的技术可以分为一般扫描器和特殊扫描器。一般扫描器在编制过程中通过常规的系统调用完成对系统扫描,这种扫描只是网络管理员使用,因为这种扫描器在扫描过程中会花费很长时间、无法通过防火墙、在被扫描机器的日志上留下大量被扫描的信息。而特殊扫描器则通过一些未公开的函数、系统设计漏洞或非正常调用产生一些特殊信息,这些信息使系统某些功能无法生效,但最后却使扫描程序得到正常的结果,这种系统一般主要是黑客编制的。

1.8 网络扫描技术的发展史

扫描技术本身并不是一个网络功能,而且在网络发展到一定阶段后,这种需求的产生,导致扫描技术的产生和发展。根据扫描技术的发展过程,可以把扫描分为手工扫描、使用通用扫描器、设计专用扫描器三个主要阶段。三个阶段互相重叠,没有明确的界限,由于扫描技术主要是由民间进行推进,所以目前也没有分类标准和判断标准。

1.8.1 手工扫描阶段

最简单、最原始的扫描方法就是手工扫描,所谓手工扫描就是用要扫描服务的客户端与服务器进行连接,或通过其他的工具采用非常规的方式与服务器进行连接,从而验证对方是否打开了某个端口,提供了某个服务,或具有某个漏洞。在结果中,如果程序正常运行就表示该服务器提供此项服务,否则表示对方没有提供此项服务。

比如,要验证对方是否在2121端口提供文件传输服务(FTP服务),则可以使用操作系统自带的FTP客户端程序连接对方。以Windows XP为例,假设对方服务器的URL地址为“ftp.test.net”,一种办法是在命令行状态下输入“ftp ftp.test.net 2121”,如果对方提示输入用户名和密码,则表示服务器ftp.test.net的端口2121处于“开”状态,并且提供FTP服务,即使此时不知道用户名和密码,即不是合法用户,也足以验证对方提供了FTP这项服务。另一种方法是由于Windows提供的IE(Internet Explorer)浏览器内嵌有FTP功能,所以在IE浏览器的地址输入框中输入ftp://ftp.test.net:2121/可以验证同样的结论。

再如,想扫描192.168.1.1~192.168.1.254这段IP中哪些主机是开的,则可以通过如下一个批处理来完成:

        for /l %%a in (1 1254) do ( ping 192.168.1.%%a)

运行由上面一行命令所组成的批处理文件,则系统会自动按顺序依次不停地ping每一台主机。

手工扫描的最大优点就是几乎不需要任何其他工具,方便、快捷,可以在任何一台主机上实验。但缺点也很明显,由于手工操作方式需要手工输入,因此需要使用者本身对要扫描的协议很了解,知道其原理,并且手工输入由于命令行功能有限,不可能快速、大范围地扫描。

在手工扫描时,有一个比较有效的办法,就是采用Telnet客户端进行验证,因为Telnet客户端是Windows自带的程序,并不需要额外安装,随便一台主机都可以直接使用。Telnet登录对方系统后,不仅能输入有限的指令,还可以读到对方的返回信息,这一方便的特性,使手工扫描用户可以通过交换式操作,实时读取对方的关键信息,为下一步的判断提供准确的信息。

例如,要检测对方主机的80端口是否提供WWW服务,只需要在命令行中输入:

        Telnet <对方IP地址> 80

则可以连接对方的80端口,如果连接失败,会提示Telnet无法连接到远端主机,如果成功,则对方主机进入到等待客户端输入命令的状态,这个时候可以随便输入一些字符,并连续回车,五六个回车后,对方就会返回如下内容:

        HTTP/1.1400 Bad Request
        Server: Microsoft-IIS/5.1
        Date: Sun, 19 Jun 2011 14:43:50 GMT
        Content-Type: text/html
        Content-Length: 87
        <html><head><title>Error</title></head><body>The parameter is incorrect. </body>
        </html>
        失去了跟主机的连接。
        C:\ >

根据以上内容,很容易知道对方返回的HTTP协议,并内嵌HTML语言的网页代码。这些足以证明对方的80端口提供WWW服务。

用同样的方法,可以测试21端口是否提供了FTP服务,在命令行中输入:

        Telnet <对方IP地址> 21

则可以连接到对方21端口,如果连接失败,同样会提示Telnet无法连接到远端主机。如果成功,则对方会显示如下内容:

        220-Microsoft FTP Service
        220 欢迎访问LRM服务器

需要说明的是,上面返回信息只是Windows默认的FTP服务器软件,其他的FTP服务器软件返回信息则不会完全一样。上面的显示,足以证明对方21端口提供FTP服务。(以上内容中,WWW、FTP的配置与验证详见第7章“基于协议的服务扫描器的设计”中有关服务器的安装和配置章节。)

上述所讲的手工扫描方式只是一些简单的方式,在第3章的3.3节“手工扫描”中,详细地介绍复杂的扫描方式。

1.8.2 使用通用扫描器阶段

当前Internet上主要采用TCP(Transmission Control Protocol,传输控制协议)和UDP(User Datagram Protocol,用户数据报协议)作为传输层运行协议。虽然除此之外还有很多协议,但这些协议一般都是传输层以上各层的协议,靠TCP或UDP进行网际传送。而端口的概念是处于传输层上的,所以在扫描端口时,我们可以说扫描的是TCP或UDP端口而不是别的协议的端口,那些协议端口只是通过TCP或UDP端口体现出来而已,因此,在编写通用扫描器的时候,只需要用TCP或UDP协议向对应的端口发数据就行了,而不一定非用哪一个协议。例如,139端口是NetBIOS协议端口,想扫描139端口是不是处于“开”状态,只需要向目标主机139端口发送信息然后根据返回信息进行判断就可以了。

通用扫描器也就是向指定的一段端口分别发送建立连接的请求,如果对方存在对应的服务,连接就可成功建立,否则无法建立。利用这个特点,我们可以判断对方对应的端口是否开。这种扫描器一般只能扫描出对方某一端口是否开放,然后检索端口数据库,给出对这一端口提供的服务。

1.8.3 设计专用扫描器阶段

通用扫描器有不准确或不精确的缺点。比如,某人的服务器没有提供NetBIOS服务,而正好有一个服务器应用软件使用139端口进行通信,此时由于扫描器在139端口建立连接成功而通过查端口数据库认为139端口处于“开”状态而且提供了NetBIOS服务。

专用扫描器则不求功能多,只扫描特定的一个或几个端口,扫描后,不仅给出所扫端口是否处于“开”的状态,指出其提供的服务,而且会拿对应的服务和目标主机建立连接,从而获得对方服务器版本号、用户列表、共享目录、漏洞等信息。

1.9 扫描器的限制

几乎所有的扫描器都有一定的局限性,这些局限性不仅取决于扫描算法的优劣,还取决于其他各方面的限制,一个万能的、全面的扫描器是不存在的。

首先,操作系统对扫描器有影响。当前主流的操作系统主要有两大派系,一个是Windows派系,另一个是UNIX/Linux派系。Windows派系由于所有版本均由Microsoft公司开发,所以保持了接口的统一性和连续性;UNIX和Linux本身差距较大,且各自有很多派系和版本,但总体来说,各接口差别不大。从软件开发来说,Windows派系和UNIX/Linux派系最多只能达到源码一级的兼容,且多为无界面的命令行程序,其余部分则大多不兼容,至于编译出的可执行文件,则完全不兼容,这也证实了万能的、全面的扫描器是不存在的。

除扫描方的操作系统之外,被扫方的操作系统也需要考虑。通常情况下,各服务协议的作用正是要减免异构系统之间的差异,但不同的操作系统还是有细微的差别。例如Windows 2000和Windows XP是功能和发布时间非常接近的两个版本,但默认的配置差别较大,所以扫描的结果差异较大。

除了操作系统的影响,扫描器还与采用的算法有关,不同的扫描器,所采用算法不同,扫描出的效果也不尽相同。例如要扫描本地所有TCP/UDP连接及使用的端口,有数种方式,例如,DOS命令重定向、SNMP方式、系统API方式、采用钩子函数等,这些方式各有利弊,并不能确定都是成功的,受很多因素的影响(如用户级别、是否安装并运行了某个服务),所扫描的效果也不尽相同。

1.10 当前网络常见的漏洞

一个系统中有什么漏洞?这是一个较难回答的问题,因为通常情况下,每当指出一个确切的攻击方案时,随着各种技术细节越来越公开,使得很多软件的设计者或开发者,特别是系统的开发者,想从各个层次堵住漏洞。因此漏洞大有“只可意会,不可言传”的感觉。当然,也有一些漏洞,即使是攻击方式和算法公开,也因难以处理而仍然存在。

下面列出一些目前为止仍然广泛使用或仍没有太好解决方案的漏洞。

1.10.1 DOS和DDOS

DOS(Denial Of Service,拒绝服务攻击)是一种原理简单,但普遍存在,并且很难预防和解决的攻击方式。

在C/S(客户端/服务器)模式下,客户端连接服务器端,并提出服务申请,服务器根据服务申请查找所需要数据,然后将查到的数据以服务响应的方式回复给客户端。通常的情况下,由于服务器需要占用一定的系统资源(CPU、网络、内存、硬盘)然后才能查到数据并回复,所以在某一时刻一般只能响应一定数量的客户端服务申请,当客户端服务申请个数超过这个数量时,多余的申请个数只能等待,等到正在处理的服务结束后,多余的客户端再获得服务器端的服务。在这种模式下,如果有一个主机同时向某个服务器始终发送远超服务器在某时刻响应客户端的上限数量,则其他主机同时向该服务器提出申请的时候,就会处于等待状态。虽然偶尔其他主机也能得到服务器的服务响应(因为该用户的申请和发起DOS攻击各客户端处于同一个级别,所以该主机也能申请到服务),但总的感觉是服务器访问速度极慢,或大量访问失败,从现象上看,像是服务器拒绝向自己提供服务,故称这种方式为“拒绝服务”式攻击。

随着网络和服务器性能越来越高,通常一台普通家用电脑很难对一个高性能的服务器造成实质性的DOS攻击,所以黑客在DOS技术上做了升级,改成DDOS(Distributed DOS,分布式拒绝服务攻击)。该技术中,各个主机所采用的仍是DOS攻击,但同时采用了分布于不同主机上的多台主机同时攻击同一个服务器的方式。

到目前为止,对于DOS和DDOS还没有一个好的解决方案,只能在一定程度上预测和防止。在第11章中,将以漏洞扫描器的方式详细地说明和讲解该方式。

1.10.2 缓冲区溢出

缓冲区溢出问题源于一些程序语法分析和编译器不严格,是一种非常普遍、非常危险的漏洞,并且存在于各种操作系统、各种应用软件中,可以说是目前为止最具危害性的攻击方式。

要完全理解缓冲区溢出,就要从程序的运行原理上来分析。比如下面一个程序:

        1   #include <stiod.h>
        2   void main(int argc, char **argv)
        3   {
        4      if (argc==2)//格式必须是“命令参数”
        5         readstr((char *)argv[1]);//输入内容
        6       else
        7           printf("Usage:%s <string for print>", (char *)argv[0]);
        8       return 0;
        9   }
        10 void readstr(char *str)
        11 {//将参数1复制到16字节的缓冲区中,并打印出来
        12     char buff[16];
        13     strcpy((char *)buff,str);
        14     printf("%s",(char *)buff);
        15 }

这是一个简单的C语言程序,该程序将作为参数的字符串复制到buff缓冲区中,并打印出该字符串。编程完成后程序执行,如果输入的参数是字符串“Hello,world.”,则首先执行第5行,并转向第10行的函数readstr,在执行第13行时,把“Hello,world.”复制到缓冲区buff中,随后执行第14行,将“Hello,world.”打印出来。整个程序由于“Hello,world.”的长度是13个字符(12个字母和一个表示字符串结束的字符'\0'),该值小于buff的长度16,所以程序正常执行,并打印出正确的结果。

如果输入的字符串是“ThisIsABufferOverflowTesting.”呢?这时,整个字符串的长度超过了buff的长度16,但strcpy函数本身在复制的时候,并不考虑是否超过了buff的长度,它只是按自己的要求,只要不遇到字符'\0',复制就不会停止,因而其内容前16个字节保存在buff中,第16个字节之后的内容,将保存在buff数组之后的内存中,这就是缓冲区溢出,这样的错误会在运行的时候报错。

到现在为止,缓冲区溢出还只是一个bug,还达不到“漏洞”的级别。要想成为一个攻击时可以利用的漏洞,就需要更深入的分析,并且需要很多计算机底层的相关知识,下面只是简要地说明一下。

首先,程序在内存中,主程序的数据部分在数据段(Data Segment,如上例中的buff[16])中,代码部分在代码段(Code Segment,如上例第2~15行中,除了第12行之外的所有行)中,而函数的数据部分在栈段(Stack Segment)中,函数的代码在代码段中,其中readstr函数的栈段如图1.2所示。

图1.2 函数调用堆栈结构

当函数readstr执行之前,系统会在堆栈中建立图1.2的结构,并将函数readstr之后的地址(即第8行代码的地址)填入到“函数调用结束返回地址”中,以便函数执行完后,返回到函数下一行执行;在函数过程中,所需要的数据保存在buff数组中,根据函数体的需要进行操作;函数运行结束以后,转向“函数调用结束返回地址”所指向的地址。

回到前面的缓冲区溢出,当向buff中复制数据的时候,由于strcpy不做长度的检测,所以当长度超过16时,多余的部分就会覆盖后面的内容。根据实际输入的内容多少,覆盖的多少也不同。试想,当输入数据达到一定量的时候,多余的部分会依次覆盖图1.2中“栈段数据填充”、“函数调用结束返回地址”、“堆栈空余空间”中的部分或全部内容。但此时程序并不知道这些地方内容被覆盖了,当readstr函数执行结束后,仍然会跳转到“函数调用结束返回地址”所指向的地方执行。如果覆盖“堆栈空余空间”的数据是一段程序,而覆盖“函数调用结束返回地址”后的指向地址正好指向“堆栈空余空间”中的这段代码,则readstr函数执行结束后,就不会返回调用处之后的一行(例子中的第8行),而是执行了经过精心安排的代码。这段代码有可能是攻击者的代码,可能做攻击的事情,例如创建了一个用户,提升了某个用户的权限,或启动了某个服务,总之这种攻击的危害性完全取决于这段代码。

1.10.3 注入式攻击

注入式攻击是将本来不属于设计者原意的东西通过“注入”的方式加入到某系统中,这样做的目的是想让注入的那部分出现意想不到的效果。缓冲区溢出的方式就属于注入式攻击,除此之外,更多的是SQL注入式攻击。

SQL注入式攻击即利用SQL语句本身的语法特点,注入一部分并非设计者想要的代码,通过注入的这部分代码完成某种功能。

该部分内容将在第11章的11.1节“注入式漏洞扫描器”中进行详细阐述。

1.10.4 明文传输

在一个重要的系统中,设置一个安全级别很高的密码,在登录远程系统的时候,先确保没有人能看到你输入的按键,没有木马病毒可以记录所有的按键,输入好用户名和密码,单击登录系统,然后成功登录到了远程的主机上。

在上述整个看似安全的操作步骤中,是不是可以确保密码不会被别人知道呢?答案是否定的,由于密码是明文传输,所以在从用户的源主机到目标主机之间还有很多环节,在这些环节中的任何一处都有可能监听到该密码。

以图1.3为例,用户的主机是“源主机”,在该主机上有某系统登录界面,“目标主机”上存放着系统的认证模块。用户在源主机的系统登录界面上输入用户名和密码,则该用户名和密码以明文的方式被放在一个提交表单中。该表单在C/S(客户端/服务器)模式中称为一个数据报(Data Gram),而在B/S(浏览器/服务器)模式中则称为一个表单(Form)。该表单通过本网的交换机传到路由器,通过路由器发送到互联网上,到达对方网络的路由器,再经过对方网络的交换机后,最终送到目标主机。目标主机根据表单中的用户名、密码与数据库中的用户名、密码比对,然后将结果原路返回。

图1.3 明文传输模型图

在上述传输的链路中,每一个环节都有可能被监听,而数据在经过每一个环节的时候,由于传的都是明文,所以几乎是无密可保,而且这种现象是广泛存在的。

解决这类问题通常没有很好的办法,只能通过制度等规定,交换机、路由器必须是专用的,在上面不能随意安装或使用未授权的软件。

本书不对这类问题进行深入讨论,但可以通过后面部分章节的内容进行验证。如在第7章的7.1节“WWW服务扫描”中,详细讲述了一个WWW服务器的配置过程;在第11章的11.4节“明文密码嗅探”中,详细讲述了一个如何通过Sniffer程序监听网络上的明文密码传输的例子。

1.10.5 简单密码

简单密码的危害不言而喻,但也是普遍存在的,因为简单的密码会使攻击者很容易获得密码,并随后直接以正常用户使用某系统。

简单密码很难有一个非常准确的界定,通常是指如下情况:

1)密码位数比较少。因为位数少的密码,很容易被对方采用穷举法进行破解。一般来说,三位及以内的密码都可以认为位数比较少。

2)密码是一个简单英文单词或拼音音节,这可以通过字典方式进行穷举。一般来说,使用英文作为密码的是一个英文人名,或一个普通的英文单词(假设为高中英文词汇),或一个中文的姓氏,这三者的总和大约不到一万个,黑客很容易根据词典生成这样的常用词字典,然后通过程序读取字典中的每一个词,进行穷举破解。

3)密码只使用了一个字符集。通常的键盘按键中,可以用做密码的必然是可见字符,这些可见字符可以分为大写字母字符集(26个)、小写字母字符集(26个)、数字字符集(10个)、标点符号字符集(33个)。在设定密码的时候,很多人为了输入方便,而只使用某一个字符集,这使得穷举的空间数大为减少。

4)密码使用自己姓名、家庭电话号码、车牌号等信息,而这些信息同时也会以另一种方式公开,这会给一些认识自己或通过别的方式获得此信息的人以可乘之机。

5)在所有需要密码的场合均使用同一密码。

在上述1~3各项中,为了更详细地阐明这些密码的危险,特在第11章的11.2节“主机弱密码扫描”进行详细的说明。