FTP 协议 基于 Netfilter Conntrack 的 动态端口 开放


由于 防火墙的默认策略 应该是DROP,对于像 FTP 这种在被动模式下,需要新开端口建立数据通道的协议而言,防火墙不能够主动的开辟相关的端口,也就在默认DROP情况下,FTP的数据端口不能通信,就会出现能登录成功,但获取不到目录的情况。关于 FTP 的介绍,可参考这篇 帖子

在 Linux 中,Netfilter 自带一个 conntrack 模块,于是就先以 ftp 协议为例研究了一下。其主要原理是,监控相应端口的流量,尝试获取到含有动态端口的数据包,然后,将相应端口的数据包标记成RELATED的数据(猜测是这样),因而,使其实现通信。

实现 ftp 动态端口的模块 文件是 net/netfilter/nf_conntrack_ftp.c,include/linux/netfilter/nf_conntrack_ftp.h,include/uapi/linux/netfilter/nf_conntrack_ftp.h,net/netfilter/nf_nat_ftp.c,还有一个 net/netfilter/ip_vs_ftp.c 文件暂时没看懂干啥的。这些文件均来自于 linux 内核文件。在 Ubuntu 18.04 系统中,以 apt-get install linux-source-5.0.0 的方式获取。

测试系统 Ubuntu 18.04,内核版本:5.0.0-31-generic,iptables 版本:1.6.1

nf_conntrack_ftp 模块加载

首先,了解如何开启linux系统自带的 ftp 协议的动态端口模块。

  1. 准备ftp客户端和服务器,linux下可使用vsftpd作为ftp服务器,相关安装方法,可参考这篇帖子。windows下的ftp客户端,可使用WinScp这个软件。
  2. 设置系统对conntrack模块的支持
    vim /etc/sysctl.conf
    添加一行:
    net.netfilter.nf_conntrack_helper=1
    然后使用sysctl -p使其生效。
    主要是新版系统需要加这个一句,旧版系统不需要,旧版似乎是默认开启的,此部分可参考这个文章
  3. 加载 nf_conntrack_ftp 模块
    modprobe nf_conntrack
    modprobe nf_conntrack_ftp
    
  4. iptables 测试规则(此处仅是ipv4的测试规则,ipv6需要用到ip6tables)
    iptables -F # 清空规则
    iptables -P INPUT ACCEPT
    iptables -P OUTPUT ACCEPT
    iptables -P FORWARD DROP # FORWARD链规则默认DROP
    iptables -A FORWARD -p tcp -m state --state NEW --dport 21 -j ACCEPT
    iptables -A FORWARD -p tcp -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
    # 按照对上面那篇文章的理解,应该是用下面这个规则的,
    # 但是,测试发现,只用下面这个反而通信不了,用上面那句规则,模块是会工作的。有点迷,可能没理解好。
    # iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -m helper --helper opc -p tcp -j ACCEPT
    
    此外,还可能需要要一条这样的规则
    iptables -A PREROUTING -t raw -p tcp --dport 21 -j CT --helper ftp
    

注意:
(1)必须要用-D选项删除上面这条规则后,nf_conntrack_ftp模块才能被卸载,否则卸载时会提示被使用。
(2)应使用 iptables -t raw-nL 查看这条规则的配置。
此处在 FORWARD 链上测试,也可自行改成INPUT和OUTPUT链,Ubuntu 18.04 使用 bridge_utils 配置网桥后,需要额外加载模块,FOWWARD 链规则才生效,相关操作可见这篇帖子
5. 开启 ftp 服务器后,使用 winscp 客户端对其进行访问测试。
默认情况下,winscp 是用 被动模式通信的,被动模式的确需要动态端口开放这这一策略。
不过首先要明确一点,防火墙上做动态端口开放,显然要求协议是明文的,加密是不行的,加密就没法从数据包里获得地址和动态端口信息了(除非知道秘钥,并且重写模块)。所以,连接设置如下:
在这里插入图片描述
如果选用主动模式通信,可点击上面界面中的“高级”,然后进入“连接”,取消“被动模式”的选中即可。主动模式,似乎只需要再开放20端口即可,不用conntrack,当然这和防火墙策略有关,也许不想实时开着20端口呢。
在这里插入图片描述
然后,连接就能读取成功,去除模块,或者减少iptables的放行规则,将无法通信。

