身为安全工程师,我用 eBPF 实时监测网络攻击,让 DDoS 无处遁形
为什么选择 eBPF?
利用 eBPF 检测 DDoS 攻击
1. 监测 SYN Flood 攻击
2. 监测 UDP Flood 攻击
3. 采取防御措施
利用 eBPF 检测端口扫描
1. 监测 TCP 连接请求
2. 监测 UDP 包
3. 采取防御措施
总结与展望
作为一名安全工程师,保障公司网络安全是我的首要职责。面对日益猖獗的网络攻击,传统的安全防御手段往往显得力不从心。为了更有效地检测和防御网络攻击,我开始探索 eBPF (Extended Berkeley Packet Filter) 技术。eBPF 允许我们在内核中动态地运行自定义代码,而无需修改内核源代码或加载内核模块,这为网络安全领域带来了革命性的变革。本文将分享我如何利用 eBPF 技术来实时监测网络攻击行为,例如 DDoS 攻击和端口扫描等,并及时采取防御措施,从而提升公司整体的网络安全防护能力。
为什么选择 eBPF?
在深入探讨 eBPF 如何用于网络安全之前,让我们先了解一下为什么选择 eBPF。传统的网络安全工具,例如入侵检测系统(IDS)和入侵防御系统(IPS),通常在用户空间运行,需要将网络数据包从内核空间复制到用户空间进行分析。这种方式存在以下几个缺点:
- 性能开销大:数据包在内核空间和用户空间之间复制会带来显著的性能开销,尤其是在高流量的网络环境下,容易造成系统瓶颈。
- 延迟高:数据包需要经过多个处理环节才能完成分析,导致检测和响应延迟较高,难以应对快速变化的网络攻击。
- 灵活性不足:传统的安全工具通常基于预定义的规则和签名进行检测,难以应对新型或未知的攻击。
eBPF 则不同,它具有以下优势:
- 高性能:eBPF 程序直接在内核中运行,避免了数据包在内核空间和用户空间之间复制的开销,大大提高了性能。
- 低延迟:eBPF 程序可以实时地分析网络数据包,及时发现并阻止恶意行为,降低了攻击造成的损失。
- 高灵活性:eBPF 允许我们编写自定义的检测逻辑,可以灵活地应对各种新型攻击。
- 安全性:eBPF 程序在运行前会经过内核的验证器(verifier)检查,确保程序的安全性,避免对系统造成危害。
正是由于 eBPF 具有以上优势,我决定将其应用到网络安全领域,以提升公司网络的安全防护能力。
利用 eBPF 检测 DDoS 攻击
DDoS (Distributed Denial of Service) 攻击是一种常见的网络攻击方式,攻击者通过控制大量的“僵尸”主机向目标服务器发送海量的请求,导致目标服务器资源耗尽,无法正常提供服务。利用 eBPF,我们可以实时地监测网络流量,检测 DDoS 攻击行为。
1. 监测 SYN Flood 攻击
SYN Flood 攻击是一种常见的 DDoS 攻击方式,攻击者通过发送大量的 SYN 包,而不完成 TCP 三次握手,导致服务器资源被占用,无法响应正常的连接请求。我们可以使用 eBPF 来监测 SYN 包的流量,如果发现 SYN 包的流量异常增高,则可以判断可能存在 SYN Flood 攻击。
以下是一个简单的 eBPF 程序,用于监测 SYN 包的流量:
#include <linux/bpf.h> #include <linux/pkt_cls.h> #include <linux/ip.h> #include <linux/tcp.h> #include <bpf/bpf_helpers.h> #include <bpf/bpf_endian.h> #define MAX_ADDRS 1024 struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(key_size, 4); /* IPv4 address */ __uint(value_size, 8); /* Packet count */ __uint(max_entries, MAX_ADDRS); } syn_flood_map SEC("maps"); SEC("xdp") int xdp_syn_flood(struct xdp_md *ctx) { void *data_end = (void *)(long)ctx->data_end; void *data = (void *)(long)ctx->data; struct ethhdr *eth = data; struct iphdr *ip; struct tcphdr *tcp; __u32 src_addr; __u64 *pkt_cnt; int ip_header_size; /* 解析以太网头部 */ if (data + sizeof(*eth) > data_end) return XDP_PASS; if (bpf_ntohs(eth->h_proto) != ETH_P_IP) return XDP_PASS; /* 解析 IP 头部 */ ip = data + sizeof(*eth); if (data + sizeof(*eth) + sizeof(*ip) > data_end) return XDP_PASS; if (ip->protocol != IPPROTO_TCP) return XDP_PASS; ip_header_size = ip->ihl * 4; if (ip_header_size < sizeof(*ip)) return XDP_PASS; /* 解析 TCP 头部 */ tcp = data + sizeof(*eth) + ip_header_size; if (data + sizeof(*eth) + ip_header_size + sizeof(*tcp) > data_end) return XDP_PASS; /* 只监测 SYN 包 */ if (!(tcp->syn && !tcp->ack)) return XDP_PASS; /* 获取源 IP 地址 */ src_addr = bpf_ntohl(ip->saddr); /* 更新 SYN 包计数 */ pkt_cnt = bpf_map_lookup_elem(&syn_flood_map, &src_addr); if (!pkt_cnt) { __u64 init_cnt = 1; bpf_map_update_elem(&syn_flood_map, &src_addr, &init_cnt, BPF_ANY); } else { *pkt_cnt += 1; } return XDP_PASS; } char _license[] SEC("license") = "GPL";
该 eBPF 程序的功能是:
- 解析网络数据包的以太网头部、IP 头部和 TCP 头部。
- 判断是否为 SYN 包(SYN 标志位为 1,ACK 标志位为 0)。
- 如果是 SYN 包,则获取源 IP 地址,并在
syn_flood_map
中更新该 IP 地址的 SYN 包计数。
我们可以使用 bpftool
等工具将该 eBPF 程序加载到内核中,并定期地从 syn_flood_map
中读取数据,如果发现某个 IP 地址的 SYN 包计数异常增高,则可以判断该 IP 地址可能正在发起 SYN Flood 攻击。
2. 监测 UDP Flood 攻击
UDP Flood 攻击是另一种常见的 DDoS 攻击方式,攻击者通过发送大量的 UDP 包,导致服务器带宽耗尽,无法响应正常的请求。与 SYN Flood 攻击不同,UDP Flood 攻击不需要建立连接,因此更加隐蔽和难以防御。我们可以使用 eBPF 来监测 UDP 包的流量,如果发现 UDP 包的流量异常增高,则可以判断可能存在 UDP Flood 攻击。
以下是一个简单的 eBPF 程序,用于监测 UDP 包的流量:
#include <linux/bpf.h> #include <linux/pkt_cls.h> #include <linux/ip.h> #include <linux/udp.h> #include <bpf/bpf_helpers.h> #include <bpf/bpf_endian.h> #define MAX_ADDRS 1024 struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(key_size, 4); /* IPv4 address */ __uint(value_size, 8); /* Packet count */ __uint(max_entries, MAX_ADDRS); } udp_flood_map SEC("maps"); SEC("xdp") int xdp_udp_flood(struct xdp_md *ctx) { void *data_end = (void *)(long)ctx->data_end; void *data = (void *)(long)ctx->data; struct ethhdr *eth = data; struct iphdr *ip; struct udphdr *udp; __u32 src_addr; __u64 *pkt_cnt; int ip_header_size; /* 解析以太网头部 */ if (data + sizeof(*eth) > data_end) return XDP_PASS; if (bpf_ntohs(eth->h_proto) != ETH_P_IP) return XDP_PASS; /* 解析 IP 头部 */ ip = data + sizeof(*eth); if (data + sizeof(*eth) + sizeof(*ip) > data_end) return XDP_PASS; if (ip->protocol != IPPROTO_UDP) return XDP_PASS; ip_header_size = ip->ihl * 4; if (ip_header_size < sizeof(*ip)) return XDP_PASS; /* 解析 UDP 头部 */ udp = data + sizeof(*eth) + ip_header_size; if (data + sizeof(*eth) + ip_header_size + sizeof(*udp) > data_end) return XDP_PASS; /* 获取源 IP 地址 */ src_addr = bpf_ntohl(ip->saddr); /* 更新 UDP 包计数 */ pkt_cnt = bpf_map_lookup_elem(&udp_flood_map, &src_addr); if (!pkt_cnt) { __u64 init_cnt = 1; bpf_map_update_elem(&udp_flood_map, &src_addr, &init_cnt, BPF_ANY); } else { *pkt_cnt += 1; } return XDP_PASS; } char _license[] SEC("license") = "GPL";
该 eBPF 程序与监测 SYN Flood 攻击的程序类似,不同之处在于它监测的是 UDP 包的流量,并将 UDP 包的计数存储在 udp_flood_map
中。我们可以使用类似的方式将该 eBPF 程序加载到内核中,并定期地从 udp_flood_map
中读取数据,如果发现某个 IP 地址的 UDP 包计数异常增高,则可以判断该 IP 地址可能正在发起 UDP Flood 攻击。
3. 采取防御措施
当我们检测到 DDoS 攻击时,需要及时采取防御措施,以减轻攻击造成的影响。常见的防御措施包括:
限制源 IP 地址的流量:可以使用防火墙或流量控制工具限制来自攻击源 IP 地址的流量,例如使用
iptables
命令:iptables -A INPUT -s <攻击源IP地址> -j DROP
清洗流量:可以使用 DDoS 防护服务,将流量重定向到清洗中心,过滤掉恶意流量,只将正常的流量转发到服务器。
增加服务器带宽:增加服务器的带宽可以提高服务器的抗攻击能力,但这种方式成本较高,且无法完全防御 DDoS 攻击。
部署 CDN:将网站内容缓存到 CDN 节点上,可以分散攻击流量,减轻服务器的压力。
在实际应用中,我们需要根据具体的攻击类型和流量情况选择合适的防御措施,并不断地调整和优化防御策略,以应对不断变化的网络攻击。
利用 eBPF 检测端口扫描
端口扫描是一种常见的网络侦查手段,攻击者通过扫描目标主机的开放端口,了解目标主机提供的服务,从而寻找潜在的漏洞。利用 eBPF,我们可以实时地监测端口扫描行为,及时发现并阻止恶意扫描。
1. 监测 TCP 连接请求
端口扫描通常通过发送 TCP 连接请求来探测目标主机的开放端口。我们可以使用 eBPF 来监测 TCP 连接请求,如果发现某个 IP 地址在短时间内向目标主机发送了大量的连接请求,则可以判断该 IP 地址可能正在进行端口扫描。
以下是一个简单的 eBPF 程序,用于监测 TCP 连接请求:
#include <linux/bpf.h> #include <linux/pkt_cls.h> #include <linux/ip.h> #include <linux/tcp.> #include <bpf/bpf_helpers.h> #include <bpf/bpf_endian.h> #define MAX_ADDRS 1024 struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(key_size, 4); /* IPv4 address */ __uint(value_size, 8); /* Connection count */ __uint(max_entries, MAX_ADDRS); } port_scan_map SEC("maps"); SEC("xdp") int xdp_port_scan(struct xdp_md *ctx) { void *data_end = (void *)(long)ctx->data_end; void *data = (void *)(long)ctx->data; struct ethhdr *eth = data; struct iphdr *ip; struct tcphdr *tcp; __u32 src_addr; __u64 *conn_cnt; int ip_header_size; /* 解析以太网头部 */ if (data + sizeof(*eth) > data_end) return XDP_PASS; if (bpf_ntohs(eth->h_proto) != ETH_P_IP) return XDP_PASS; /* 解析 IP 头部 */ ip = data + sizeof(*eth); if (data + sizeof(*eth) + sizeof(*ip) > data_end) return XDP_PASS; if (ip->protocol != IPPROTO_TCP) return XDP_PASS; ip_header_size = ip->ihl * 4; if (ip_header_size < sizeof(*ip)) return XDP_PASS; /* 解析 TCP 头部 */ tcp = data + sizeof(*eth) + ip_header_size; if (data + sizeof(*eth) + ip_header_size + sizeof(*tcp) > data_end) return XDP_PASS; /* 只监测 SYN 包 */ if (tcp->syn) { /* 获取源 IP 地址 */ src_addr = bpf_ntohl(ip->saddr); /* 更新连接计数 */ conn_cnt = bpf_map_lookup_elem(&port_scan_map, &src_addr); if (!conn_cnt) { __u64 init_cnt = 1; bpf_map_update_elem(&port_scan_map, &src_addr, &init_cnt, BPF_ANY); } else { *conn_cnt += 1; } } return XDP_PASS; } char _license[] SEC("license") = "GPL";
该 eBPF 程序的功能是:
- 解析网络数据包的以太网头部、IP 头部和 TCP 头部。
- 判断是否为 SYN 包(SYN 标志位为 1)。
- 如果是 SYN 包,则获取源 IP 地址,并在
port_scan_map
中更新该 IP 地址的连接计数。
我们可以使用 bpftool
等工具将该 eBPF 程序加载到内核中,并定期地从 port_scan_map
中读取数据,如果发现某个 IP 地址在短时间内向目标主机发送了大量的连接请求,则可以判断该 IP 地址可能正在进行端口扫描。
2. 监测 UDP 包
除了 TCP 端口扫描,攻击者还可以使用 UDP 包进行端口扫描。与 TCP 端口扫描不同,UDP 端口扫描不需要建立连接,因此更加隐蔽。我们可以使用 eBPF 来监测 UDP 包,如果发现某个 IP 地址向目标主机发送了大量的 UDP 包,则可以判断该 IP 地址可能正在进行 UDP 端口扫描。
以下是一个简单的 eBPF 程序,用于监测 UDP 包:
#include <linux/bpf.h> #include <linux/pkt_cls.h> #include <linux/ip.h> #include <linux/udp.h> #include <bpf/bpf_helpers.h> #include <bpf/bpf_endian.h> #define MAX_ADDRS 1024 struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(key_size, 4); /* IPv4 address */ __uint(value_size, 8); /* Packet count */ __uint(max_entries, MAX_ADDRS); } udp_scan_map SEC("maps"); SEC("xdp") int xdp_udp_scan(struct xdp_md *ctx) { void *data_end = (void *)(long)ctx->data_end; void *data = (void *)(long)ctx->data; struct ethhdr *eth = data; struct iphdr *ip; struct udphdr *udp; __u32 src_addr; __u64 *pkt_cnt; int ip_header_size; /* 解析以太网头部 */ if (data + sizeof(*eth) > data_end) return XDP_PASS; if (bpf_ntohs(eth->h_proto) != ETH_P_IP) return XDP_PASS; /* 解析 IP 头部 */ ip = data + sizeof(*eth); if (data + sizeof(*eth) + sizeof(*ip) > data_end) return XDP_PASS; if (ip->protocol != IPPROTO_UDP) return XDP_PASS; ip_header_size = ip->ihl * 4; if (ip_header_size < sizeof(*ip)) return XDP_PASS; /* 解析 UDP 头部 */ udp = data + sizeof(*eth) + ip_header_size; if (data + sizeof(*eth) + ip_header_size + sizeof(*udp) > data_end) return XDP_PASS; /* 获取源 IP 地址 */ src_addr = bpf_ntohl(ip->saddr); /* 更新 UDP 包计数 */ pkt_cnt = bpf_map_lookup_elem(&udp_scan_map, &src_addr); if (!pkt_cnt) { __u64 init_cnt = 1; bpf_map_update_elem(&udp_scan_map, &src_addr, &init_cnt, BPF_ANY); } else { *pkt_cnt += 1; } return XDP_PASS; } char _license[] SEC("license") = "GPL";
该 eBPF 程序与监测 UDP Flood 攻击的程序类似,不同之处在于该程序的目的是为了检测端口扫描行为。我们可以使用类似的方式将该 eBPF 程序加载到内核中,并定期地从 udp_scan_map
中读取数据,如果发现某个 IP 地址向目标主机发送了大量的 UDP 包,则可以判断该 IP 地址可能正在进行 UDP 端口扫描。
3. 采取防御措施
当我们检测到端口扫描行为时,可以采取以下防御措施:
限制源 IP 地址的流量:可以使用防火墙或流量控制工具限制来自扫描源 IP 地址的流量,例如使用
iptables
命令:iptables -A INPUT -s <扫描源IP地址> -j DROP
关闭不必要的端口:关闭不必要的端口可以减少攻击面,降低被攻击的风险。
使用端口敲门:端口敲门是一种身份验证技术,只有在客户端按照预定义的顺序向服务器发送一系列的连接请求后,服务器才会开放相应的端口。这种方式可以有效地防止端口扫描。
总结与展望
eBPF 作为一种强大的网络安全工具,可以帮助我们实时地监测网络攻击行为,并及时采取防御措施。在本文中,我分享了如何利用 eBPF 来检测 DDoS 攻击和端口扫描等常见的网络攻击行为。当然,eBPF 的应用场景远不止于此,例如,我们还可以使用 eBPF 来监测恶意软件传播、检测漏洞利用等。随着 eBPF 技术的不断发展,相信它将在网络安全领域发挥越来越重要的作用。
需要注意的是,编写和部署 eBPF 程序需要一定的技术水平,且 eBPF 程序的安全性至关重要。在实际应用中,我们需要 тщательно 审核 eBPF 程序的代码,并进行充分的测试,以确保程序的安全性和可靠性。同时,我们还需要不断地学习和掌握 eBPF 的最新技术,以便更好地利用它来保护我们的网络安全。