实验环境
我使用的是 Ubuntu 24.04.4,基本环境配置见ARP攻击原理学习
网络结构如下:
| 节点 |
IP 地址 |
作用 |
| 外部主机 |
10.9.0.5 |
用来模拟外部访问者 |
| 路由器 |
10.9.0.1(外网口eth0 )/ 192.168.60.1(内网口eth1) |
作为防火墙 |
| 内部服务器 |
192.168.60.5 |
受防火墙保护 |
启动实验前先清理容器环境,防止被之前的网络配置干扰
1
2
|
sudo docker stop $(sudo docker ps -q) 2>/dev/null
sudo docker system prune -f
|
进入实验目录并启动
1
2
|
cd seed-labs/category-network/Firewall/Labsetup
sudo docker compose up -d
|
打开三个终端分别进入对应的容器
外部主机:
1
|
docker exec -it hostA-10.9.0.5 /bin/bash
|
路由器:
1
|
docker exec -it seed-router /bin/bash
|
内部服务器:
1
|
docker exec -it host1-192.168.60.5 /bin/bash
|
Netfilter内核级防火墙
内核级防火墙和普通应用层过滤不同,是在内核中写一个模块,当包进入内核时就进行处理,拦截位置更靠前。这里需要实现让外部主机无法通过 ping 和 telnet 访问路由器
防火墙的拦截程序在 seedFilter.c,大致逻辑就是注册两个hook,hook1在确认目标是自己时调用打印函数打印包信息,hook2在包进入时调用丢包函数进行判断,丢弃目标 IP 是10.9.0.1的 ICMP 包和 23 端口的 TCP 包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/if_ether.h>
#include <linux/inet.h>
static struct nf_hook_ops hook1, hook2;
unsigned int blockTCPICMP(void *priv, struct sk_buff *skb,
const struct nf_hook_state *state)
{
struct iphdr *iph;
struct tcphdr *tcph;
u16 port = 23;
char ip[16] = "10.9.0.1";
u32 ip_addr;
if (!skb) return NF_ACCEPT;
iph = ip_hdr(skb);
in4_pton(ip, -1, (u8 *)&ip_addr, '\0', NULL);
if (iph->daddr != ip_addr) {
return NF_ACCEPT;
}
if (iph->protocol == IPPROTO_ICMP) {
printk(KERN_WARNING "*** Dropping %pI4 (ICMP)\n", &(iph->daddr));
return NF_DROP;
}
if (iph->protocol == IPPROTO_TCP) {
tcph = tcp_hdr(skb);
if (ntohs(tcph->dest) == port) {
printk(KERN_WARNING "*** Dropping %pI4 (TCP), port %d\n", &(iph->daddr), port);
return NF_DROP;
}
}
return NF_ACCEPT;
}
unsigned int printInfo(void *priv, struct sk_buff *skb,
const struct nf_hook_state *state)
{
struct iphdr *iph;
char *hook;
char *protocol;
switch (state->hook){
case NF_INET_LOCAL_IN: hook = "LOCAL_IN"; break;
case NF_INET_LOCAL_OUT: hook = "LOCAL_OUT"; break;
case NF_INET_PRE_ROUTING: hook = "PRE_ROUTING"; break;
case NF_INET_POST_ROUTING: hook = "POST_ROUTING"; break;
case NF_INET_FORWARD: hook = "FORWARD"; break;
default: hook = "IMPOSSIBLE"; break;
}
printk(KERN_INFO "*** %s\n", hook); // Print out the hook info
iph = ip_hdr(skb);
switch (iph->protocol){
case IPPROTO_UDP: protocol = "UDP"; break;
case IPPROTO_TCP: protocol = "TCP"; break;
case IPPROTO_ICMP: protocol = "ICMP"; break;
default: protocol = "OTHER"; break;
}
// Print out the IP addresses and protocol
printk(KERN_INFO " %pI4 --> %pI4 (%s)\n",
&(iph->saddr), &(iph->daddr), protocol);
return NF_ACCEPT;
}
int registerFilter(void) {
printk(KERN_INFO "Registering filters.\n");
hook1.hook = printInfo;
hook1.hooknum = NF_INET_LOCAL_IN;
hook1.pf = PF_INET;
hook1.priority = NF_IP_PRI_FIRST;
nf_register_net_hook(&init_net, &hook1);
hook2.hook = blockTCPICMP;
hook2.hooknum = NF_INET_PRE_ROUTING;
hook2.pf = PF_INET;
hook2.priority = NF_IP_PRI_FIRST;
nf_register_net_hook(&init_net, &hook2);
return 0;
}
void removeFilter(void) {
printk(KERN_INFO "The filters are being removed.\n");
nf_unregister_net_hook(&init_net, &hook1);
nf_unregister_net_hook(&init_net, &hook2);
}
module_init(registerFilter);
module_exit(removeFilter);
MODULE_LICENSE("GPL");
|
由于容器中没有安装make,新开一个终端,在宿主机进入源码目录,这里面包含 Makefile 和 seedFilter.c。在这里对内核模块进行编译
1
2
|
cd /home/th4uma/seed-labs/category-network/Firewall/Labsetup/volumes/packet_filter
make
|
在路由器容器中进入源码目录
1
|
cd /volumes/packet_filter
|
移除旧模块,清空旧内核日志
1
2
|
rmmod seedFilter
dmesg -C
|
手动加载模块,然后用 lsmod 查看模块是否加载成功
1
2
|
insmod seedFilter.ko
lsmod | grep seedFilter
|
在外部主机中访问路由器,可以看到外部主机无法收到响应,ping全部丢包,telnet 也一直卡在尝试连接
1
2
|
ping 10.9.0.1
telnet 10.9.0.1
|
回到路由器中查看内核日志,可以看到丢弃ICMP包和23端口的TCP包的日志,和上面代码对应,说明拦截规则生效
无状态防火墙
这部分是用 iptables 配置无状态防火墙规则。为了防止外部恶意探测,这里实现让外部主机不能ping通内部服务器,但内部服务器可以正常ping外部主机
在路由器中配置 FORWARD 链,它负责处理路由器转发的数据包
1
2
3
4
|
iptables -A FORWARD -i eth0 -p icmp --icmp-type echo-request -j DROP
iptables -A FORWARD -i eth1 -p icmp --icmp-type echo-request -j ACCEPT
iptables -A FORWARD -p icmp --icmp-type echo-reply -j ACCEPT
iptables -P FORWARD DROP
|
含义分别是:
- 丢掉外网口
eth0 收到的 ICMP 请求包,也就是阻止外部主机 Ping 内部服务器
- 正常转发内网口
eth1 收到的 ICMP 请求包,也就是允许内部服务器主动 Ping 外部主机
- 正常转发所有 ICMP 响应包,让内部发起的 Ping 能收到响应
- 默认策略是
DROP,丢掉所有没有匹配规则的包
在外部主机中 Ping 内部服务器,全部丢包
在内部服务器中 Ping 外部主机,全部成功,说明达到了防火墙的目的
网络流量限速
前两个部分都是直接丢弃所有外部特定流量,适用于不可访问的服务器,而这部分适用于可访问服务器。这里用 iptables 的 limit 模块限制特定源 IP 的发包速率,防止受到 ICMP 泛洪攻击
先清空之前的 FORWARD 规则
添加限速规则
1
2
3
|
iptables -A FORWARD -s 10.9.0.5 -p icmp -m limit --limit 10/minute --limit-burst 5 -j ACCEPT
iptables -A FORWARD -s 10.9.0.5 -p icmp -j DROP
iptables -A FORWARD -j ACCEPT
|
含义分别是:
- 对于
10.9.0.5这个 IP 发出的 ICMP 包,只允许平均每分钟 10 个包通过,并允许最开始突发通过 5 个包
- 丢掉
10.9.0.5这个 IP 的其他 ICMP 包
- 放行其他不匹配前两条规则的包,防止影响其他转发流量
在外部主机中 Ping 内部服务器
可以看到前5个包全部通过,这是--limit-burst 5允许的突发流量,之后大约每6秒才会成功返回1个数据包,这就是--limit 10/minute在起限制
现实应用中还可以根据源 IP 和协议类型来设置限速,从而防止DoS。但这种方法无法避免DDoS