nf_conntrack_ftp 模块粗读

今天太晚了,先不写了。

include/uapi/linux/netfilter/nf_conntrack_ftp.h

这个文件主要是定义了FTP几种模式:

/* This enum is exposed to userspace */
enum nf_ct_ftp_type {
   
	/* PORT command from client */
	NF_CT_FTP_PORT,
	/* PASV response from server */
	NF_CT_FTP_PASV,
	/* EPRT command from client */
	NF_CT_FTP_EPRT,
	/* EPSV response from server */
	NF_CT_FTP_EPSV,
};

上面两个是ipv4下面的主动和被动模式,下面两个则是ipv6下面的主动和被动模式

include/linux/netfilter/nf_conntrack_ftp.h

#define FTP_PORT	21

#define NF_CT_FTP_SEQ_PICKUP	(1 << 0)

#define NUM_SEQ_TO_REMEMBER 2
/* This structure exists only once per master */
struct nf_ct_ftp_master {
   
	/* Valid seq positions for cmd matching after newline */
	u_int32_t seq_aft_nl[IP_CT_DIR_MAX][NUM_SEQ_TO_REMEMBER];
	/* 0 means seq_match_aft_nl not set */
	u_int16_t seq_aft_nl_num[IP_CT_DIR_MAX];
	/* pickup sequence tracking, useful for conntrackd */
	u_int16_t flags[IP_CT_DIR_MAX];
};

没太懂这里的,但是能看出来时用来存储序列号的,就是conntrack时,要校验并更新数据流的序列号。如果序列号对不上,并不会放行这个包。

net/netfilter/nf_conntrack_ftp.c

#define MAX_PORTS 8
static u_int16_t ports[MAX_PORTS];
static unsigned int ports_c;
module_param_array(ports, ushort, &ports_c, 0400);

static bool loose;
module_param(loose, bool, 0600);

首先限制了最大监听的端口数量,不超过8个ftp服务器的端口。
module_param 这个貌似是可以在加载模块是传递参数,0400这个表明权限,但是不太懂这个loose变量到底是什么啥意思,修复的那个bug代表啥含义。

static int try_rfc959(const char *, size_t, struct nf_conntrack_man *,
		      char, unsigned int *);
static int try_rfc1123(const char *, size_t, struct nf_conntrack_man *,
		       char, unsigned int *);
static int try_eprt(const char *, size_t, struct nf_conntrack_man *,
		    char, unsigned int *);
static int try_epsv_response(const char *, size_t, struct nf_conntrack_man *,
			     char, unsigned int *);
			     
static struct ftp_search {
   
	const char *pattern;
	size_t plen;
	char skip;
	char term;
	enum nf_ct_ftp_type ftptype;
	int (*getnum)(const char *, size_t, struct nf_conntrack_man *, char, unsigned int *);
} search[IP_CT_DIR_MAX][2];

这四个函数,就是尝试解析 ftp 数据包的,分别以那四种情况,去尝试解析出 动态端口。
然后含有个search结构体数据,里面包含了四种情况对应的解析方法,getnum指针就指向这几个函数。

/* Return 1 for match, 0 for accept, -1 for partial. */
static int find_pattern(const char *data, size_t dlen,
			const char *pattern, size_t plen,
			char skip, char term,
			unsigned int *numoff,
			unsigned int *numlen,
			struct nf_conntrack_man *cmd,
			int (*getnum)(const char *, size_t,
				      struct nf_c
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值