Featured image of post 防火墙探索

防火墙探索

实验环境

我使用的是 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 内核级防火墙

防火墙的拦截程序在 seedFilter.c

  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
#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 udphdr *udph;

   u16  port   = 23;
   char ip[16] = "10.9.0.1";
   u32  ip_addr;

   if (!skb) return NF_ACCEPT;

   iph = ip_hdr(skb);
   // Convert the IPv4 address from dotted decimal to 32-bit binary
   in4_pton(ip, -1, (u8 *)&ip_addr, '\0', NULL);

   if (iph->protocol == IPPROTO_UDP) {
       udph = udp_hdr(skb);
       if (iph->daddr == ip_addr && ntohs(udph->dest) == port){
            printk(KERN_WARNING "*** Dropping %pI4 (UDP), 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 = blockUDP;
   hook2.hooknum = NF_INET_POST_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,新开一个终端,在宿主机进入源码目录,这里面包含 MakefileseedFilter.c。在这里对内核进行编译

1
2
cd /home/th4uma/seed-labs/category-network/Firewall/Labsetup/volumes/packet_filter
make

在路由器容器中进入源码目录

1
cd /volumes/packet_filter

为了方便观察新的日志,加载模块前先清空 dmesg

1
2
dmesg -C
insmod seedFilter.ko

lsmod 查看模块是否加载成功:

1
lsmod | grep seedFilter

模块加载后,在外部主机中访问路由器:

1
2
ping 10.9.0.1
telnet 10.9.0.1

可以看到外部主机无法收到响应,ping 出现 100% 丢包,telnet 也一直卡在连接阶段。

回到路由器中查看内核日志:

1
dmesg | tail -20

日志中出现了 Dropping 10.9.0.1 (ICMP)Dropping 10.9.0.1 (TCP), port 23,说明内核模块已经在底层拦截了发往路由器的 ICMP 和 Telnet 流量。

这个任务和普通应用层过滤不同,拦截动作发生在内核网络协议栈中。只要数据包匹配模块中写好的条件,就会直接返回 NF_DROP,后面的网络栈不会继续处理这个包,所以外部主机表现为收不到任何响应。

无状态防火墙规则

前面的 Netfilter 模块是直接写内核代码进行过滤,这一部分改用 iptables 配置无状态防火墙规则。目标是让外部主机不能主动 Ping 内部服务器,但内部服务器仍然可以正常访问外部主机。

为了避免前面加载的内核模块影响后续实验,可以先在路由器中卸载模块并清理旧规则:

1
2
rmmod seedFilter
iptables -F

接着在路由器中配置 FORWARD 链。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 echo-request 直接丢弃,也就是阻止外部主机主动 Ping 内部服务器;
  • 从内网口 eth1 进入的 ICMP echo-request 允许通过,也就是允许内部服务器主动 Ping 外部主机;
  • ICMP echo-reply 允许通过,保证内部主动发起的 Ping 能收到响应;
  • 默认策略设置为 DROP,没有匹配到规则的转发流量全部丢弃。

在外部主机中尝试 Ping 内部服务器:

1
ping 192.168.60.5

可以看到外部主机访问内部服务器失败,出现 100% 丢包。再从内部服务器向外部方向发起 Ping:

1
ping 10.9.0.5

内部方向的访问可以正常收到响应,说明规则实现了简单的单向访问控制。这个规则集本身没有记录连接状态,只是根据入口网卡、协议类型和 ICMP 类型进行匹配,所以属于无状态过滤。

网络流量限速

除了直接允许或拒绝数据包,防火墙还可以限制某类流量的速率。这里使用 iptableslimit 模块限制来自 10.9.0.5 的 ICMP 流量,用来模拟对 ICMP 泛洪的防护。

先在路由器中清空之前的 FORWARD 规则:

1
iptables -F 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 的 ICMP 流量,但速率限制为每分钟 10 个包,并允许最开始有 5 个包的突发流量。第二条规则会丢弃同一源地址剩余的 ICMP 包。第三条规则放行其他流量,防止实验过程中把无关通信一起阻断。

在外部主机中连续 Ping 内部服务器:

1
ping 192.168.60.5

从结果可以看到,前 5 个包连续通过,这是 --limit-burst 5 允许的突发流量。之后返回包的序号变成 713192531,中间的包被防火墙丢弃,基本符合每分钟 10 个包,也就是大约 6 秒通过 1 个包的限制。

实验总结

这次实验主要对防火墙的三种实现方式进行了验证。

Netfilter 模块是在内核层直接拦截数据包,灵活性高,但需要编写和加载内核模块,操作风险也更高。iptables 则是在已有框架上配置规则,更适合日常管理和实验验证。无状态规则可以根据源地址、入口网卡、协议类型等字段快速决定是否放行,但它不理解连接上下文,只能做比较简单的访问控制。limit 模块进一步说明防火墙不仅能阻断流量,也可以对流量进行速率控制,从而减轻简单的泛洪攻击影响。

从结果看,外部主机无法主动访问内部服务器,内部服务器仍可以访问外部主机;限速规则也成功把连续 ICMP 流量控制在指定速率内,说明实验中的防火墙规则生效。

Like 0
本站已不稳定运行 小时 分钟
共发表文章 29 篇 ,总计 127.33 k 字
本站总访问量:
使用 Hugo 构建
主题 StackJimmy 设计