Firewall Exploration

Overview

防火墙的本质是查看对应的会话表,根据会话表中不同的内容和规则对数据包采取不同的操作

防火墙主要工作在网络层

iptables详解

IPtables系列讲解

Tasks

task1:使用防火墙

原理

当我们对于链、规则以及表有了一个初始概念的时候就可以做了,这里面明显要进行的就是过滤,这里是在A上面进行的防火墙设置,所以此时明显要操作的是input和output链

1-2

1-1

具体实现

  • 防止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),发现可以登录

    1-4

    之后在A上面设置INPUT链的filter表规则

sudo iptables -A INPUT -s 10.0.2.5 -j DROP

运行并查看filter表规则

1-5

此时在B上进行telnet会发现无法连接上

1-6

  • 阻止A对外部网站的访问

以百度为例,百度对应的IP地址为182.61.200.6和182.61.200.7,将其添加到对应的OUTPUT链里面,在添加前可以正常访问百度

1-7

运行以下命令

sudo iptables -A OUTPUT -d 182.61.200.6 -j DROP
sudo iptables -A OUTPUT -d 182.61.200.7 -j DROP

此时的filter表

1-8

采取ping操作来访问,操作不被允许,访问失败

1-9

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成功了

2-1

之后加载模块,加载成功

2-2

并且确保此时的filter表为空

2-3

之后开始测试

  • 首先是A(10.0.2.4)到B(10.0.2.5)的telnet和ssh测试,可以看到这些包都被drop掉了并且connection没有建立

    2-4

  • 之后是B到A的telnet和ssh测试,可以看到后面六条记录记录了从B到A进行telnet和ssh操作时被drop掉的包

    2-6

    2-5

  • 最后是访问从A访问百度的测试,在浏览器上访问百度

    2-7

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

3-1

3-2

之后运行下面两条命令

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表

3-3

task3.a:建立穿过防火墙的隧道

3-4

在这个task里面,我只采用了两个VM来实现,首先,当我们直接telnet B(10.0.2.5)的时候,会发现不能成功

3-5

之后运行下面的命令来建立A(端口为8000)和B(端口22)之间的ssh连接,并且设置B转发到本机的23端口

3-6

这时候已经建立好了ssh连接,当A上面从端口8000发送过去的数据包到达B的22端口的时候,B会转发到23端口,所以之后我们在主机A上面运行telnet localhost 8000的时候,从本质上来讲,是在将端口与localhost 8000发送TCP包来建立telnet连接,但是之后端口8000收到TCP数据包之后并不用来建立telnet连接,而是利用ssh隧道来实现转发,这时候wireshark抓包可以看见,并没有telnet数据包,而是ssh数据包

3-7

3-9

task3.b:通过ssh隧道来访问外部网站(以www.fudan.edu.cn为例)

使用了动态端口转发:当数据包到达VM B的时候,并不是静态的转发到目的地IP,而是根据包的信息来决定目的地

  • 将本地端口9000与B主机22端口建立ssh隧道

    3-10

    此时本地端口与B主机的22端口已经建立好了连接,之后就是浏览器与本地9000的连接的建立

  • 建立浏览器连接

    3-11

  • 访问以及抓包观察

    我们可以看到,访问www.fudan.edu.cn成功,并且通过抓数据包可以看到并不是通过10.0.2.4来直接进行访问202.120.224.81的,而是通过ssh连接来让B进行访问

    3-13

    3-12

    同时,当经过ssh隧道,由于并不在是直接访问对应IP,而是通过ssh连接来访问,所以绕过了防火墙,同时我们结合上面的图来一起分析,可以看到A到B是通过ssh,而B到对应的web服务器是通过http连接

    3-14

    之后断开ssh连接并且清除缓存,此时发现代理拒绝连接

    3-15

    之后当我们重新家里ssh隧道之后,我们又可以看到对应界面,就和之前访问成功的时候一样

task4:规避进入过滤

首先是要进行访问的内部网络的网络页面

4-1

下面是要输入的防火墙的INPUT里面的规则以及此时防火墙filter表中的规则

4-2

之后我们无法通过建立ssh连接以及http进行访问,因为这时候从外部进行访问的ssh连接也被屏蔽掉了,之后我们需要建立一个reverse连接,原理是此时并不制止从内部发往外部的ssh连接,我们建立了反向ssh隧道

4-3

此时A的80端口已经与远程端的8000端口建立了连接,并且进行转发的主机是10.0.2.5

之后可以看到,成功访问

4-4

原理示意图:

4-5

参考blog:reverse ssh在最后how does the reverse tunnel work

复盘

这个lab给我的感觉就是太过于狭小、太偏向于技术层面而失去了对于整体系统的把握,虽然能够对于iptables这一种防火墙有所了解,但是对于所有的防火墙的认知还是有一点匮乏,其中task3和task4有比较好的理解,task2其实感觉就是task1的一种具体实现


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

Firewall_VPN 上一篇
RBAC访问控制 下一篇