Firewall Exploration
Overview
防火墙的本质是查看对应的会话表,根据会话表中不同的内容和规则对数据包采取不同的操作
防火墙主要工作在网络层
Tasks
task1:使用防火墙
原理
当我们对于链、规则以及表有了一个初始概念的时候就可以做了,这里面明显要进行的就是过滤,这里是在A上面进行的防火墙设置,所以此时明显要操作的是input和output链
具体实现
防止A(10.0.2.4)对B(10.0.2.5)进行telnet操作
这时候在A的output链上面添加filter表规则(防火墙运行在A上面),输入命令
sudo iptables -A OUTPUT -d 10.0.2.5 -j DROP
之后查看对应的filter表的内容并且ping 10.0.2.5,会发现一直在trying,阻止telnet成功
防止B对A进行telnet操作
一样的道理,首先未设置防火墙规则的时候在B上telnet A(10.0.2.4),发现可以登录
之后在A上面设置INPUT链的filter表规则
sudo iptables -A INPUT -s 10.0.2.5 -j DROP
运行并查看filter表规则
此时在B上进行telnet会发现无法连接上
- 阻止A对外部网站的访问
以百度为例,百度对应的IP地址为182.61.200.6和182.61.200.7,将其添加到对应的OUTPUT链里面,在添加前可以正常访问百度
运行以下命令
sudo iptables -A OUTPUT -d 182.61.200.6 -j DROP
sudo iptables -A OUTPUT -d 182.61.200.7 -j DROP
此时的filter表
采取ping操作来访问,操作不被允许,访问失败
task2:实现一个简单的防火墙
LKM允许我们在运行期间给内核添加一个新的模块。这种新的模块使我们能扩展内核功能,而无需重建核心甚至是重启电脑。作为LKM中防火墙的包过滤部分是可以被实现的。然而,这还不够。为了使过滤模块能够阻止传入/传出的数据包,必须将模块插入到数据包处理路径中。
Netfilter旨在促进授权用户对数据包的操作。Netfilter通过在Linux内核中实现许多hooks来实现此目标。这些钩子被插入到多个地点,包括数据包流入与流出的路径。如果我们想操作流入的数据包,我们只需要连接我们的程序(在LKM内)到相应的钩子上即可。当一个流入数据包到达,我们的程序将被唤醒。我们的程序可以决定这个数据包是否该被阻止或是放行;此外,我们还可以在程序中修改数据包。
我们写了一个调用hook_func的函数,当满足nf_hook_ops条件的包出现时,它就会被调用。这个函数执行适当的逻辑,在我们的例子中就是丢弃数据包。我们填入nf_hook_ops结构,它定义了特定的Netfilter钩子到target target,它的优先级(如果你有多个钩子,这很重要),并给目标包的类型(在本例中是IPV4),并将hook_func绑定到它
在这里除了task1之外新增了两个规则:阻止A到B的SSH连接以及阻止B到A的SSH连接
一定不要让自己的文件夹有空格!!!!!
具体实现
终于make成功了
之后加载模块,加载成功
并且确保此时的filter表为空
之后开始测试
首先是A(10.0.2.4)到B(10.0.2.5)的telnet和ssh测试,可以看到这些包都被drop掉了并且connection没有建立
之后是B到A的telnet和ssh测试,可以看到后面六条记录记录了从B到A进行telnet和ssh操作时被drop掉的包
最后是访问从A访问百度的测试,在浏览器上访问百度
code
#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/inet.h>
static struct nf_hook_ops FilterHookRule_1;
static struct nf_hook_ops FilterHookRule_2;
static struct nf_hook_ops FilterHookRule_3;
static struct nf_hook_ops FilterHookRule_4;
static struct nf_hook_ops FilterHookRule_5;
int eq_daddr(const struct iphdr *iph, const char *ip_addr)
{
//check if the dst ip address equals the given address
char source[16];
snprintf(source, 16, "%pI4", &iph->daddr);
if (strcmp(source, ip_addr) == 0)
return 1;
return 0;
}
int eq_saddr(const struct iphdr *iph, const char *ip_addr)
{
//check if the src ip address equals the given address
char source[16];
snprintf(source, 16, "%pI4", &iph->saddr);
if (strcmp(source, ip_addr) == 0)
return 1;
return 0;
}
unsigned int Filter_1(void * priv, struct sk_buff * skb, const struct nf_hook_state * state)
{
//考虑task1.1规则的实现,防止AtelnetB
struct iphdr *iph;
struct tcphdr *tcph;
iph = ip_hdr(skb);
tcph = (void *)iph + iph->ihl * 4;
if (iph->protocol == IPPROTO_TCP && tcph->dest == htons(23) && eq_daddr(iph, "10.0.2.5") && eq_saddr(iph, "10.0.2.4"))
{
printk(KERN_INFO "Dropping telnet from %pI4 packet to %pI4\n", &iph->saddr, &iph->daddr);
return NF_DROP;
}
else
{
return NF_ACCEPT;
}
}
unsigned int Filter_2(void * priv, struct sk_buff * skb, const struct nf_hook_state * state)
{
//考虑task1.2规则的实现,防止BtelnetA
struct iphdr *iph;
struct tcphdr *tcph;
iph = ip_hdr(skb);
tcph = (void *)iph + iph->ihl * 4;
if (iph->protocol == IPPROTO_TCP && tcph->dest == htons(23) && eq_daddr(iph, "10.0.2.4") && eq_saddr(iph, "10.0.2.5"))
{
printk(KERN_INFO "Dropping telnet from %pI4 packet to %pI4\n", &iph->saddr, &iph->daddr);
return NF_DROP;
}
else
{
return NF_ACCEPT;
}
}
unsigned int Filter_3(void * priv, struct sk_buff * skb, const struct nf_hook_state * state)
{
//考虑task1.3规则的实现,防止A访问外部网站,比如百度
struct iphdr *iph;
struct tcphdr *tcph;
iph = ip_hdr(skb);
tcph = (void *)iph + iph->ihl * 4;
if (iph->protocol == IPPROTO_TCP && (tcph->dest == htons(80) || tcph->dest == htons(443)) && (eq_daddr(iph, "180.101.49.11") || eq_daddr(iph, "180.101.49.12")) && eq_saddr(iph, "10.0.2.4"))
{
printk(KERN_INFO "Dropping web packet from %pI4 packet to %pI4\n", &iph->saddr, &iph->daddr);
return NF_DROP;
}
else
{
return NF_ACCEPT;
}
}
unsigned int Filter_4(void * priv, struct sk_buff * skb, const struct nf_hook_state * state)
{
//新规则的实现,防止B ssh A
struct iphdr *iph;
struct tcphdr *tcph;
iph = ip_hdr(skb);
tcph = (void *)iph + iph->ihl * 4;
if (iph->protocol == IPPROTO_TCP && tcph->dest == htons(22) && eq_daddr(iph, "10.0.2.4") && eq_saddr(iph, "10.0.2.5"))
{
printk(KERN_INFO "Dropping ssh from %pI4 packet to %pI4\n", &iph->saddr, &iph->daddr);
return NF_DROP;
}
else
{
return NF_ACCEPT;
}
}
unsigned int Filter_5(void * priv, struct sk_buff * skb, const struct nf_hook_state * state)
{
//新规则的实现,防止A ssh B
struct iphdr *iph;
struct tcphdr *tcph;
iph = ip_hdr(skb);
tcph = (void *)iph + iph->ihl * 4;
if (iph->protocol == IPPROTO_TCP && tcph->dest == htons(22) && eq_daddr(iph, "10.0.2.5") && eq_saddr(iph, "10.0.2.4"))
{
printk(KERN_INFO "Dropping ssh from %pI4 packet to %pI4\n", &iph->saddr, &iph->daddr);
return NF_DROP;
}
else
{
return NF_ACCEPT;
}
}
int setUpFilter(void)
{
printk(KERN_INFO "Registering filters.\n");
FilterHookRule_1.hook = Filter_1; //对应的function
FilterHookRule_1.hooknum = NF_INET_LOCAL_OUT; //对应OUTPUT链
FilterHookRule_1.pf = PF_INET;
FilterHookRule_1.priority = NF_IP_PRI_FIRST; //优先级
nf_register_hook(&FilterHookRule_1);
FilterHookRule_2.hook = Filter_2; //对应的function
FilterHookRule_2.hooknum = NF_INET_LOCAL_IN; //对应input链
FilterHookRule_2.pf = PF_INET;
FilterHookRule_2.priority = NF_IP_PRI_FIRST; //优先级
nf_register_hook(&FilterHookRule_2);
FilterHookRule_3.hook = Filter_3; //对应的function
FilterHookRule_3.hooknum = NF_INET_LOCAL_OUT; //对应OUTPUT链
FilterHookRule_3.pf = PF_INET;
FilterHookRule_3.priority = NF_IP_PRI_FIRST; //优先级
nf_register_hook(&FilterHookRule_3);
FilterHookRule_4.hook = Filter_4; //对应的function
FilterHookRule_4.hooknum = NF_INET_LOCAL_IN; //对应INPUT链
FilterHookRule_4.pf = PF_INET;
FilterHookRule_4.priority = NF_IP_PRI_FIRST; //优先级
nf_register_hook(&FilterHookRule_4);
FilterHookRule_5.hook = Filter_5; //对应的function
FilterHookRule_5.hooknum = NF_INET_LOCAL_OUT; //对应OUTPUT链
FilterHookRule_5.pf = PF_INET;
FilterHookRule_5.priority = NF_IP_PRI_FIRST; //优先级
nf_register_hook(&FilterHookRule_5);
return 0;
}
void removeFilter(void)
{
nf_unregister_hook(&FilterHookRule_1);
nf_unregister_hook(&FilterHookRule_2);
nf_unregister_hook(&FilterHookRule_3);
nf_unregister_hook(&FilterHookRule_4);
nf_unregister_hook(&FilterHookRule_5);
}
module_init(setUpFilter);
module_exit(removeFilter);
MODULE_LICENSE("GPL");
task2复盘
文件夹命名尽量不要带空格,makefile哭泣
其实这个task的思路还是比较简单的,本质上来讲就是当满足对应的条件(匹配条件if)的时候,来完成相应的东作用(drop)。其实是引入的比较新的东西的话也就是LKM和netfilter的与挂钩点register的东西,不存在理解上面的难点
task3:规避出口过滤
实验背景
we show how such egress filtering can be bypassed using the tunnel mechanism. There are many ways to establish tunnels; in this task, we only focus on SSH tunnels.
实验过程
设置iptables来屏蔽对应的telnet连接和发往www.fudan.edu.cn主机的数据包
首先,查看现在的filter表并且查询www.fudan.edu.cn的IP地址,发现是202.120.224.81
之后运行下面两条命令
sudo iptables -A OUTPUT -s 10.0.2.4 -p tcp --dport 23 -j DROP
sudo iptables -A OUTPUT -s 10.0.2.4 -d 202.120.224.81 -j DROP
之后查看filter表
task3.a:建立穿过防火墙的隧道
在这个task里面,我只采用了两个VM来实现,首先,当我们直接telnet B(10.0.2.5)的时候,会发现不能成功
之后运行下面的命令来建立A(端口为8000)和B(端口22)之间的ssh连接,并且设置B转发到本机的23端口
这时候已经建立好了ssh连接,当A上面从端口8000发送过去的数据包到达B的22端口的时候,B会转发到23端口,所以之后我们在主机A上面运行telnet localhost 8000的时候,从本质上来讲,是在将端口与localhost 8000发送TCP包来建立telnet连接,但是之后端口8000收到TCP数据包之后并不用来建立telnet连接,而是利用ssh隧道来实现转发,这时候wireshark抓包可以看见,并没有telnet数据包,而是ssh数据包
task3.b:通过ssh隧道来访问外部网站(以www.fudan.edu.cn为例)
使用了动态端口转发:当数据包到达VM B的时候,并不是静态的转发到目的地IP,而是根据包的信息来决定目的地
将本地端口9000与B主机22端口建立ssh隧道
此时本地端口与B主机的22端口已经建立好了连接,之后就是浏览器与本地9000的连接的建立
建立浏览器连接
访问以及抓包观察
我们可以看到,访问www.fudan.edu.cn成功,并且通过抓数据包可以看到并不是通过10.0.2.4来直接进行访问202.120.224.81的,而是通过ssh连接来让B进行访问
同时,当经过ssh隧道,由于并不在是直接访问对应IP,而是通过ssh连接来访问,所以绕过了防火墙,同时我们结合上面的图来一起分析,可以看到A到B是通过ssh,而B到对应的web服务器是通过http连接
之后断开ssh连接并且清除缓存,此时发现代理拒绝连接
之后当我们重新家里ssh隧道之后,我们又可以看到对应界面,就和之前访问成功的时候一样
task4:规避进入过滤
首先是要进行访问的内部网络的网络页面
下面是要输入的防火墙的INPUT里面的规则以及此时防火墙filter表中的规则
之后我们无法通过建立ssh连接以及http进行访问,因为这时候从外部进行访问的ssh连接也被屏蔽掉了,之后我们需要建立一个reverse连接,原理是此时并不制止从内部发往外部的ssh连接,我们建立了反向ssh隧道
此时A的80端口已经与远程端的8000端口建立了连接,并且进行转发的主机是10.0.2.5
之后可以看到,成功访问
原理示意图:
参考blog:reverse ssh在最后、how does the reverse tunnel work
复盘
这个lab给我的感觉就是太过于狭小、太偏向于技术层面而失去了对于整体系统的把握,虽然能够对于iptables这一种防火墙有所了解,但是对于所有的防火墙的认知还是有一点匮乏,其中task3和task4有比较好的理解,task2其实感觉就是task1的一种具体实现
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!