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内核级防火墙

内核级防火墙和普通应用层过滤不同,是在内核中写一个模块,当包进入内核时就进行处理,拦截位置更靠前。这里需要实现让外部主机无法通过 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,新开一个终端,在宿主机进入源码目录,这里面包含 MakefileseedFilter.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包的日志,和上面代码对应,说明拦截规则生效

1
dmesg | tail -20

无状态防火墙

这部分是用 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 内部服务器,全部丢包

1
ping 192.168.60.5

在内部服务器中 Ping 外部主机,全部成功,说明达到了防火墙的目的

1
ping 10.9.0.5

网络流量限速

前两个部分都是直接丢弃所有外部特定流量,适用于不可访问的服务器,而这部分适用于可访问服务器。这里用 iptableslimit 模块限制特定源 IP 的发包速率,防止受到 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这个 IP 发出的 ICMP 包,只允许平均每分钟 10 个包通过,并允许最开始突发通过 5 个包
  • 丢掉10.9.0.5这个 IP 的其他 ICMP 包
  • 放行其他不匹配前两条规则的包,防止影响其他转发流量

在外部主机中 Ping 内部服务器

1
ping 192.168.60.5

可以看到前5个包全部通过,这是--limit-burst 5允许的突发流量,之后大约每6秒才会成功返回1个数据包,这就是--limit 10/minute在起限制

现实应用中还可以根据源 IP 和协议类型来设置限速,从而防止DoS。但这种方法无法避免DDoS

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