Internet给全世界的人们带来了无限的生机,真正实现了无国界的全球村。但是还有很多困扰我们的因素,象IP地址的短缺,大量带宽的损耗,以及政府规章的限制和编程技术的不足。
现在,由于多年来网络系统累积下了无数的漏洞,我们将面临着更大的威胁,网络中潜伏的好事者将会以此作为缺口来对系统进行攻击,我们也不得不为以前的疏忽付出更大的努力。虽然大多的网络系统产品都标榜着安全的旗号,但就我们现在的网络协议和残缺的技术来看,危险无处不在。
拒绝服务攻击是一种遍布全球的系统漏洞,黑客们正醉心于对它的研究,而无数的网络用户将成为这种攻击的受害者。Tribe Flood Network, tfn2k, smurf, targa…还有许多的程序都在被不断的开发出来。这些程序象瘟疫一样在网络中散布开来,使得我们的村落更为薄弱,我们不得不找出一套简单易用的安全解决方案来应付黑暗中的攻击。
当前的技术概况
· 软件弱点是包含在操作系统或应用程序中与安全相关的系统缺陷,这些缺陷大多是由于错误的程序编制,粗心的源代码审核,无心的副效应或一些不适当的绑定所造成的。根据错误信息所带来的对系统无限制或者未经许可的访问程度,这些漏洞可以被分为不同的等级。
· 典型的拒绝服务攻击有如下两种形式:资源耗尽和资源过载。当一个对资源的合理请求大大超过资源的支付能力时就会造成拒绝服务攻击(例如,对已经满载的Web服务器进行过多的请求。)拒绝服务攻击还有可能是由于软件的弱点或者对程序的错误配置造成的。区分恶意的拒绝服务攻击和非恶意的服务超载依赖于请求发起者对资源的请求是否过份,从而使得其他的用户无法享用该服务资源。
· 错误配置也会成为系统的安全隐患。这些错误配置通常发生在硬件装置,系统或者应用程序中。如果对网络中的路由器,防火墙,交换机以及其他网络连接设备都进行正确的配置会减小这些错误发生的可能性。如果发现了这种漏洞应当请教专业的技术人员来修理这些问题。
如果换个角度,也可以说是如下原因造成的:
· 错误配置。错误配置大多是由于一些没经验的,无责任员工或者错误的理论所导致的。开发商一般会通过对您进行简单的询问来提取一些主要的配置信息,然后在由经过专业培训并相当内行的专业人士来解决问题。
· 软件弱点。由于使用的软件几乎完全依赖于开发商,所以对于由软件引起的漏洞只能依靠打补丁,安装hot fixes和Service packs来弥补。当某个应用程序被发现有漏洞存在,开发商会立即发布一个更新的版本来修正这个漏洞。
· 拒绝服务攻击。拒绝服务攻击大多是由于错误配置或者软件弱点导致的。某些DoS攻击是由于开发协议固有的缺陷导致的,某些DoS攻击可以通过简单的补丁来解决,还有一些导致攻击的系统缺陷很难被弥补。最后,还有一些非恶意的拒绝服务攻击的情况,这些情况一般是由于带宽或者资源过载产生瓶颈导致的,对于这种问题没有一个固定的解决方案。
深入DoS
DoS的攻击方式有很多种。最基本的DoS攻击就是利用合理的服务请求来占用过多的服务资源,致使服务超载,无法响应其他的请求。这些服务资源包括网络带宽,文件系统空间容量,开放的进程或者向内的连接。这种攻击会导致资源的匮乏,无论计算机的处理速度多么快,内存容量多么大,互连网的速度多么快都无法避免这种攻击带来的后果。因为任何事都有一个极限,所以,总能找到一个方法使请求的值大于该极限值,因此就会使所提供的服务资源匮乏,千万不要自认为自己拥有了足够宽的带宽就会有一个高效率的网站,拒绝服务攻击会使所有的资源变得非常渺小。
传统上,攻击者所面临的主要问题是网络带宽,由较小的网络规模和较慢的网络速度,无法使攻击者发出过多的请求,然而,类似"the ping of death"的攻击类型紧需要很少量的包就可以摧毁一个没有打过补丁的UNIX系统。当然,多数的DoS攻击还是需要相当大的带宽的,但是高带宽是大公司所拥有的,而以个人为主的黑客很难享用。为了克服这个缺点,恶意的攻击者开发了分布式的攻击。这样,攻击者就可以利用工具集合许多的网络带宽来对同一个目标发送大量的请求。
以下的两种情况最容易导致拒绝服务攻击:
· 由于程序员对程序错误的编制,导致系统不停的建立进程,最终耗尽资源,只能重新启动机器。不同的系统平台都会采取某些方法可以防止一些特殊的用户来占用过多的系统资源,我们也建议尽量采用资源管理的方式来减轻这种安全威胁。
· 还有一种情况是由磁盘存储空间引起的。假如一个用户有权利存储大量的文件的话,他就有可能只为系统留下很小的空间用来存储日志文件等系统信息。这是一种不良的操作习惯,会给系统带来隐患。这种情况下应该对系统配额作出考虑。
从安全的角度来看,本地的拒绝服务攻击可以比较容易的追踪并消除。而我们这篇文章主要是针对于网络环境下的DoS攻击。下面我们大体讨论一下较为常见的基于网络的拒绝服务攻击:
· Smurf (directed broadcast)。广播信息可以通过一定的手段(通过广播地址或其他机制)发送到整个网络中的机器。当某台机器使用广播地址发送一个ICMP echo请求包时(例如PING),一些系统会回应一个ICMP echo回应包,也就是说,发送一个包会收到许多的响应包。Smurf攻击就是使用这个原理来进行的,当然,它还需要一个假冒的源地址。也就是说在网络中发送源地址为要攻击主机的地址,目的地址为广播地址的包,会使许多的系统响应发送大量的信息给被攻击主机(因为他的地址被攻击者假冒了)。使用网络发送一个包而引出大量回应的方式也被叫做"放大器",这些smurf放大器可以在www.netscan.org网站上获得,一些无能的且不负责任的网站仍有很多的这种漏洞。
· SYN flooding 一台机器在网络中通讯时首先需要建立TCP握手,标准的TCP握手需要三次包交换来建立。一台服务器一旦接收到客户机的SYN包后必须回应一个SYN/ACK包,然后等待该客户机回应给它一个ACK包来确认,才真正建立连接。然而,如果只发送初始化的SYN包,而不发送确认服务器的ACK包会导致服务器一直等待ACK包。由于服务器在有限的时间内只能响应有限数量的连接,这就会导致服务器一直等待回应而无法响应其他机器进行的连接请求。
· Slashdot effect 这种攻击手法使web服务器或其他类型的服务器由于大量的网络传输而过载,一般这些网络流量是针对某一个页面或一个链接而产生的。当然这种现象也会在访问量较大的网站上正常发生,但我们一定要把这些正常现象和拒绝服务攻击区分开来。如果您的服务器突然变得拥挤不堪,甚至无法响应再多的请求时,您应当仔细检查一下这个资源匮乏的现象,确认在10000次点击里全都是合法用户进行的,还是由5000个合法用户和一个点击了5000次的攻击者进行的。
拒绝服务一般都是由过载导致的,而过载一般是因为请求到达了极限。
拒绝服务攻击的发展
由于我们防范手段的加强,拒绝服务攻击手法也在不断的发展。
Tribe Flood Network (tfn) 和tfn2k引入了一个新概念:分布式。这些程序可以使得分散在互联网各处的机器共同完成对一台主机攻击的操作,从而使主机看起来好象是遭到了不同位置的许多主机的攻击。这些分散的机器由几台主控制机操作进行多种类型的攻击,如UDP flood, SYN flood等。
操作系统和网络设备的缺陷在不断地被发现并被黑客所利用来进行恶意的攻击。如果我们清楚的认识到了这一点,我们应当使用下面的两步尽量阻止网络攻击来保护我们的网络:
A)尽可能的修正已经发现的问题和系统漏洞。
B)识别,跟踪或禁止这些令人讨厌的机器或网络对我们的访问。
我们先来讨论一下B),在B)中我们面临的主要问题是如何识别那些恶意攻击的主机,特别是使用拒绝服务攻击的机器。因为这些机器隐藏了他们自己的地址,而冒用被攻击者的地址。攻击者使用了数以千计的恶意伪造包来使我们的主机受到攻击。"tfn2k"的原理就象上面讲的这么简单,而他只不过又提供了一个形象的界面。假如您遭到了分布式的拒绝服务攻击,实在是很难处理。
解决此类问题的一些专业手段----包过滤及其他的路由设置
有一些简单的手法来防止拒绝服务式的攻击。最为常用的一种当然是时刻关注安全信息以期待最好的方法出现。管理员应当订阅安全信息报告,实时的关注所有安全问题的发展。:)
第二步是应用包过滤的技术,主要是过滤对外开放的端口。这些手段主要是防止假冒地址的攻击,使得外部机器无法假冒内部机器的地址来对内部机器发动攻击。
对于应该使用向内的包过滤还是使用向外的包过滤一直存在着争论。RFC 2267建议在全球范围的互连网上使用向内过滤的机制,但是这样会带来很多的麻烦,在中等级别的路由器上使用访问控制列表不会带来太大的麻烦,但是已经满载的骨干路由器上会受到明显的威胁。
另一方面,ISP如果使用向外的包过滤措施会把过载的流量转移到一些不太忙的设备上。 ISP也不关心消费者是否在他们的边界路由器上使用这种技术。当然,这种过滤技术也并不是万无一失的,这依赖于管理人员采用的过滤机制。
使用DNS来跟踪匿名攻击
也许大家仍旧保存着侥幸心理,认为这些互连网上给我们带来无数麻烦DoS漏洞或许随着路由器包过滤,网络协议升级到IPv6或者随时的远程响应等手段变得越来越不重要。但从一个具有责任感的网管的观点来看,我们的目标并不是仅仅阻止拒绝服务攻击,而是要追究到攻击的发起原因及操作者。
当网络中有人使用假冒了源地址的工具(如tfn2k)时,我们虽然没有现成的工具来确认它的合法性,但我们可以通过使用DNS来对其进行分析:
假如攻击者选定了目标www.ttttt.com,他必须首先发送一个DNS请求来解析这个域名,通常那些攻击工具工具会自己执行这一步,调用gethostbyname()函数或者相应的应用程序接口,也就是说,在攻击事件发生前的DNS请求会提供给我们一个相关列表,我们可以利用它来定位攻击者。
使用现成工具或者手工读取DNS请求日志,来读取DNS可疑的请求列表都是切实可行的,然而,它有三个主要的缺点:
· 攻击者一般会以本地的DNS为出发点来对地址进行解析查询,因此我们查到的DNS请求的发起者有可能不是攻击者本身,而是他所请求的本地DNS服务器。尽管这样,如果攻击者隐藏在一个拥有本地DNS的组织内,我们就可以把该组织作为查询的起点。
· 攻击者有可能已经知道攻击目标的IP地址,或者通过其他手段(host, ping)知道了目标的IP地址,亦或是攻击者在查询到IP地址后很长一段时间才开始攻击,这样我们就无法从DNS请求的时间段上来判断攻击者(或他们的本地服务器)。
· DNS对不同的域名都有一个却省的存活时间,因此攻击者可以使用存储在DNS缓存中的信息来解析域名。为了更好做出详细的解析记录,您可以把DNS却省的TTL时间缩小,但这样会导致DNS更多的去查询所以会加重网络带宽的使用。
在许多情况下,只要您拥有足够的磁盘空间,记录所有的DNS请求并不是一种有害的做法。在BIND8.2中做记录的话,可以在named.conf中假如下面的几行:
logging {
channel requestlog { file "dns.log"; };
category queries { requestlog; };
};
使用ngrep来处理tfn2k攻击
根据使用DNS来跟踪tfn2k驻留程序的原理,现在已经出现了称为ngrep的实用工具。经过修改的ngrep可以监听大约五种类型的tfn2k拒绝服务攻击(targa3, SYN flood, UDP flood, ICMP flood 和 smurf),它还有一个循环使用的缓存用来记录DNS和ICMP请求。如果ngrep发觉有攻击行为的话,它会将其缓存中的内容打印出来并继续记录ICMP回应请求。假如攻击者通过ping目标主机的手段来铆定攻击目标的话,在攻击过程中或之后记录ICMP的回应请求是一种捕获粗心的攻击者的方法。由于攻击者还很可能使用其他的服务来核实其攻击的效果(例如web),所以对其他的标准服务也应当有尽量详细的日志记录。
还应当注意,ngrep采用的是监听网络的手段,因此,ngrep无法在交换式的环境中使用。但是经过修改的ngrep可以不必和你的DNS在同一个网段中,但是他必须位于一个可以监听到所有DNS请求的位置。经过修改的ngrep也不关心目标地址,您可以把它放置在DMZ网段,使它能够检查横贯该网络的tfn2k攻击。从理论上讲,它也可以很好的检测出对外的tfn2k攻击。
在ICMP flood事件中,ICMP回应请求的报告中将不包括做为tfn2k flood一部分的ICMP包。Ngrep还可以报告检测出来的除smurf之外的攻击类型(TARGA, UDP, SYN, ICMP等)。混合式的攻击在缺省情况下表现为ICMP攻击,除非你屏蔽了向内的ICMP回应请求,这样它就表现为UDP或SYN攻击。这些攻击的结果都是基本类似的。
以下的代码在使用前应当更改一些参数。
#define DNS_REQUEST_MAX 5000
#define ICMP_REQUEST_MAX 1000
通知ngrep最大的请求跟踪数(在检测攻击之前)。传输较为繁忙的网站应当增加这一数值(网络流量较为繁忙的网站DNS的请求数最好在10,000,而ICMP请求为2000-3000)
#define FLOOD_THRESHOLD 20
用在10秒中内有多少同一类型的攻击包来确认为真正的攻击。数目设计的越大,程序报受攻击的可能性就越小。假如您老是收到错误的警报,那么您应当增加一下这个数值。
#define DNS_SERVER_IP "10.0.0.8"
Ngrep通过监视DNS服务器的53端口的UDP包来跟踪向内的DNS请求(只有UDP)。因此,ngrep需要知道您的DNS服务器的IP地址。
我们的设备可能会有多个DNS服务器,但我们认为对一台DNS服务器的支持足以证明这项技术的能力。
#define TTL_THRESHOLD 150
tfn2k SYN flood 攻击使用的 TTL值通常在200-255的范围内。估计到攻击者与目标主机之间不止50跳,因此我们可以只查找TTL时间高于150的包。假如您相信攻击者在50跳左右,那么您可以对TTL的限制进行一下更改。
如果/usr/local/include 和/usr/local/include/net目录在linux系统中不存在的话,安装会失败。加入您在安装时遇到了pcap.h 或 bpf.h的错误时你可以运行
mkdir /usr/local/include; mkdir /usr/local/include/net然后重新运行make install-incl。然后我们需要编译ngrep 。首先解包
tar xvzf ngrep-1.35.tar.gz
然后进行配置
cd ngrep; ./configure
然后把ngrep.c复制到ngrep目录里。你可以覆盖也可以备份原始的ngrep.c文件。在这里,您应当回顾在修改过的ngrep.c里的配置,至少您应当把DNS_SERVER_IP更改为您所使用的DNS的地址。更改完毕后你就可以运行make,这样就建立了ngrep应用程序。
Modified ngrep.c source code
/********* TFN detection defines *******************************/
/* how many DNS and ICMP requests to track */
#define DNS_REQUEST_MAX 5000
#define ICMP_REQUEST_MAX 1000
/* flood threshold is matches per 10 seconds */
#define FLOOD_THRESHOLD 20
/* IP of your DNS server */
#define DNS_SERVER_IP "10.9.100.8"
/* TFN syn uses ttl between 200-255. Assuming less than 50 hops,
flag stuff with ttl > TTL_THRESHOLD (other critera are used
as well) */
#define TTL_THRESHOLD 150
/**************************************************************/
#include
#include
#include
#ifdef LINUX
#include
#endif
#if defined(BSD) || defined(SOLARIS)
#include
#include
#include
#include
#include
#include
#include
#endif
#if defined(LINUX) && !defined(HAVE_IF_ETHER_H)
h>
#include
#include
#include "regex.h"
#include "ngrep.h"
tatic char rcsver[] = "$Revision: 1.35 $";
int snaplen = 65535, promisc = 1, to = 1000;
int show_empty = 0, show_hex = 0, quiet = 0;
int match_after = 0, keep_matching = 0, invert_match = 0;
int matches = 0, max_matches = 0;
char pc_err[PCAP_ERRBUF_SIZE], *re_err;
int (*match_func)();
int re_match_word = 0, re_ignore_case = 0;
truct re_pattern_buffer pattern;
char *regex, *filter;
truct bpf_program pcapfilter;
truct in_addr net, mask;
char *dev = NULL;
int link_offset;
cap_t *pd;
/**************** TFN2K detection **********************************/
unsigned int udp_flood_count=0, syn_flood_count=0;
unsigned int targa_flood_count=0, icmp_flood_count=0;
unsigned long my_dns, targ1, targ2, rfp1, icmp_flood=1;
time_t t;
unsigned long dns_circbuff[DNS_REQUEST_MAX];
unsigned int dns_cb_ptr=0;
unsigned long icmp_circbuff[ICMP_REQUEST_MAX];
unsigned int icmp_cb_ptr=0;
void add_dns (unsigned long ipadd){
dns_circbuff[dns_cb_ptr++]=ipadd;
if (dns_cb_ptr==DNS_REQUEST_MAX) dns_cb_ptr=0;}
void add_icmp (unsigned long ipadd){
icmp_circbuff[icmp_cb_ptr++]=ipadd;
if (icmp_cb_ptr==ICMP_REQUEST_MAX) dns_cb_ptr=0;}
void anti_tfn_init (void) {
unsigned int x;
for(x=0;xfor(x=0;xmy_dns=inet_addr(DNS_SERVER_IP);
rintf("Ngrep with TFN detection modifications by wiretrip / www.wiretrip.netn");
rintf("Watching DNS server: %sn",inet_ntoa(my_dns));
targ1=htons(16383); targ2=htons(8192);
rfp1=htons(~(ICMP_ECHO << 8)); /* hopefull this is universal ;) */
alarm(20);}
void print_circbuffs (void) {
unsigned int x;
rintf("Last (%u) DNS requests:n",DNS_REQUEST_MAX);
for(x=0;xif(dns_circbuff[x]>0) printf("%sn",inet_ntoa(dns_circbuff[x]));
rintf("nLast (%u) ICMP echo requests (pings):n",ICMP_REQUEST_MAX);
for(x=0;xif (icmp_circbuff[x]>0) printf("%sn",inet_ntoa(icmp_circbuff[x]));}
void reset_counters (int sig) {
udp_flood_count=syn_flood_count=targa_flood_count=icmp_flood_count=0;
alarm(10);}
void tfn_attack_detected (char* attack_type){
if(icmp_flood==0) return;
(void)time(&t);
rintf("n%s",ctime(&t));
rintf("A TFN2K %s attack has been detected!nn",attack_type);
rint_circbuffs();
rintf("nIncoming realtime ICMP echo requests (pings):n");
icmp_flood=0;}
/*********************************************************************/
int main(int argc, char **argv) {
char c;
ignal(SIGINT,dealloc);
ignal(SIGQUIT,dealloc);
ignal(SIGABRT,dealloc);
ignal(SIGPIPE,dealloc);
ignal(SIGALRM,reset_counters);
anti_tfn_init();
while ((c = getopt(argc, argv, "d:")) != EOF) {
witch (c) {
case d:
dev = optarg;
reak;}}
if (!dev)
if (!(dev = pcap_lookupdev(pc_err))) {
error(pc_err);
exit(-1);}
if ((pd = pcap_open_live(dev, snaplen, promisc, to, pc_err)) == NULL) {
error(pc_err);
exit(-1);}
if (pcap_lookupnet(dev,&net.s_addr,&mask.s_addr, pc_err) == -1) {
error(pc_err);
exit(-1);}
rintf("interface: %s (%s/", dev, inet_ntoa(net));
rintf("%s)n",inet_ntoa(mask));
witch(pcap_datalink(pd)) {
case DLT_EN10MB:
case DLT_IEEE802:
link_offset = ETHHDR_SIZE;
reak;
case DLT_SLIP:
link_offset = SLIPHDR_SIZE;
reak;
case DLT_PPP:
link_offset = PPPHDR_SIZE;
reak;
case DLT_RAW:
link_offset = RAWHDR_SIZE;
reak;
case DLT_NULL:
link_offset = LOOPHDR_SIZE;
reak;
default:
fprintf(stderr,"fatal: unsupported interface typen");
exit(-1);
} while (pcap_loop(pd,0,(pcap_handler)process,0));}
void process(u_char *data1, struct pcap_pkthdr* h, u_char *p) {
truct ip* ip_packet = (struct ip *)(p + link_offset);
witch (ip_packet->ip_p) {
case IPPROTO_TCP: {
truct tcphdr* tcp = (struct tcphdr *)(((char *)ip_packet) + ip_packet->ip_hl*4);
if(tcp->th_flags==0x22 && ip_packet->ip_ttl > TTL_THRESHOLD){
if(++syn_flood_count > FLOOD_THRESHOLD) tfn_attack_detected("SYN");}
if(ip_packet->ip_ttl==0 &&
(ip_packet->ip_off==targ1 || ip_packet->ip_off==targ2)){
if(++targa_flood_count > FLOOD_THRESHOLD) tfn_attack_detected("TARGA");
}} break;
case IPPROTO_UDP: {
truct udphdr* udp = (struct udphdr *)(((char *)ip_packet) + ip_packet->ip_hl*4);
#ifdef HAVE_DUMB_UDPHDR
if ((ntohs(udp->source) + ntohs(udp->dest)) == 65536) {
#else
if ((ntohs(udp->uh_sport) + ntohs(udp->uh_dport)) == 65536) {
#endif
if(++udp_flood_count > FLOOD_THRESHOLD) tfn_attack_detected("UDP");}
if(ip_packet->ip_dst.s_addr==my_dns &&
#ifdef HAVE_DUMB_UDPHDR
tohs(udp->dest) == 53) {
#else
tohs(udp->uh_dport) == 53) {
#endif
add_dns(ip_packet->ip_src.s_addr);
}} break;
icmp_cksum==rfp1 && ip_packet->ip_ttl==0){
if(++icmp_flood_count > FLOOD_THRESHOLD) tfn_attack_detected("ICMP");
} else { if(icmp_flood>0) add_icmp(ip_packet->ip_src.s_addr);
else printf("%sn",inet_ntoa(ip_packet->ip_src));}}}}}
void dealloc(int sig) {
if (filter) free(filter);
exit(0);}